svelte icon indicating copy to clipboard operation
svelte copied to clipboard

Transition ends too soon / "jumps" near end of transition

Open EliasVal opened this issue 1 year ago • 13 comments

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

EliasVal avatar Jun 22 '23 12:06 EliasVal

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 :)

EliasVal avatar Jun 22 '23 16:06 EliasVal

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

ezra-en avatar Jun 22 '23 17:06 ezra-en

Jump may be related to https://github.com/sveltejs/svelte/issues/8351, race conditions seem to be relevant

ezra-en avatar Jun 22 '23 17:06 ezra-en

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?

robertadamsonsmith avatar Jun 22 '23 19:06 robertadamsonsmith

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?

EliasVal avatar Jun 22 '23 20:06 EliasVal

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.

EliasVal avatar Jun 22 '23 20:06 EliasVal

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.

EliasVal avatar Jun 22 '23 22:06 EliasVal

This is extremely weird, since having the same element, but switching the animation from Svelte's to pure CSS works completely fine.

EliasVal avatar Jun 22 '23 22:06 EliasVal

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.

EliasVal avatar Jun 22 '23 22:06 EliasVal

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.

robertadamsonsmith avatar Jun 23 '23 14:06 robertadamsonsmith

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 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.

EliasVal avatar Jun 23 '23 15:06 EliasVal

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....)

robertadamsonsmith avatar Jun 23 '23 15:06 robertadamsonsmith

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.

robertadamsonsmith avatar Jun 23 '23 15:06 robertadamsonsmith

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.

EliasVal avatar Jun 23 '23 21:06 EliasVal