Saying you wanted to show a loading indicator when navigating between routes, or something of that nature — the best way to do that would be to have a navigation hook:
import { onnavigate } from 'svelte/runtime/app';
onnavigate((from, to, promise) => {
console.log(`navigating from ${from.href} to ${to.href}`);
const loading = new LoadingIndicator();
promise.then(loading.destroy);
});
Need to consider what the arguments should be. Is there a possibility of something like onprogress, for example? Should from and to just be hrefs, or should we also get params etc? Should it be possible to cancel the navigation, or change it, or inject some data (e.g. {slidingTo: 'left'}), or delay it pending some operation?
And should it be onnavigate(...), or on('navigate', ...)? i.e. do we anticipate adding more events?
I personally really like your idea of exporting some kind of event emitter so additional events can be added in the future. Giving the callback from and to with a href also sounds like a future-proof idea if e.g. params should be added at a later stage.
// Possibly the default export?
import sapper from 'sapper/runtime.js';
sapper.on('navigate', ({ from, to, promise }) => { // object given to callback?
const loading = new LoadingIndicator();
promise
.then(loading.destroy)
// catch for when the navigation was overwritten?
.catch(() => {
console.log(`navigating from ${from.href} to ${to.href} was aborted`);
loading.destroy();
});
});
@jakearchibald has been playing around with a proposal to standardise navigation transitions. So maybe that's worth a look when trying to come up with an implementation.
Thanks for sharing that @jbmoelker! Hadn't seen that before. A lot of great information there.
I really like reason, which would give a lot of possibilities to fine tune potential animation during navigation. Since sapper navigation works by fetching a component and rendering that over the current component, I don't think newWindow and transitionUntil will be needed, because the document will be intact. What do you think?
Maybe sapper could wait for all the promises returned from the navigate listeners to resolve before rendering, and give a loaded promise to the callback which could be used for logic after the render?
function animate(keyframes, options) {
return new Promise(resolve => {
var animation = document.querySelector('main').animate(keyframes, options);
animation.onfinish = resolve;
})
}
sapper.on('navigate', ({ url, reason, loaded }) => {
loaded.then(() => animate([
{ opacity: 0 }, { opacity: 1 }
], { duration: 250, fill: 'backwards' }));
return reason === 'start'
? Promise.resolve()
: animate([{ opacity: 1 }, { opacity: 0 }], { duration: 250, fill: 'forwards' });
});

Maybe it could be useful with the newly rendered component as a sort of newWindow? Would allow for e.g. more animation logic in the components, and make things like nested routes easier to work with:
sapper.on('navigate', ({ url, reason, loaded, currentComponent }) => {
loaded.then(({ newComponent }) => newComponent.animateIn(reason));
return reason === 'start' ? Promise.resolve() : currentComponent.animateOut(reason);
});
Any progress on this?
This would also be very useful on the client for things such as google analytics / facebook pixel pageview events.
No JS event is fired, so there currently isn't any clean way to do this that I can see.
From Mozilla docs
Note: calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by performing a browser action, such as clicking on the back button (or calling history.back() in JavaScript), when navigating between two history entries for the same document.
Also, would it not be better to have the user trigger the navigation in the event handler with a function?
Something like:
sapper.on('navigate', async ({ url, loaded }) => {
await loaded()
window.ga && window.ga('send', url)
window.fbq && window.fbq('track', 'PageView')
})
This would mean that it would be trivial to block the route change by not calling the callback (if say a user has filled out a form but not saved yet), or to delay it.
While specific hooks would indeed be nice, for now you can add analytics and these sorts of global actions to a layout's onstate (before render) and onupdate (after render). For example:
// routes/_layout.html
onupdate({ changed, previous, current }) {
if (process.env.NODE_ENV === 'development') {
console.log('onupdate', previous, changed, current)
}
if (changed.path || changed.query) {
ga('set', 'page', current.path)
ga('send', 'pageview')
document.querySelectorAll('embed[data-youtube]').forEach(embed_youtube)
}
},
This was implemented (completely differently to anything discussed in this issue) in #642.
You can now listen for path, param, or query changes in any components:
import { stores } from '@sapper/app';
const { page, preloading, session } = stores();
page.subscribe(({ path, params, query }) => {
// do amazing things
})
For preloading indicators there is also the preloading store.
For me preloading is finished way before page is fully loaded, it can be seen on medium size pages. First preloading is triggered to false, and up to 1s later, the page changes.
Either there is an error in the documentation saying that "indicating whether or not a navigation is pending", or this hook does not work correctly. I guess it's about "preloading" and not "navigation", if it's the case, then I guess there is still no way to attach to navigation events, and this issue should be kept open.
For me preloading is finished way before page is fully loaded, it can be seen on medium size pages. First preloading is triggered to false, and up to 1s later, the page changes.
Either there is an error in the documentation saying that "indicating whether or not a navigation is pending", or this hook does not work correctly. I guess it's about "preloading" and not "navigation", if it's the case, then I guess there is still no way to attach to navigation events, and this issue should be kept open.
+1
Most helpful comment
This was implemented (completely differently to anything discussed in this issue) in #642.
You can now listen for path, param, or query changes in any components:
For preloading indicators there is also the
preloadingstore.https://sapper.svelte.dev/docs#Stores