两个元素之间的细长过渡“跳跃”
Posted
技术标签:
【中文标题】两个元素之间的细长过渡“跳跃”【英文标题】:Svelte transition between two elements "jumps" 【发布时间】:2020-05-09 22:32:01 【问题描述】:我喜欢 Svelte,但我坚持做一些基本的事情(尽管只是表面上的)。下面的代码应该在两个元素之间平滑过渡,但它会“跳跃”——显然是在传入元素到达之前为它腾出空间。
这个问题类似于 Rich Harris 几年前指出的this one,但我没有看到解决方案已实施。 Svelte tutorial site 转换的所有示例都只有一个元素。
这是基本的标记/代码:
#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。您可以看到按钮向下跳来为新元素腾出空间。我添加了一个等于 400 持续时间的“in”转换延迟(我知道这是默认设置,但为了清楚起见,我想明确设置它)。延迟应该允许第一个元素在转换下一个元素之前转换出来,如第一个链接中所述(Harris 称之为“延迟的黑客使用”)并建议here。
我还尝试将被淘汰的元素显式设置为 position: absolute,这样它就不会占用空间。 Here is a (still not working properly) example。似乎有点不雅,即使它正在工作。出于某种原因,转换覆盖了设置位置的类:absolute。
非常感谢任何帮助!
更新:我得到了想要的效果with this code。我在这里所做的是复制和修改 Svelte 的飞行过渡以获取一个额外的参数——“位置”,它可以设置为“绝对”或“相对”或任何你想要的。对 CSS 进行了一些调整以确保没有奇怪的副作用(将容器设置为 position: relative,并将每个元素的宽度设置为 100% 以确保它们不会改变大小)。这行得通,但我仍然觉得应该有一种不那么劳动密集型的方法,无需修改 Svelte 的过渡。
【问题讨论】:
所有代码沙箱链接都不起作用。 我写了一个blog post 来解决这个CSS网格问题。希望有帮助! 【参考方案1】:我也是从 Vue 过来的,出入是我对 Svelte 怀念的一件事。 Rich Harris 甚至在 Svelte 3 之前就承认了这一点,但据我所知从未真正实施过修复。
单一条件、仅延迟、out-in 转换方法的问题在于,Svelte 会在条件切换后创建传入元素,尽管在转换中存在延迟。您可以减慢转换速度并检查开发工具以查看这一点,两个元素都将存在传入的转换延迟不会阻止元素具有大小,只是可见性。
解决它的一种方法是使用绝对位置执行您所做的事情,有点密集并成为样板。另一种方法是为包含正在转换的元素的容器设置绝对高度,将其他所有内容拉出容器(示例中的按钮)并隐藏溢出as seen here,非常依赖于 css,并且在某些情况下并不总是很好布局。
我使用的最后一种方法更加圆润,但是由于 Svelte 有一个在动画完成时调度的 outroend 事件,因此您可以为蓝色或您的第二个条件添加一个变量,然后放入 else if阻止第二个条件(此处为蓝色)并连接触发器,以便它检查活动变量并将其关闭,然后在 outroend 事件as seen here 中打开另一个变量,您还可以删除任何延迟,因为持续时间变为延迟.
在转换期间检查 DOM 似乎这是两个元素不同时存在的唯一方法,因为它们依赖于不同的条件,我相信还有更优雅的方法可以实现这一点,但这是可行的对我来说。
编辑:
还有一个只适用于browsers that support CSS grid spec 的选项,幸运的是,这在这一点上几乎是通用的。它与绝对定位方法非常相似,还有一个额外的好处是您根本不必担心元素的高度
这背后的想法是,使用 CSS Grid,我们可以强制 2 个元素与 grid-area
或 grid-column
和 grid-row
占用相同的空间,方法是给两个元素(或超过 2 个)相同的开始和结束列和 1 列乘 1 行的隐式网格上的行(网格足够智能,不会创建我们不会使用的额外列和行)。由于 Svelte 在它的过渡中使用了变换,我们可以让元素来来去去而无需任何布局转换,很好。我们不再需要担心影响元素的绝对位置或延迟,我们可以将过渡时间微调到完美。
这是一个REPL 来展示一个简单的设置,另一个REPL 来展示如何使用它来获得一些非常漂亮的分层效果,哇!
【讨论】:
非常感谢!很高兴知道我的方法没有比它需要的更密集,很高兴看到一些替代方法。在容器上设置绝对高度很困难,因为我永远不知道我的内容到底有多高(没有 JS)。 Vue 有一个“移动”过渡,可以平滑地将其他项目移出不同大小的内容——在 Svelte 中看到这些东西会很高兴! @JacobRunge 要找到一个元素的确切高度,您总是可以使用 bind:this 将 html 元素放入一个变量中,然后从 onMount 中的元素获取 offsetHeight,如下所示:svelte.dev/repl/aaa5744fb6c64c5dadf707941c9e45c1?version=3.18.0 但它确实会为 Vue 开箱即用的东西带来一些额外的工作。与移动过渡相同,Svelte 可以对动画、交叉淡入淡出和翻转执行类似的操作,但给出的示例很复杂。很高兴看到像 Vue 那样对动画进行更多抽象。 注意事项:如果内容有不同的大小,它可能会改变你的网格大小并导致不同的“跳跃”。要解决这个问题,您需要防止网格缩小/增长。【参考方案2】:如果您碰巧有两个以上的状态要在它们之间进行交换,那么将行为抽象到自定义存储中真的很有帮助。商店可能看起来像这样:
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
这种技术模拟 Vue 的 out-in
行为,首先将当前状态设置为 null
,然后在第一个元素过渡出来后在 onOutro
中应用新状态。
这是一个REPL 示例。这里的优点是您可以使用不同的动画动作和时间来拥有任意数量的状态,而无需跟踪交换逻辑。但是,如果您的条件标记中有默认的 else
块,这将不起作用。
【讨论】:
感谢您的贡献!在答案中包含您链接到的一些 REPL 示例代码可能会很好。基于链接的答案的问题在于,如果链接不再有效,答案就会失去意义。 感谢您的提示!希望更新提供一个很好的解释。 或实用版:let outroDone = false;
... #if someConditionToShowFirstNode <div transition:fly|local="..." on:outroend=()=>outroDone=true></div> :else if outroDone <div transition:fly|local=" ... no delay needed ... "></div> /if
以上是关于两个元素之间的细长过渡“跳跃”的主要内容,如果未能解决你的问题,请参考以下文章
Vue/CSS,如何在两个交替元素之间进行平滑的高度过渡(包括小提琴)