svelte
svelte copied to clipboard
Transition ends too soon / "jumps" near end of transition
Describe the bug
Using a transition on an element (tested on <div />
, <span />
, <p />
and <h1 />
elements) shows a "jump" at the end of the transition, with or without an easing function.
Tested with existing and new, fresh projects. Problem occurs in both.
https://github.com/sveltejs/svelte/assets/46247153/71220be3-0643-4d4e-8556-1a780f735c9e
Edit 1:
Some narrowing down of the problem: Svelte transitions are too slow(?)
On the left I use a regular, @keyframes
css transition, on the right, Svelte's transition:fly
.
https://github.com/sveltejs/svelte/assets/46247153/3412ffde-ee64-4282-8fbb-55d6474ecc72
Edit 2:
I have noticed that the issue happens after the first load of the page: meaning that after running npm run dev
and opening the website it works normally but after HMR or fully reloading the issue happens.
Reproduction
Code snippet:
Snippet 1 (Original):
<script>
import { fly } from 'svelte/transition';
import { onMount } from 'svelte';
let mounted = false;
onMount(() => {
mounted = true;
});
</script>
{#if mounted}
<!-- Has also been tried with fly|local -->
<div class="b" transition:fly={{ y: -150, duration: 550 }} />
{/if}
<style>
.b {
width: 100px;
height: 100px;
background-color: red;
}
</style>
Snippet 2 (Edit 1):
<script lang="ts">
import { fly } from 'svelte/transition';
import { linear } from 'svelte/easing';
import { onMount } from 'svelte';
let mounted = false;
onMount(() => {
mounted = true;
});
</script>
<div class="a">
{#if mounted}
<div class="b">
<p>@keyframes</p>
</div>
<div class="c" transition:fly={{ y: -150, duration: 1550, delay: 3000 }}>
<p>svelte transition</p>
</div>
{/if}
</div>
<style>
.a {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
gap: 5rem;
}
.a .b,
.c {
height: 100px;
width: 100px;
background-color: red;
}
.a .b {
animation: 1150ms linear 3000ms 1 normal both running fly;
}
@keyframes fly {
from {
transform: translate(0, -150px);
opacity: 0;
}
to {
transform: translate(0, 0);
opacity: 1;
}
}
</style>
Logs
No response
System Info
System:
OS: Windows 10 10.0.23481
CPU: (12) x64 AMD Ryzen 5 5600X 6-Core Processor
Memory: 7.58 GB / 15.91 GB
Binaries:
Node: 18.4.0 - C:\Program Files\nodejs\node.EXE
Yarn: 1.22.19 - ~\AppData\Roaming\npm\yarn.CMD
npm: 9.7.1 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.23481.1000.0), Chromium (114.0.1823.43)
Internet Explorer: 11.0.23481.1000
Google Chrome: 114.0.5735.134
npmPackages:
svelte: ^3.54.0 => 3.59.2
Severity
annoyance
Updated post with more information on what might be happening, probably an issue with how Svelte handles transitions. if anybody could point me to where Svelte handles transitions in the source code so I could do some more digging that'd be great :)
Updated post with more information on what might be happening, probably an issue with how Svelte handles transitions. if anybody could point me to where Svelte handles transitions in the source code so I could do some more digging that'd be great :)
Underlying directive code:
https://github.com/sveltejs/svelte/blob/master/packages/svelte/src/runtime/internal/transitions.js
animation handlers are also in runtime/internal/
and here for the transition definitions https://github.com/sveltejs/svelte/tree/master/packages/svelte/src/runtime/transition
Jump may be related to https://github.com/sveltejs/svelte/issues/8351, race conditions seem to be relevant
I copied snippet 2 into the repl here, and made 1 small change to add the linear easing to the fly transition with easing:linear
:
https://svelte.dev/repl/59aa1a42f30c4bb39e38da77f0ab96d2?version=4.0.0
The keyframe animation and svelte transitions match perfectly.
I don't see the jumping behaviour that I can see in your videos, so maybe there is something outside of the snippets that you've provided which is causing the problem?
I copied snippet 2 into the repl here, and made 1 small change to add the linear easing to the fly transition with
easing:linear
:https://svelte.dev/repl/59aa1a42f30c4bb39e38da77f0ab96d2?version=4.0.0
The keyframe animation and svelte transitions match perfectly.
I don't see the jumping behaviour that I can see in your videos, so maybe there is something outside of the snippets that you've provided which is causing the problem?
I've provided everything I could provide, I haven't hid any of the code. Issue still persists after setting easing:linear
. Maybe try running it locally instead of the REPL?
I would also like to add that the issue happens after the first load of the page: meaning that after running npm run dev
and opening the website it works normally but after HMR or fully reloading the issue happens. I'll add this detail to the original post in a moment.
After further investigation, I can tell what it is not.
It's not a problem with the easing functions, or the fact that Svelte generates many key-frames for the animation.
Even just having 0%
and 100%
keyframes (modified the create_rule
function) makes the transition 'buggy'.
Removing the cleanup()
function lets the animation complete (though not in time) but also feels like fixing the issue with chewed gum and band-aids, so I'm certain it's not a viable option.
This is extremely weird, since having the same element, but switching the animation from Svelte's to pure CSS works completely fine.
I have a suspicion that requestAnimationFrame()
(called in runtime/internal/environment.js
) is somehow involved. I'll investigate further tomorrow as it is getting pretty late here.
I downloaded the repl code I linked to earlier, installed it locally, and now I can reproduce the issue every time (so, yes, this issue is not reproducible on the repl).
I have a fix! I don't entirely understand why, but if you alter the transition to start at an opacity other than 0, then everything works correctly:
transition:fly={{ y: -150, duration: 1550, delay: 3000, opacity:0.0001 }}
Adding a small opacity start value to the scale
and blur
transitions also allows them to not jump at the end. Only the fade
transition can't be fixed that way, since it is hard coded to start at opacity 0.
I've not investigated further, but maybe this is a web browser issue, where it is doing something clever when it sees fully transparent elements on mounting the page. It seems unlikely to be an issue with the transition code (since that looks very solid), and I don't think that svelte would be doing anything special with fully transparent elements. Svelte could change to use an opacity of 0.000001 as the default for these transitions, but a proper fix should be elsewhere I would have thought.
I downloaded the repl code I linked to earlier, installed it locally, and now I can reproduce the issue every time (so, yes, this issue is not reproducible on the repl).
I have a fix! I don't entirely understand why, but if you alter the transition to start at an opacity other than 0, then everything works correctly:
transition:fly={{ y: -150, duration: 1550, delay: 3000, opacity:0.0001 }}
Adding a small opacity start value to the
scale
andblur
transitions also allows them to not jump at the end. Only thefade
transition can't be fixed that way, since it is hard coded to start at opacity 0.I've not investigated further, but maybe this is a web browser issue, where it is doing something clever when it sees fully transparent elements on mounting the page. It seems unlikely to be an issue with the transition code (since that looks very solid), and I don't think that svelte would be doing anything special with fully transparent elements. Svelte could change to use an opacity of 0.000001 as the default for these transitions, but a proper fix should be elsewhere I would have thought.
I think we should test this on a browser like Firefox, since I've tested this on Chrome and Edge which are basically the same since Edge uses chromium. If the issue persists on Firefox it may be a bug with chromium.
Checking again, I can see that for a fly transition with duration:1000
:
- if
opacity:0
, then largest contentful paint is 1.04 s - if
opacity:0.0001
, then largest contentful paint is 0.04s
So, could be related to some kind of browser optimisation?
Strangely, if the text inside the transitioning div is removed, then the fix of setting a non-zero opacity stops working (it jumps at the end again....)
I think we should test this on a browser like Firefox, since I've tested this on Chrome and Edge which are basically the same since Edge uses chromium. If the issue persists on Firefox it may be a bug with chromium.
Good point. I just tried running it in Firefox, and everything works fine there, so it is almost certainly a Chrome bug.
I can confirm that setting the opacity
to 0.00001
fixes the issue, but it is only a band-aid fix, so I reported the problem to Google. I will keep you guys updated but for now I will close the issue and update the original post with the fix :) Thank you for your help.