You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 06:10:32