This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { browser } from '$app/environment';
|
||||||
import PhotoCard from './PhotoCard.svelte';
|
import PhotoCard from './PhotoCard.svelte';
|
||||||
import { Empty, Loading } from '$lib/components/ui';
|
import { Empty, Loading } from '$lib/components/ui';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
@@ -34,18 +35,83 @@
|
|||||||
function getVisiblePhotos() {
|
function getVisiblePhotos() {
|
||||||
return photos.slice(0, displayedCount);
|
return photos.slice(0, displayedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 响应式列数(默认 3)
|
||||||
|
let columnCount = $state(3);
|
||||||
|
|
||||||
|
function getColumnCount() {
|
||||||
|
if (!browser) return 3;
|
||||||
|
const w = window.innerWidth;
|
||||||
|
if (w < 768) return 2;
|
||||||
|
if (w >= 1200) return 4;
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 + 响应式更新
|
||||||
|
$effect(() => {
|
||||||
|
if (!browser) return;
|
||||||
|
columnCount = getColumnCount();
|
||||||
|
|
||||||
|
let timer;
|
||||||
|
const onResize = () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
columnCount = getColumnCount();
|
||||||
|
}, 150);
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
window.removeEventListener('resize', onResize);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 瀑布流分列:贪心平衡算法
|
||||||
|
// 每次把下一张放到当前最短的列,自动平衡各列总高度
|
||||||
|
function splitToColumns(items, colCount) {
|
||||||
|
const columns = Array.from({ length: colCount }, () => []);
|
||||||
|
const heights = new Array(colCount).fill(0);
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
// 相对高度 = 高/宽,列宽相同所以可直接比
|
||||||
|
let h = item.width && item.height ? item.height / item.width : 0.75;
|
||||||
|
|
||||||
|
// 找到最短列
|
||||||
|
let minIdx = 0;
|
||||||
|
for (let i = 1; i < colCount; i++) {
|
||||||
|
if (heights[i] < heights[minIdx]) minIdx = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns[minIdx].push(item);
|
||||||
|
heights[minIdx] += h;
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if photos.length === 0 && !hasMore}
|
{#if photos.length === 0 && !hasMore}
|
||||||
<Empty message={m.no_photos()} icon="📷" />
|
<Empty message={m.no_photos()} icon="📷" />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="photo-scroll-container" bind:this={scrollContainer}>
|
<div class="photo-scroll-container" bind:this={scrollContainer}>
|
||||||
{#key waterfall + '_' + borderLess}
|
{#key waterfall + '_' + borderLess + '_' + columnCount}
|
||||||
<div class="photo-grid" class:photo-grid-borderless={borderLess} class:photo-grid-waterfall={waterfall}>
|
{#if waterfall}
|
||||||
|
<div class="photo-grid-waterfall" class:photo-grid-borderless={borderLess}>
|
||||||
|
{#each splitToColumns(getVisiblePhotos(), columnCount) as col}
|
||||||
|
<div class="waterfall-column">
|
||||||
|
{#each col as photo (photo.id)}
|
||||||
|
<PhotoCard {photo} {onUpgradeQuality} {borderLess} {waterfall} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="photo-grid" class:photo-grid-borderless={borderLess}>
|
||||||
{#each getVisiblePhotos() as photo, i (photo.id)}
|
{#each getVisiblePhotos() as photo, i (photo.id)}
|
||||||
<PhotoCard {photo} {onUpgradeQuality} {borderLess} {waterfall} index={i} />
|
<PhotoCard {photo} {onUpgradeQuality} {borderLess} {waterfall} index={i} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
{#if isLoading}
|
{#if isLoading}
|
||||||
@@ -103,22 +169,35 @@
|
|||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 瀑布流布局 */
|
/* 瀑布流布局:Flex 列分栏 */
|
||||||
.photo-grid-waterfall {
|
.photo-grid-waterfall {
|
||||||
display: block;
|
display: flex;
|
||||||
column-count: 3;
|
gap: var(--space-md);
|
||||||
column-gap: var(--space-md);
|
padding-bottom: var(--space-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 瀑布流 + 紧凑:无间距 */
|
|
||||||
.photo-grid-waterfall.photo-grid-borderless {
|
.photo-grid-waterfall.photo-grid-borderless {
|
||||||
column-gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.waterfall-column {
|
||||||
.photo-grid-waterfall {
|
flex: 1;
|
||||||
column-count: 2;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-grid-waterfall .waterfall-column > :global(.photo-card) {
|
||||||
|
break-inside: avoid;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-grid-waterfall .waterfall-column > :global(.photo-card):last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-grid-waterfall.photo-grid-borderless .waterfall-column > :global(.photo-card) {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
@@ -127,12 +206,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
|
||||||
.photo-grid-waterfall {
|
|
||||||
column-count: 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-more-trigger,
|
.load-more-trigger,
|
||||||
.loading-trigger {
|
.loading-trigger {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user