循环中fade过渡delay属性失效:是否为标准行为及解决方法咨询
我给组件应用了带delay参数的fade过渡效果,单独渲染时表现正常,但放入#each循环后delay属性被忽略,只有duration生效。
组件代码:
<div in:fade={{ delay: delayPlus(), duration: 1500 }}> <p>{word}</p> </div>
延迟值生成函数:
function delayPlus() { let delay: number = 0; count.subscribe((value) => (delay = value)); count.update(() => delay + (Math.floor(Math.random() * 451) + 50)); return delay; }
单独逐个渲染组件时,各组件会按delay设置依次显示;但用#each循环渲染words数组时,所有组件会立即出现,只遵守duration的设置。请问这是标准行为吗?怎么让循环渲染实现和逐个渲染一样的效果?
这是标准行为吗?
是。当#each循环一次性渲染所有组件时,Svelte会批量处理过渡效果,此时你的delayPlus()函数会在同一时间点被所有组件调用——要么因为响应式订阅的异步特性,导致所有组件拿到初始的0作为延迟;要么因为同步更新count时,所有函数调用共享同一轮更新的最终值,无法产生依次递增的延迟。而逐个渲染时,delayPlus()是在不同时间点被调用的,能正确累积延迟值,所以表现正常。
解决方法
1. 基于循环索引生成延迟值
放弃依赖响应式变量count,直接用循环项的索引计算延迟,给每个项分配递增的基础延迟,再叠加随机值:
{#each words as word, index} <div in:fade={{ delay: index * 200 + Math.floor(Math.random() * 451) + 50, duration: 1500 }}> <p>{word}</p> </div> {/each}
这种方式简单直接,索引递增保证了组件依次出现,随机值保留了你原本的需求。
2. 手动控制渲染时机(模拟逐个渲染)
如果需要完全复刻逐个渲染的逻辑,不依赖索引,可以通过动态往数组中添加项的方式,用setTimeout控制每个组件的渲染时机:
<script> import { onMount } from 'svelte'; let displayWords = []; const words = ["foo", "bar", "baz"]; async function renderWords() { for (const word of words) { displayWords = [...displayWords, word]; // 等待随机延迟后再添加下一个 await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 451) + 50)); } } onMount(renderWords); </script> {#each displayWords as word} <div in:fade={{ duration: 1500 }}> <p>{word}</p> </div> {/each}
这种方式下,每个组件的过渡会在前一个组件的延迟结束后触发,和逐个渲染的效果完全一致。
3. 修复原函数的响应式逻辑
原delayPlus()的问题在于subscribe是异步触发的,函数返回时delay还没被更新。可以改成直接获取count的当前值(如果count是Svelte的writable store):
function delayPlus() { const currentDelay = count.current; // 直接获取store当前值 const newDelay = currentDelay + (Math.floor(Math.random() * 451) + 50); count.set(newDelay); return currentDelay; }
但注意:#each循环初始化时会同步调用所有组件的delayPlus(),此时count的更新是同步的,所有延迟值会被正确累积。不过这种方式依赖store的内部状态,不如前两种方法直观可控。
内容的提问来源于stack exchange,提问作者Kotkoroid

