Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svelte transition impacts navigation #628

Closed
imCorfitz opened this issue Jul 21, 2019 · 31 comments
Closed

Svelte transition impacts navigation #628

imCorfitz opened this issue Jul 21, 2019 · 31 comments
Labels

Comments

@imCorfitz
Copy link

imCorfitz commented Jul 21, 2019

Describe the bug
If a component used in a route has a transition applied, it will delay navigation and potentially create UI errors.

To Reproduce
In a brand new installation, create a new component e.g. Linker.svelte and include it in the index route.

<!-- src/lib/Linker.svelte -->
<script>
  import { fade } from "svelte/transition";
  let showImage = false;
</script>

<div
  on:mouseenter={() => {
    showImage = true;
  }}
  on:mouseleave={() => {
    showImage = false;
  }}>

  <h1>Wow hi!</h1>
  <a href="/about">About</a>
  {#if showImage}
    <img
      transition:fade={{ duration: 2000 }}
      src="https://picsum.photos/20"
      alt="" />
  {/if}
</div>
<!-- src/routes/index.svelte -->
<script>
  import Linker from "../lib/Linker.svelte";
</script>

...

<Linker />
<h1>Great success!</h1>

Moving the cursor on top of the link, a small picture will slowly fade in. Once clicking on the link, the image will slowly fade out again, but the /about page content will already be appended to the <slot /> and visible in the bottom. If the user moves the cursor back on top of the link while it is fading out, it will just fade in again and prevent the component from being removed completely - causing UI errors.

ezgif-4-99f035a10dee

Expected behavior
When navigating, default behaviour should be to just switch route. No delays nor attempts to finalise transitions or to show both routes in the <slot /> at the same time.

Information about your Sapper Installation:

  • Issue appears in both Chrome and Safari from what I have tested, but I expect this to be the same across all browsers.

  • Using iMac 2011 with OSX Mojave

  • Running in localhost

  • Current latest release v0.27.4

  • Svelte v3

  • dynamic application

  • Using Rollup

Severity
Mainly it is just annoying, and I can't really find a workaround to prevent it unless I just remove the transition from the image having it appear and disappear instantly.

Additional context
Maybe a modifier to the transition could be an idea. We currently have modifiers like once or preventDefault on click events. Maybe add a transition:fade|ignoreRoute={{duration: 200}} or the quite opposite - having the developer purposely asking for route specific transition.

@waspeer
Copy link

waspeer commented Aug 29, 2019

I second this! Right now I have to avoid using transitions to keep navigation working.

@thgh
Copy link

thgh commented Sep 15, 2019

Solution to this in Vue: https://vuejs.org/v2/guide/transitions.html#Transition-Modes

@antony
Copy link
Member

antony commented Oct 28, 2019

Ran into this earlier and only just figured out what it was!

Definitely needs to be fixed.

@thgh I think the specific answer to this problem is to actually not render transitions at all on route change. I don't need items in my list, for example, to fade out when I change the page - I just want them gone.

@mustafa0x
Copy link
Contributor

Does |local not fix this?

https://svelte.dev/docs#Transition_events

@romland
Copy link

romland commented Dec 10, 2019

Since this issue is still open, I can confirm that |local does fix this problem in my playground.

@antony
Copy link
Member

antony commented Feb 14, 2020

@romland @mustafa0x for me, |local fixed the issue - so thank you hugely for that.

I think there is an issue with routing and the tolerance of transitions, so I will be looking to see if there is a way to fix this when I have time (and brainpower).

@iandoesallthethings
Copy link

Just wanted to +1 this issue. Routing started acting funny as I was writing a back-end api. So I spent 2 hours trying to debug my async calls before finding this.

|local stops components from sticking on navigation, but the animations glitch out. Oh well.

@mattpilott
Copy link

After being stuck for the last few days on what the heck was going on, i found this thread and applied |local to all transitions and finally seems to have fixed my incorrectly destroyed pages.

Be great to get this fixed at some point, ive run into it before too!

@antony
Copy link
Member

antony commented Jun 17, 2020

@matt3224 are you using an old version of Sapper or Svelte? I'm fairly sure was fixed a few months ago now.

@mattpilott
Copy link

Im using svelte 3.23.2 and 0.27.16 @antony i also thought this had been resolved. If i get time ill see if i can make a repro repo of it

@mattpilott
Copy link

Still seeing this today with latest latest versions. I do my best to now use |local on everything unless that gives undesirable results i then use css transitions and class changes to get around it for now. Would be great to see this fixed once and for all!

@mustafa0x
Copy link
Contributor

On a related note, I feel there's a good argument for making |local the default for transitions.

@niklasgrewe
Copy link

i do not know if this is the right place, but i have a similar error. I have a List Component with Child Elements like this:

<IntersectionObserver let:intersecting>
  {#if intersecting}
     ...
     {#if $isMobileOnly}
       ....
      {:else}
          <List horizontal>
             <div transition:page|local><Card href="/service" title="Service" /></div>
             <div transition:page|local><Card href="/gallery" title="Gallery" /></div>
             <div transition:page|local><Card href="/about" title="About" /></div>
       {/if}
   {/if}
</IntersectionObserver>

When i add |local to my transitions, nothing happend. When i remove |local all Card transitions are fired, when i switch to another page, instead of just executing the transition from the Card i clicked. I also tried to replace my <Card /> with svelte:component this={Card} /> but it doesn't work too.

Is there any other way to fix this? Any ideas?

@evdama
Copy link

evdama commented Nov 28, 2020

HA! After three days of trying to find out what the heck is going on I came across this thread and guess what... exact same issue as the OP and applying |local to my transitions fixed things...

A component from routeA showed up on routeB after using a href to go from routeA to routeB so all contents/components of routeB came in below the component from routeA 🤯

Of course I have a ton of <slots> in my code on those two routes, then there's a _layout.svelte in a subdir, the one which routeB is in, and for the first time in my Svelte career I decided using a [slug].svelte route would be a good idea...

That was four days ago... after peeling away one of those layers per day, trying to make sure it's not that thing, I'm here now, four layers deep into the rabbithole 🤣

The fifth layer of course was the browser cache, which I also made sure isn't the problem, that one was Endgegner on level ahm layer 2, which only took about 3 hours to defeat entirely and make sure no stone is left unturned...

Well, |local ftw now it is 🤠

For reference, all my package version below... it's up to date code so the issue still seems to be present these days:

sa@m1-mini: ~/0/edm  |master U:2 ✗| alias gr | gr svelte package.json
package.json:17:    "@egjs/svelte-infinitegrid": "^3.0.4",
package.json:30:    "@tsconfig/svelte": "^1.0.10",
package.json:52:    "eslint-plugin-svelte3": "^2.7.3",
package.json:79:    "rollup-plugin-svelte": "^7.0.0",
package.json:85:    "svelte": "^3.30.0",
package.json:86:    "svelte-check": "^1.1.17",
package.json:87:    "svelte-click-outside": "^1.0.0",
package.json:88:    "svelte-favicon-badge": "^1.0.0",
package.json:89:    "svelte-forms-lib": "^1.2.2",
package.json:90:    "svelte-grid": "^3.3.1",
package.json:91:    "svelte-inline-input": "^1.2.1",
package.json:92:    "svelte-preprocess": "^4.6.1",
package.json:93:    "svelte-scrollto": "^0.2.0",
package.json:94:    "svelte-spinner": "^2.0.2",
package.json:95:    "svelte-typewriter": "^2.4.1",
package.json:135:   "validate": "svelte-check"
sa@m1-mini: ~/0/edm  |master U:2 ✗| gr \"sapper\" package.json
package.json:84:    "sapper": "^0.28.10",
sa@m1-mini: ~/0/edm  |master U:2 ✗|

Found what was sending me in circles for the last few days... My <PageTransition> component used to create smoother page transitions when changing routes

// PageTransition.svelte

<script>
  import { cubicInOut } from 'svelte/easing'

	let duration = 250
	let delay = duration


	const transitionIn = () => ({
		duration,
		delay,
		easing: cubicInOut,
		css: (t) => `opacity: ${t}`,
	})


	const transitionOut = () => ({
		duration,
		delay: 0,
		easing: cubicInOut,
		css: (t) => `opacity: ${t}`,
	})
</script>


<div in:transitionIn out:transitionOut|local>
	<slot></slot>
</div>

Adding |local to out:transtionsOut fixed all the weirdness I had seen and couldn't easily find for the last few days.

@benmccann benmccann transferred this issue from sveltejs/sapper Mar 24, 2021
@macmillen
Copy link

I also encountered this issue with svelte kit but the |local directive doesn't solve my problem because my desired behavior is that all of my animations always trigger on route changes. With local that's not possible anymore because my animated components lie deeply nested in the component tree and have multiple render conditions additionally to the implicit route change condition. Does anyone know how to fix / workaround this? I really hope this annoying bug gets fixed soon and all transitions are handled gracefully on any condition.

@JuanDouek
Copy link

i do not know if this is the right place, but i have a similar error. I have a List Component with Child Elements like this:

<IntersectionObserver let:intersecting>
  {#if intersecting}
     ...
     {#if $isMobileOnly}
       ....
      {:else}
          <List horizontal>
             <div transition:page|local><Card href="/service" title="Service" /></div>
             <div transition:page|local><Card href="/gallery" title="Gallery" /></div>
             <div transition:page|local><Card href="/about" title="About" /></div>
       {/if}
   {/if}
</IntersectionObserver>

When i add |local to my transitions, nothing happend. When i remove |local all Card transitions are fired, when i switch to another page, instead of just executing the transition from the Card i clicked. I also tried to replace my <Card /> with svelte:component this={Card} /> but it doesn't work too.

Is there any other way to fix this? Any ideas?

Try this:

<IntersectionObserver let:intersecting>
  {#if intersecting}
     ...
  {/if}

  {#if intersecting && $isMobileOnly}
    ...
  {:else if intersecting}
      <List horizontal>
         <div transition:page|local><Card href="/service" title="Service" /></div>
         <div transition:page|local><Card href="/gallery" title="Gallery" /></div>
         <div transition:page|local><Card href="/about" title="About" /></div>
  {/if}
</IntersectionObserver>

@braebo
Copy link
Member

braebo commented Jun 22, 2021

I think a lot of issues involving transitions (elements not being removed from the dom, transitions interfering with navigation, etc) could be mitigated if we had a way to trigger / abort a transition manually, and/or forcefully remove elements from the dom without having to wait for a transition to finish. I find myself battling with this often, especially when |local isn't an option.

mquandalle added a commit to mquandalle/mesaidesvelo that referenced this issue Nov 21, 2021
Ça serait bien d'avoir une solution plus systématique à
sveltejs/kit#628
mquandalle added a commit to mquandalle/mesaidesvelo that referenced this issue Nov 21, 2021
Ça serait bien d'avoir une solution plus systématique à
sveltejs/kit#628
mquandalle added a commit to mquandalle/mesaidesvelo that referenced this issue Nov 21, 2021
Ça serait bien d'avoir une solution plus systématique à
sveltejs/kit#628
@mattpilott
Copy link

@evdama i'm finding that this no longer works in kit 202 i'm seeing the scroll to top flash before the next page fades in again like we used to get

@eduardoliron
Copy link

I was experiencing a similar problem as the one in the first post, reported by @imCorfitz.
As @macmillen said, the |local solution wasn't working properly either, once the animations stopped working after I applied it.

It turns out that the bug happens when the parent div to the {#key refresh} code is set as relative.
Changing it, the code worked as expected.

@mattpilott
Copy link

@eduardoliron this is quite interesting, i actually use a position: relative as a css reset, i'll try setting to static and see if i can confirm your results

@Rich-Harris
Copy link
Member

I'm going to close this since transitions are working as intended (Svelte won't remove elements until transitions have completed), and |local is the way to get the desired outcome. I do acknowledge that some behaviour around transitions is a little counterintuitive though — it's on our list of things to revisit for Svelte 4, when we have the opportunity to make breaking changes.

@power-f-GOD
Copy link

power-f-GOD commented May 8, 2022

For some reason |local did not work for me, and even when I used/tried it, the smooth enter and exit transitions were lost (in this case, a Modal; it just, sort of, 'blips'). 💔

If you care about keeping your (smooth) transition, I have a hack which I've tried and used. Though not the best of solutions, but it worked and it works.

The root __layout.svelte

<script lang="ts">
  import { afterNavigate } from '$app/navigation';

  afterNavigate(() => {
    setTimeout(() => {
      if (document?.querySelectorAll('body .App').length > 1) {
        document?.body?.removeChild(document?.querySelector('body .App')!);
      }
    })
  })
</script>

<main class="App">
  Your App.
</main>

Welcome. ⚡️

@janwillemtulp
Copy link

I'm going to close this since transitions are working as intended (Svelte won't remove elements until transitions have completed), and |local is the way to get the desired outcome. I do acknowledge that some behaviour around transitions is a little counterintuitive though — it's on our list of things to revisit for Svelte 4, when we have the opportunity to make breaking changes.

Why is |local not the default behaviour and then you have to set the opposite, if desired (wait until something else is complete)?

@johnnysprinkles
Copy link
Contributor

johnnysprinkles commented Nov 5, 2022

Could we add a compile option for "transitionsLocalByDefault" to https://svelte.dev/docs#compile-time-svelte-compile and extend the transition grammar to include a |global modifier?

This issue keeps biting me. Most recently when "afterNavigate" was firing on a page that I just left, in spite of the fact that https://kit.svelte.dev/docs/modules#$app-navigation-afternavigate says it should unregister the handler when component unmounts. That part isn't wrong, it's just that the component was unmounted later than expected.

Either that, or maybe someone should make a lint rule that all transition, in and out needs to include the local modifier.

@hanssens
Copy link

hanssens commented Dec 8, 2022

I agree with @janwillemtulp's suggestion: this should be the default. But @johnnysprinkles' suggestion to make it configurable is not bad either!

Reasoning for us is basically we keep encountering "DOM-leaks" because a dev might forget to add a |local.

Because this issue is closed, question at @Rich-Harris regarding:

it's on our list of things to revisit for Svelte 4, when we have the opportunity to make breaking changes.

Is there by any chance a way to track or follow this topic, like is there a new issue registered? Or can we have a glimpse at considerations for Svelte v-next somewhere?

@cubedhuang
Copy link

cubedhuang commented Dec 20, 2022

image
One reason using |local may not work is that for me, I have both an if block and a key block, and using local now only transitions changes on the key block instead of both the if and the key.

Does anyone have any workarounds for this currently?

@BMorearty
Copy link

This impacts me as well. There's a div that slides in and out but it looks weird when navigating away from the page.

I would be happy with a modifier that specifies not to run transitions when navigating. E.g., transition:slide|noNavigate.

I have nested blocks that are difficult to reorganize to make |local work:

  {#await load()}
    loading...
  {:then { a, b }}
    {#if a}
      <div>✅ Congrats.</div>
    {:else if b}
      <div class="b" transition:slide={{ easing: cubicIn, duration: 200 }}>
        ...          ^^^^^^^^^^
      </div>
    {/if}
  {/await}

@ollyde
Copy link

ollyde commented Aug 22, 2023

This is still an issue on main Sveltekit if anyone else got this nasty bug. Was debugging almost a-whole day.

Would be nice to get warnings without using local, and to explicitly say |out or |wide

@FeldrinH
Copy link

FeldrinH commented Aug 22, 2023

Would be nice to ..., and to explicitly say |out or |wide

I think this is the case in Svelte 4, which was released about a month ago. (https://svelte.dev/docs/v4-migration-guide#transitions-are-local-by-default)

@ollyde
Copy link

ollyde commented Aug 22, 2023

@FeldrinH looks like I'm still on 1?

Vite is quiet behind then.

Screenshot 2023-08-22 at 20 27 03

@imCorfitz
Copy link
Author

@FeldrinH looks like I'm still on 1?

SvelteKit and Svelte are two different packages. SvelteKit is a framework making use of Svelte. Could be interesting to see if indeed updating to the latest version of Svelte (not SvelteKit necessarily) could improve and/or fix this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests