删除无用的html和图片

This commit is contained in:
zheng020 2026-05-06 11:31:37 +08:00
parent 4df395b2ad
commit 697a26300e
4 changed files with 0 additions and 420 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 KiB

BIN
image.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

View File

@ -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>