使用Svelte crossfade过渡时,如何避免元素中途轻微淡出?
问题:Svelte crossfade过渡图片时中途出现轻微淡出,如何解决?
在使用Svelte的crossfade过渡图片时,发现元素在过渡中途会出现轻微淡出的情况,调整缓动函数也无法解决。
补充背景
- 试过通过切换类解决,但过渡效果不符合需求;实际场景是图片库,元素不仅会缩放、移动,还会切换父元素。
- 查看官方crossfade相关视频,放慢速度未发现透明度变化,怀疑crossfade函数是否有更新,但测试旧版本编译器也没找到无此问题的版本。
- 自定义过渡函数不难,但想知道能不能复制复用Svelte内部的crossfade函数,调整其透明度设置来解决问题?
原测试代码
<script> import { crossfade } from 'svelte/transition'; import {quintOut} from 'svelte/easing'; const [send, receive] = crossfade({ duration: 3000, easing: quintOut }); async function fetchPhoto(noCacheMarker) { const response = await fetch(`https://source.unsplash.com/random?${noCacheMarker}`) return await response.url } let smallImg = true </script> <button on:click={() => smallImg = !smallImg}> toggle </button> <div> {#await fetchPhoto() then url} {#if smallImg} <img src="{url}" alt="" id="smallImg" out:send="{{key: url}}" in:receive="{{key: url}}" /> {:else} <img src="{url}" alt="" id="bigImg" out:send="{{key: url}}" in:receive="{{key: url}}" /> {/if} {/await} </div> <style> img { position: absolute; } #smallImg { width: 300px; height: 200px; object-fit: cover; left: 10%; bottom: 10%; } #bigImg { width: 600px; height: 400px; object-fit: cover; top: 10%; right: 10%; } </style>
解决方法
1. 自定义修改crossfade的透明度逻辑
Svelte原生crossfade的默认逻辑是让发送元素淡出、接收元素淡入,这就是过渡中途出现轻微淡出的原因。你可以复制官方crossfade的核心代码,修改透明度相关计算,强制保持元素透明度为1:
自定义crossfade函数
import { cubicOut } from 'svelte/easing'; function crossfadeCustom(options = {}) { const { duration = 300, easing = cubicOut, fallback } = options; const to_receive = new Map(); function send(node, { key }) { const data = { from: node.getBoundingClientRect(), resolve: null, reject: null, promise: new Promise((resolve, reject) => { data.resolve = resolve; data.reject = reject; }) }; to_receive.set(key, data); return { duration, easing, css: (t, u) => { const { x, y, width, height } = data.from; const dx = x - data.to.x; const dy = y - data.to.y; const dw = width / data.to.width; const dh = height / data.to.height; return ` position: fixed; z-index: 1000; transform: translate(${dx * u}px, ${dy * u}px) scale(${u * (dw - 1) + 1}, ${u * (dh - 1) + 1}); opacity: 1; /* 强制保持透明度为1,取消淡出效果 */ `; }, tick: (t, u) => { if (t === 1) data.resolve(); } }; } function receive(node, { key }) { const data = to_receive.get(key); if (!data) return fallback?.(node, options) || {}; to_receive.delete(key); data.to = node.getBoundingClientRect(); return { duration, easing, css: (t) => { const { x, y, width, height } = data.from; const dx = x - data.to.x; const dy = y - data.to.y; const dw = width / data.to.width; const dh = height / data.to.height; return ` position: fixed; z-index: 1000; transform: translate(${dx * (1 - t)}px, ${dy * (1 - t)}px) scale(${(1 - t) * (dw - 1) + 1}, ${(1 - t) * (dh - 1) + 1}); opacity: 1; /* 强制保持透明度为1,取消淡入效果 */ `; } }; } return [send, receive]; } export default crossfadeCustom;
在组件中使用自定义函数
替换原来的crossfade导入即可:
<script> import { quintOut} from 'svelte/easing'; import crossfadeCustom from './crossfadeCustom.js'; const [send, receive] = crossfadeCustom({ duration: 3000, easing: quintOut }); // 其余代码和原测试代码一致 </script>
2. 替代方案:单个元素切换类+animate
如果场景允许,用单个img元素配合类切换和flip动画,能避免crossfade的双元素交叉透明度问题,实现更简洁的过渡:
<script> import { quintOut } from 'svelte/easing'; import { flip } from 'svelte/animate'; async function fetchPhoto() { const response = await fetch(`https://source.unsplash.com/random?${Date.now()}`) return response.url } let smallImg = true let url </script> <button on:click={() => smallImg = !smallImg}> toggle </button> <div> {#await fetchPhoto() then imgUrl} {url = imgUrl} <img src={url} alt="" class:small={smallImg} animate:flip={{ duration: 3000, easing: quintOut }} /> {/await} </div> <style> img { position: absolute; object-fit: cover; } .small { width: 300px; height: 200px; left: 10%; bottom: 10%; } :global(img:not(.small)) { width: 600px; height: 400px; top: 10%; right: 10%; } </style>
内容的提问来源于stack exchange,提问作者Corrl
相关产品推荐
相关产品推荐

