删除无用的html和图片
This commit is contained in:
parent
4df395b2ad
commit
697a26300e
Binary file not shown.
|
Before Width: | Height: | Size: 365 KiB |
BIN
squearbj.png
BIN
squearbj.png
Binary file not shown.
|
Before Width: | Height: | Size: 412 KiB |
420
横向瀑布流.html
420
横向瀑布流.html
@ -1,420 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
||||||
<title>横向瀑布流 · 完美填满与呼吸间隙</title>
|
|
||||||
<style>
|
|
||||||
* { margin:0; padding:0; box-sizing:border-box; }
|
|
||||||
body { background:#0f0f14; overflow:hidden; height:100vh; height:-webkit-fill-available; font-family:system-ui, -apple-system, sans-serif; }
|
|
||||||
.scroll-container {
|
|
||||||
position:fixed; top:0; left:0; width:100vw; height:100vh; height:-webkit-fill-available;
|
|
||||||
overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; cursor:grab;
|
|
||||||
}
|
|
||||||
.scroll-container:active { cursor:grabbing; }
|
|
||||||
.scroll-container.dragging { cursor:grabbing; }
|
|
||||||
.waterfall-inner { position:relative; height:100%; min-height:100%; }
|
|
||||||
.card {
|
|
||||||
position:absolute; border-radius:14px; overflow:hidden; cursor:pointer;
|
|
||||||
transition: transform 0.25s, box-shadow 0.3s; will-change:transform;
|
|
||||||
box-shadow:0 4px 18px rgba(0,0,0,0.35); border:1px solid rgba(255,255,255,0.06);
|
|
||||||
background-size:cover; background-position:center;
|
|
||||||
display:flex; flex-direction:column; justify-content:flex-end; align-items:center;
|
|
||||||
}
|
|
||||||
.card:active { transform:scale(0.96); }
|
|
||||||
.card .pattern-layer { position:absolute; inset:0; opacity:0.25; pointer-events:none; }
|
|
||||||
.card .likes {
|
|
||||||
position:relative; z-index:1; color:white; font-weight:700; font-size:1rem;
|
|
||||||
text-shadow:0 2px 6px rgba(0,0,0,0.7); margin-bottom:12px; user-select:none;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
position:fixed; top:0; left:0; right:0; z-index:100; padding:12px 18px;
|
|
||||||
display:flex; align-items:center; justify-content:space-between;
|
|
||||||
background:linear-gradient(180deg, rgba(15,15,20,0.95) 50%, rgba(15,15,20,0) 100%);
|
|
||||||
pointer-events:none;
|
|
||||||
}
|
|
||||||
.header > * { pointer-events:auto; }
|
|
||||||
.header-title { font-size:1.05rem; font-weight:700; color:#fff; display:flex; align-items:center; gap:8px; }
|
|
||||||
.header-dot { width:9px; height:9px; border-radius:50%; background:#ff6b6b; animation:pulse 2s infinite; }
|
|
||||||
@keyframes pulse { 0%,100%{box-shadow:0 0 0 0 rgba(255,107,107,0.6)} 50%{box-shadow:0 0 0 12px rgba(255,107,107,0)} }
|
|
||||||
.status { color:#ccc; font-size:0.8rem; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header">
|
|
||||||
<div class="header-title"><div class="header-dot"></div>灵感瀑布</div>
|
|
||||||
<div class="status" id="statusText">自动滚动中</div>
|
|
||||||
</div>
|
|
||||||
<div class="scroll-container" id="scrollContainer">
|
|
||||||
<div class="waterfall-inner" id="waterfallInner"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
(() => {
|
|
||||||
const container = document.getElementById('scrollContainer');
|
|
||||||
const inner = document.getElementById('waterfallInner');
|
|
||||||
const statusText = document.getElementById('statusText');
|
|
||||||
|
|
||||||
// ---------- 布局引擎(带列间隙填充)----------
|
|
||||||
class WaterfallLayout {
|
|
||||||
constructor(containerHeight, unitHeight = 35, minGap = 10, jitter = 0.4) {
|
|
||||||
this.containerHeight = containerHeight;
|
|
||||||
this.unitHeight = unitHeight;
|
|
||||||
this.minGap = minGap;
|
|
||||||
this.jitter = jitter;
|
|
||||||
this.recalcUnits();
|
|
||||||
}
|
|
||||||
|
|
||||||
recalcUnits() {
|
|
||||||
this.unitCount = Math.max(4, Math.round(this.containerHeight / this.unitHeight));
|
|
||||||
this.actualUnitHeight = this.containerHeight / this.unitCount;
|
|
||||||
this.rowCurrentX = new Array(this.unitCount).fill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分段高度
|
|
||||||
computeHeights(allCards) {
|
|
||||||
const n = allCards.length;
|
|
||||||
if (n === 0) return new Map();
|
|
||||||
const sortedLikes = [...allCards.map(c => c.likes)].sort((a,b)=>a-b);
|
|
||||||
const heightMap = new Map();
|
|
||||||
const perturbation = 0.08;
|
|
||||||
|
|
||||||
for (const card of allCards) {
|
|
||||||
let less = 0, equal = 0;
|
|
||||||
for (const v of sortedLikes) {
|
|
||||||
if (v < card.likes) less++;
|
|
||||||
else if (v === card.likes) equal++;
|
|
||||||
}
|
|
||||||
const percentile = (less + equal * 0.5) / n;
|
|
||||||
let ratio;
|
|
||||||
if (percentile < 0.60) ratio = 0.25;
|
|
||||||
else if (percentile < 0.85) ratio = 0.333;
|
|
||||||
else if (percentile < 0.96) ratio = 0.5;
|
|
||||||
else ratio = 1.0;
|
|
||||||
|
|
||||||
const noise = 1 + (Math.random() - 0.5) * 2 * perturbation;
|
|
||||||
let rawHeight = this.containerHeight * ratio * noise;
|
|
||||||
rawHeight = Math.max(this.containerHeight * 0.22, Math.min(this.containerHeight, rawHeight));
|
|
||||||
const units = Math.round(rawHeight / this.actualUnitHeight);
|
|
||||||
const clampedUnits = Math.max(1, Math.min(units, this.unitCount));
|
|
||||||
const finalHeight = clampedUnits * this.actualUnitHeight;
|
|
||||||
heightMap.set(card.id, finalHeight);
|
|
||||||
}
|
|
||||||
return heightMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全量计算
|
|
||||||
compute(cardsData) {
|
|
||||||
this.rowCurrentX = new Array(this.unitCount).fill(0);
|
|
||||||
const heightMap = this.computeHeights(cardsData);
|
|
||||||
const rawCards = [];
|
|
||||||
for (const card of cardsData) {
|
|
||||||
const height = heightMap.get(card.id);
|
|
||||||
const width = (height * 9) / 16;
|
|
||||||
const units = Math.round(height / this.actualUnitHeight);
|
|
||||||
const span = Math.max(1, Math.min(units, this.unitCount));
|
|
||||||
|
|
||||||
let bestRow = 0, bestX = Infinity;
|
|
||||||
for (let r = 0; r <= this.unitCount - span; r++) {
|
|
||||||
let maxX = 0;
|
|
||||||
for (let s = 0; s < span; s++) maxX = Math.max(maxX, this.rowCurrentX[r + s]);
|
|
||||||
if (maxX < bestX) { bestX = maxX; bestRow = r; }
|
|
||||||
}
|
|
||||||
const left = bestX;
|
|
||||||
const top = bestRow * this.actualUnitHeight;
|
|
||||||
const gap = this.minGap + (Math.random() - 0.5) * this.minGap * this.jitter * 2;
|
|
||||||
const newX = left + width + gap;
|
|
||||||
for (let s = 0; s < span; s++) this.rowCurrentX[bestRow + s] = newX;
|
|
||||||
|
|
||||||
rawCards.push({ ...card, left, top, width, height, spanUnits: span });
|
|
||||||
}
|
|
||||||
// 应用列间隙分布
|
|
||||||
return this.applyColumnGaps(rawCards);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 追加卡片并应用间隙
|
|
||||||
addCards(newCards, allCardsSoFar) {
|
|
||||||
const heightMap = this.computeHeights(allCardsSoFar);
|
|
||||||
const rawCards = [];
|
|
||||||
for (const card of newCards) {
|
|
||||||
const height = heightMap.get(card.id);
|
|
||||||
const width = (height * 9) / 16;
|
|
||||||
const units = Math.round(height / this.actualUnitHeight);
|
|
||||||
const span = Math.max(1, Math.min(units, this.unitCount));
|
|
||||||
|
|
||||||
let bestRow = 0, bestX = Infinity;
|
|
||||||
for (let r = 0; r <= this.unitCount - span; r++) {
|
|
||||||
let maxX = 0;
|
|
||||||
for (let s = 0; s < span; s++) maxX = Math.max(maxX, this.rowCurrentX[r + s]);
|
|
||||||
if (maxX < bestX) { bestX = maxX; bestRow = r; }
|
|
||||||
}
|
|
||||||
const left = bestX;
|
|
||||||
const top = bestRow * this.actualUnitHeight;
|
|
||||||
const gap = this.minGap + (Math.random() - 0.5) * this.minGap * this.jitter * 2;
|
|
||||||
const newX = left + width + gap;
|
|
||||||
for (let s = 0; s < span; s++) this.rowCurrentX[bestRow + s] = newX;
|
|
||||||
|
|
||||||
rawCards.push({ ...card, left, top, width, height, spanUnits: span });
|
|
||||||
}
|
|
||||||
return this.applyColumnGaps(rawCards);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 核心:将每一列的剩余垂直空间随机分配为卡片间隙
|
|
||||||
applyColumnGaps(cards) {
|
|
||||||
if (cards.length === 0) return cards;
|
|
||||||
// 按 left 聚类成列(同一列的卡片 left 几乎相同)
|
|
||||||
const sorted = [...cards].sort((a,b) => a.left - b.left);
|
|
||||||
const columns = [];
|
|
||||||
let currentCol = [sorted[0]];
|
|
||||||
for (let i = 1; i < sorted.length; i++) {
|
|
||||||
const prev = currentCol[currentCol.length - 1];
|
|
||||||
if (Math.abs(sorted[i].left - prev.left) < this.minGap * 2) {
|
|
||||||
currentCol.push(sorted[i]);
|
|
||||||
} else {
|
|
||||||
columns.push(currentCol);
|
|
||||||
currentCol = [sorted[i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
columns.push(currentCol);
|
|
||||||
|
|
||||||
// 处理每一列
|
|
||||||
for (const col of columns) {
|
|
||||||
// 按 top 排序
|
|
||||||
col.sort((a,b) => a.top - b.top);
|
|
||||||
const sumHeight = col.reduce((s, c) => s + c.height, 0);
|
|
||||||
const slack = this.containerHeight - sumHeight;
|
|
||||||
if (slack <= 0) continue; // 已满或超出,保持原样
|
|
||||||
|
|
||||||
const n = col.length;
|
|
||||||
// 生成 n-1 个间隙(如果只有一张卡片,则将 slack 分配为顶部间隙?不,单卡片直接全高即可,无需间隙)
|
|
||||||
if (n === 1) {
|
|
||||||
// 单卡片:可稍微增加高度到容器高度,或者保持原高度在顶部,底部留白就无法避免。
|
|
||||||
// 为了视觉一致,不改变单卡高度,但可以居中?为了简单,不做变动。
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 n-1 个随机权重,归一化使总和为 slack
|
|
||||||
const rawWeights = Array.from({ length: n - 1 }, () => Math.random());
|
|
||||||
const weightSum = rawWeights.reduce((a,b) => a + b, 0) || 1;
|
|
||||||
const gaps = rawWeights.map(w => (w / weightSum) * slack);
|
|
||||||
|
|
||||||
// 调整每张卡片的 top
|
|
||||||
let currentTop = col[0].top;
|
|
||||||
col[0].finalTop = currentTop;
|
|
||||||
for (let i = 1; i < n; i++) {
|
|
||||||
currentTop += col[i-1].height + gaps[i-1];
|
|
||||||
col[i].finalTop = currentTop;
|
|
||||||
}
|
|
||||||
// 最后一张卡片底部:currentTop + lastCard.height 应等于 containerHeight
|
|
||||||
// 为精确,可微调最后一个间隙
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合并 finalTop 到卡片对象
|
|
||||||
const updatedCards = cards.map(card => {
|
|
||||||
const colCard = columns.flat().find(c => c.id === card.id);
|
|
||||||
return {
|
|
||||||
...card,
|
|
||||||
top: colCard?.finalTop !== undefined ? colCard.finalTop : card.top,
|
|
||||||
// 高度不变
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return updatedCards;
|
|
||||||
}
|
|
||||||
|
|
||||||
getTotalWidth() {
|
|
||||||
return Math.max(...this.rowCurrentX) + this.minGap * 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- 颜色/图案 ----------
|
|
||||||
const palettes = [
|
|
||||||
{ grad: 'linear-gradient(135deg, #2d1b69 0%, #4c1d95 50%, #5b21b6 100%)', acc: '#a78bfa' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #0c4a6e 0%, #0369a1 50%, #0284c7 100%)', acc: '#38bdf8' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #450a0a 0%, #991b1b 50%, #b91c1c 100%)', acc: '#f87171' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #064e3b 0%, #047857 50%, #059669 100%)', acc: '#4ade80' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #3b0764 0%, #6d28d9 50%, #7c3aed 100%)', acc: '#c084fc' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #78350f 0%, #b45309 50%, #d97706 100%)', acc: '#fbbf24' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #1e3a5f 0%, #1d4ed8 50%, #2563eb 100%)', acc: '#60a5fa' },
|
|
||||||
{ grad: 'linear-gradient(135deg, #4a1942 0%, #9d174d 50%, #be185d 100%)', acc: '#f472b6' },
|
|
||||||
];
|
|
||||||
function randomPalette() { return palettes[Math.floor(Math.random() * palettes.length)]; }
|
|
||||||
function patternSVG(acc, seed) {
|
|
||||||
const op = '0.15';
|
|
||||||
const shapes = ['circles','triangles','dots','waves'];
|
|
||||||
const s = shapes[seed % shapes.length];
|
|
||||||
if(s==='circles') return `<svg width="100%" height="100%" viewBox="0 0 200 150" preserveAspectRatio="none"><circle cx="30" cy="30" r="25" fill="none" stroke="${acc}" stroke-width="2" opacity="${op}"/><circle cx="160" cy="50" r="35" fill="none" stroke="${acc}" stroke-width="1.5" opacity="${op}"/><circle cx="80" cy="110" r="20" fill="none" stroke="${acc}" stroke-width="2" opacity="${op}"/></svg>`;
|
|
||||||
if(s==='triangles') return `<svg width="100%" height="100%" viewBox="0 0 200 150" preserveAspectRatio="none"><polygon points="40,20 70,70 10,70" fill="none" stroke="${acc}" stroke-width="1.5" opacity="${op}"/><polygon points="150,30 180,80 120,80" fill="none" stroke="${acc}" stroke-width="1" opacity="${op}"/></svg>`;
|
|
||||||
if(s==='dots') return `<svg width="100%" height="100%" viewBox="0 0 200 150" preserveAspectRatio="none"><circle cx="25" cy="25" r="3" fill="${acc}" opacity="${op}"/><circle cx="60" cy="40" r="2" fill="${acc}" opacity="${op}"/><circle cx="100" cy="20" r="4" fill="${acc}" opacity="${op}"/><circle cx="150" cy="55" r="2.5" fill="${acc}" opacity="${op}"/></svg>`;
|
|
||||||
if(s==='waves') return `<svg width="100%" height="100%" viewBox="0 0 200 150" preserveAspectRatio="none"><path d="M0,60 Q50,30 100,60 Q150,90 200,60" fill="none" stroke="${acc}" stroke-width="2" opacity="${op}"/><path d="M0,90 Q50,70 100,90 Q150,110 200,90" fill="none" stroke="${acc}" stroke-width="1.5" opacity="${op}"/></svg>`;
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- 全局状态 ----------
|
|
||||||
let allCards = [];
|
|
||||||
let idCounter = 0;
|
|
||||||
const layout = new WaterfallLayout(window.innerHeight - 80, 35, 10, 0.4);
|
|
||||||
let autoScrollActive = true;
|
|
||||||
const autoScrollSpeed = 1.2;
|
|
||||||
let userInteracting = false;
|
|
||||||
let interactionTimeout = null;
|
|
||||||
const AUTO_RESUME_DELAY = 2500;
|
|
||||||
|
|
||||||
function createCardElement(cardData) {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'card';
|
|
||||||
div.dataset.id = cardData.id;
|
|
||||||
div.style.left = cardData.left + 'px';
|
|
||||||
div.style.top = cardData.top + 'px';
|
|
||||||
div.style.width = cardData.width + 'px';
|
|
||||||
div.style.height = cardData.height + 'px';
|
|
||||||
div.style.background = cardData.palette.grad;
|
|
||||||
const pattern = document.createElement('div');
|
|
||||||
pattern.className = 'pattern-layer';
|
|
||||||
pattern.innerHTML = patternSVG(cardData.palette.acc, cardData.id);
|
|
||||||
div.appendChild(pattern);
|
|
||||||
const likesSpan = document.createElement('div');
|
|
||||||
likesSpan.className = 'likes';
|
|
||||||
likesSpan.textContent = '❤️ ' + cardData.likes;
|
|
||||||
div.appendChild(likesSpan);
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAllCards() {
|
|
||||||
inner.innerHTML = '';
|
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
allCards.forEach(c => frag.appendChild(createCardElement(c)));
|
|
||||||
inner.appendChild(frag);
|
|
||||||
inner.style.width = layout.getTotalWidth() + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCardDOM(cardData) {
|
|
||||||
const el = document.querySelector(`.card[data-id="${cardData.id}"]`);
|
|
||||||
if (!el) return;
|
|
||||||
el.style.left = cardData.left + 'px';
|
|
||||||
el.style.top = cardData.top + 'px';
|
|
||||||
el.style.width = cardData.width + 'px';
|
|
||||||
el.style.height = cardData.height + 'px';
|
|
||||||
const likesEl = el.querySelector('.likes');
|
|
||||||
if (likesEl) likesEl.textContent = '❤️ ' + cardData.likes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateBatch(count) {
|
|
||||||
const batch = [];
|
|
||||||
const minLikes = 20;
|
|
||||||
const maxLikes = 1800;
|
|
||||||
const alpha = 0.7;
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
const u = Math.random();
|
|
||||||
const likes = Math.floor(minLikes + (maxLikes - minLikes) * Math.pow(1 - u, 1/alpha));
|
|
||||||
batch.push({ id: idCounter++, likes, palette: randomPalette() });
|
|
||||||
}
|
|
||||||
return batch;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initLoad() {
|
|
||||||
const initialCards = generateBatch(50);
|
|
||||||
allCards = layout.compute(initialCards);
|
|
||||||
renderAllCards();
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendMore(count = 35) {
|
|
||||||
const newCards = generateBatch(count);
|
|
||||||
const placed = layout.addCards(newCards, allCards);
|
|
||||||
allCards.push(...placed);
|
|
||||||
const frag = document.createDocumentFragment();
|
|
||||||
placed.forEach(c => frag.appendChild(createCardElement(c)));
|
|
||||||
inner.appendChild(frag);
|
|
||||||
inner.style.width = layout.getTotalWidth() + 'px';
|
|
||||||
|
|
||||||
const leftEdge = container.scrollLeft - 600;
|
|
||||||
allCards.forEach(c => {
|
|
||||||
if (c.left + c.width < leftEdge) {
|
|
||||||
const el = document.querySelector(`.card[data-id="${c.id}"]`);
|
|
||||||
if (el) el.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (allCards.length > 200) allCards = allCards.slice(-150);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLike(cardId) {
|
|
||||||
const idx = allCards.findIndex(c => c.id == cardId);
|
|
||||||
if (idx === -1) return;
|
|
||||||
allCards[idx].likes += Math.floor(20 + Math.random() * 80);
|
|
||||||
const plainData = allCards.map(c => ({ id: c.id, likes: c.likes, palette: c.palette }));
|
|
||||||
const newLayout = layout.compute(plainData);
|
|
||||||
allCards = newLayout.map((c, i) => ({ ...c, palette: allCards[i].palette }));
|
|
||||||
allCards.forEach(c => updateCardDOM(c));
|
|
||||||
inner.style.width = layout.getTotalWidth() + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
function autoScroll() {
|
|
||||||
if (!autoScrollActive || userInteracting) return;
|
|
||||||
container.scrollLeft += autoScrollSpeed;
|
|
||||||
const maxScroll = inner.scrollWidth - container.clientWidth;
|
|
||||||
if (container.scrollLeft >= maxScroll - 600) {
|
|
||||||
appendMore(35);
|
|
||||||
}
|
|
||||||
requestAnimationFrame(() => autoScroll());
|
|
||||||
}
|
|
||||||
|
|
||||||
function pauseAutoScroll() {
|
|
||||||
userInteracting = true;
|
|
||||||
statusText.textContent = '手动模式';
|
|
||||||
clearTimeout(interactionTimeout);
|
|
||||||
interactionTimeout = setTimeout(() => {
|
|
||||||
userInteracting = false;
|
|
||||||
autoScrollActive = true;
|
|
||||||
statusText.textContent = '自动滚动中';
|
|
||||||
}, AUTO_RESUME_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
container.addEventListener('wheel', (e) => {
|
|
||||||
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
|
|
||||||
e.preventDefault();
|
|
||||||
container.scrollLeft += e.deltaY;
|
|
||||||
pauseAutoScroll();
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
let dragging = false, startX, startScroll;
|
|
||||||
container.addEventListener('pointerdown', (e) => {
|
|
||||||
dragging = true;
|
|
||||||
startX = e.clientX;
|
|
||||||
startScroll = container.scrollLeft;
|
|
||||||
container.classList.add('dragging');
|
|
||||||
pauseAutoScroll();
|
|
||||||
});
|
|
||||||
window.addEventListener('pointermove', (e) => {
|
|
||||||
if (!dragging) return;
|
|
||||||
container.scrollLeft = startScroll + (startX - e.clientX);
|
|
||||||
});
|
|
||||||
window.addEventListener('pointerup', () => {
|
|
||||||
dragging = false;
|
|
||||||
container.classList.remove('dragging');
|
|
||||||
});
|
|
||||||
|
|
||||||
inner.addEventListener('click', (e) => {
|
|
||||||
const cardEl = e.target.closest('.card');
|
|
||||||
if (!cardEl) return;
|
|
||||||
const id = Number(cardEl.dataset.id);
|
|
||||||
handleLike(id);
|
|
||||||
pauseAutoScroll();
|
|
||||||
});
|
|
||||||
|
|
||||||
container.addEventListener('touchstart', () => pauseAutoScroll(), { passive: true });
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
layout.containerHeight = window.innerHeight - 80;
|
|
||||||
layout.recalcUnits();
|
|
||||||
const plainData = allCards.map(c => ({ id: c.id, likes: c.likes, palette: c.palette }));
|
|
||||||
allCards = layout.compute(plainData);
|
|
||||||
renderAllCards();
|
|
||||||
});
|
|
||||||
|
|
||||||
initLoad();
|
|
||||||
autoScroll();
|
|
||||||
|
|
||||||
console.log('✨ 完美填满:随机垂直间隙分布,零底部留白');
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
Reference in New Issue
Block a user