两个元素之间的平滑过渡 "jumps"

Svelte transition between two elements "jumps"

我喜欢 Svelte,但我坚持一些基本的东西(尽管只是装饰性的)。下面的代码应该在两个元素之间平滑过渡,但它 "jumps"-- 显然在传入元素到达之前为它腾出空间。

问题类似于this one that Rich Harris noted a few years back, but I don't see that a solution was implemented. All examples on the Svelte tutorial site仅转换单个元素。

这是基本的markup/code:

{#if div1}
    <div 
      in:fly={{ x: 100, duration: 400, delay: 400 }}
      out:fly={{ x: 100, duration: 400 }}>Div 1</div>
{:else}
    <div 
      in:fly={{ x: 100, duration: 400, delay: 400 }}
      out:fly={{ x: 100, duration: 400 }}>Div 2</div>
{/if}

<button on:click={()=>{ div1 = !div1}}>Switch</button>

Vue 中的等效工作是:

<transition name="fly" mode="out-in">
    <div v-if="div1">Div 1</div>
    <div v-else>Div 2</div>
</transition>

这里是Code Sandbox example. You can see the button jump down to make room for the new element. I added an "in" transition delay equal to the duration of 400 (I know that's default, but I wanted to set it explicitly for clarity's sake). The delay should allow the first element to transition out before transitioning the next one in, as noted in the first link (what Harris called "hacky use of delay") and suggested here.

我还尝试明确地将被淘汰的元素设置为 position: absolute,这样它就不会占用 space。 Here is a (still not working properly) example。看起来有点不雅,即使它正在工作。出于某种原因,转换覆盖设置 class 设置 position:absolute.

非常感谢任何帮助!

更新:我得到了想要的效果 with this code。我在这里所做的是复制并修改 Svelte 的飞行过渡以采用一个额外的参数--'position,',它可以设置为 'absolute' 或 'relative' 或任何你想要的。对 CSS 进行一些调整以确保没有奇怪的副作用(将容器设置为 position: relative,并将每个元素的宽度设置为 100% 以确保它们不会改变大小)。这行得通,但我仍然觉得应该有一种劳动强度较低的方法,无需修改 Svelte 的过渡。

我也是从 Vue 过来的,out-in 是我想念 Svelte 的一件事。 Rich Harris 甚至在 Svelte 3 之前就承认了这一点,但据我所知从未真正实施过修复。

单一条件、仅延迟、出入转换方法的问题在于,尽管输入转换有延迟,但一旦条件切换,Svelte 就会创建传入元素。您可以放慢过渡速度并检查开发工具以查看这一点,两个元素都将存在传入的过渡延迟不会阻止元素具有大小,只是可见性。

一种绕过它的方法是用绝对位置做你已经做过的事情,有点密集并成为样板。另一种方法是为容纳正在转换的元素的容器设置绝对高度,将其他所有内容从容器中拉出(您的示例中的按钮)并隐藏溢出 as seen here,非常 css 依赖并且确实某些布局并不总是很好。

我使用的最后一种方法有点迂回,但由于 Svelte 有一个 outroend 事件,当动画完成时,你可以为蓝色或任何你的第二个条件添加一个变量,并放入一个else if 阻止第二个条件(此处为蓝色)并连接触发器,以便它检查活动变量并将其关闭,然后打开 outroend 事件中的另一个变量 as seen here 您还可以删除任何延迟,因为持续时间成为延迟。

从转换期间检查 DOM 看来这是两个元素不同时存在的唯一方法,因为它们取决于不同的条件,我相信还有更优雅的方法实现这一点,但这对我有用。

编辑:

还有另一个选项仅适用于 browsers that support CSS grid spec,幸运的是,这在这一点上几乎是通用的。它与绝对定位方法非常相似,还有一个额外的好处,即您完全不必担心元素的高度

这背后的想法是,使用 CSS 网格,我们可以强制 2 个元素占据相同的 space grid-areagrid-columngrid-row通过在 1 col x 1 行的隐式网格上为两个元素(或超过 2 个)提供相同的开始和结束列和行(网格足够智能,不会创建我们不会使用的额外列和行)。由于 Svelte 在其过渡中使用了变换,因此我们可以让元素来来去去而无需任何布局偏移,这很好。我们不再需要担心绝对位置影响元素或延迟,我们可以微调过渡时间以达到完美。

这是一个 REPL to show a simple setup, and another REPL 来展示如何使用它来获得一些非常甜美的分层效果,哇!

如果您碰巧要在两个以上的状态之间进行交换,将行为抽象为自定义存储非常有帮助。商店可能看起来像这样:

statefulSwap(initialState) {
    const state = writable(initialState);
    let nextState = initialState;
    
    function transitionTo(newState) {
        if(nextState === newState) return;
        nextState = newState
        state.set(null)
    }
    
    function onOutro() {
        state.set(nextState)
    }
    return {
        state,
        transitionTo,
        onOutro
    }
}

您可以使用条件块在元素之间交换:

{#if $state == "first"}
    <h1 transition:fade on:outroend={onOutro}>
        First
</h1>
{:else if $state == "second"}
    <h1 transition:fade on:outroend={onOutro}>
        Second
</h1>
{/if}

此技术通过初始将当前状态设置为 null 并在第一个元素转换出后在 onOutro 中应用新状态来模拟 Vue 的 out-in 行为。

这是一个 REPL 示例。这里的优点是您可以根据需要使用不同的动画动作和时间来拥有任意数量的状态,而无需跟踪交换逻辑。但是,如果您的条件标记中有默认的 else 块,这将不起作用。