修复前端瀑布流动画
All checks were successful
Dart CI / build (push) Successful in 49s

This commit is contained in:
2026-04-04 21:53:11 +08:00
parent 02a4ea3f4e
commit 7ff63d023e
2 changed files with 29 additions and 11 deletions

View File

@@ -7,10 +7,11 @@
* @property {(photoId: number, previewSrc: string) => void} [onUpgradeQuality] * @property {(photoId: number, previewSrc: string) => void} [onUpgradeQuality]
* @property {boolean} [borderLess] * @property {boolean} [borderLess]
* @property {boolean} [waterfall] * @property {boolean} [waterfall]
* @property {number} [index]
*/ */
/** @type {PhotoCardProps} */ /** @type {PhotoCardProps} */
let { photo, onUpgradeQuality, borderLess = false, waterfall = false } = $props(); let { photo, onUpgradeQuality, borderLess = false, waterfall = false, index = 0 } = $props();
// 使用 $derived 确保响应式更新 // 使用 $derived 确保响应式更新
let previewSrc = $derived(`/api/v1/photo/${photo.id}/preview`); let previewSrc = $derived(`/api/v1/photo/${photo.id}/preview`);
@@ -18,10 +19,13 @@
// 根据原始宽高比计算瀑布流显示比例,防止图片加载前容器塌陷 // 根据原始宽高比计算瀑布流显示比例,防止图片加载前容器塌陷
let aspectRatioStyle = $derived( let aspectRatioStyle = $derived(
waterfall && photo.width && photo.height waterfall && photo.width && photo.height
? `aspect-ratio: ${photo.width} / ${photo.height};` ? `${photo.width} / ${photo.height}`
: '' : ''
); );
// 砖块模式:交错延迟(按网格行顺序);瀑布模式:同时入场(避免列填充顺序与延迟不匹配)
let animationDelay = $derived(waterfall ? '0ms' : `${Math.min(index, 20) * 35}ms`);
function handleMouseEnter() { function handleMouseEnter() {
if (onUpgradeQuality) { if (onUpgradeQuality) {
onUpgradeQuality(photo.id, previewSrc); onUpgradeQuality(photo.id, previewSrc);
@@ -35,8 +39,8 @@
} }
</script> </script>
<a href={resolve(`/photo/${photo.id}`)} class="photo-card" class:photo-card-waterfall={waterfall} class:photo-card-compact-waterfall={borderLess && waterfall}> <a href={resolve(`/photo/${photo.id}`)} class="photo-card" class:photo-card-waterfall={waterfall} class:photo-card-compact-waterfall={borderLess && waterfall} style={`--wf-aspect: ${aspectRatioStyle}; --wf-delay: ${animationDelay};`}>
<div class="photo-wrapper" class:photo-borderless={borderLess} class:photo-waterfall={waterfall} style={aspectRatioStyle}> <div class="photo-wrapper" class:photo-borderless={borderLess} class:photo-waterfall={waterfall}>
{#if photo.mimeType?.startsWith('video/')} {#if photo.mimeType?.startsWith('video/')}
<div class="video-indicator">🎬</div> <div class="video-indicator">🎬</div>
<div class="photo-placeholder"> <div class="photo-placeholder">
@@ -65,8 +69,21 @@
display: block; display: block;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
animation: card-enter 0.35s cubic-bezier(0.4, 0, 0.2, 1) both;
animation-delay: var(--wf-delay, 0ms);
} }
@keyframes card-enter {
from {
opacity: 0;
transform: translateY(12px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* 瀑布流模式:防止卡片被列截断,并添加间距 */ /* 瀑布流模式:防止卡片被列截断,并添加间距 */
.photo-card-waterfall { .photo-card-waterfall {
break-inside: avoid; break-inside: avoid;
@@ -97,7 +114,7 @@
/* 瀑布流模式:保持原始宽高比 */ /* 瀑布流模式:保持原始宽高比 */
.photo-waterfall { .photo-waterfall {
aspect-ratio: auto; aspect-ratio: var(--wf-aspect, auto);
height: auto; height: auto;
} }
@@ -136,7 +153,6 @@
/* 瀑布流模式下 placeholder 默认 4:3 */ /* 瀑布流模式下 placeholder 默认 4:3 */
.photo-waterfall .photo-placeholder { .photo-waterfall .photo-placeholder {
aspect-ratio: 4/3; aspect-ratio: 4/3;
height: 0;
} }
.video-indicator { .video-indicator {

View File

@@ -40,11 +40,13 @@
<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}>
<div class="photo-grid" class:photo-grid-borderless={borderLess} class:photo-grid-waterfall={waterfall}> {#key waterfall + '_' + borderLess}
{#each getVisiblePhotos() as photo (photo.id)} <div class="photo-grid" class:photo-grid-borderless={borderLess} class:photo-grid-waterfall={waterfall}>
<PhotoCard {photo} {onUpgradeQuality} {borderLess} {waterfall} /> {#each getVisiblePhotos() as photo, i (photo.id)}
{/each} <PhotoCard {photo} {onUpgradeQuality} {borderLess} {waterfall} index={i} />
</div> {/each}
</div>
{/key}
{#if isLoading} {#if isLoading}
<div class="loading-trigger"> <div class="loading-trigger">