Skip to content

Commit

Permalink
Implement route transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielSharkov committed Nov 29, 2021
1 parent 417b4f6 commit a116087
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 74 deletions.
94 changes: 93 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[![Simple, fast & easy to use Svelte Router](https://github.com/DanielSharkov/svelte-router/blob/master/readme-banner.svg)](#)

[![Live Demo](https://img.shields.io/badge/▶-Live%20Demo-2962ff)](https://danielsharkov.github.io/svelte-router-examples) [![Examples](https://img.shields.io/badge/🧩-Examples-ff9100)](https://github.com/DanielSharkov/svelte-router-examples) [![npm version](https://badge.fury.io/js/@danielsharkov%2Fsvelte-router.svg)](https://badge.fury.io/js/@danielsharkov%2Fsvelte-router) ![GitHub](https://img.shields.io/github/license/danielsharkov/svelte-router)
[![Live Demo](https://img.shields.io/badge/▶-Live%20Demo-2962ff)](https://danielsharkov.github.io/svelte-router-examples)
[![Examples](https://img.shields.io/badge/🧩-Examples-ff9100)](https://github.com/DanielSharkov/svelte-router-examples)
[![npm version](https://badge.fury.io/js/@danielsharkov%2Fsvelte-router.svg)](https://badge.fury.io/js/@danielsharkov%2Fsvelte-router)
![GitHub](https://img.shields.io/github/license/danielsharkov/svelte-router)

# 🗂 Index

Expand All @@ -16,6 +19,7 @@
- [RouteLink Component](#routelink-component)
- [RouteLink with Parameters](#routelink-with-parameters)
- [Svelte Action use:link](#svelte-action-uselink)
- [Route Transitions](#route-transitions)
- [Router Examples](#-router-examples)

# 🧗‍♀️ Getting Started
Expand Down Expand Up @@ -540,6 +544,94 @@ then leave the parameter `router` blank.

---

### Route Transitions
Route transitions can't be just applied and used on a route easily. If you
would just add some transitions into the route component and navigate through
the routes, it will show unexpected behavior (see Svelte Issues: [#6779](https://github.com/sveltejs/svelte/issues/6779), [#6763](https://github.com/sveltejs/svelte/issues/6763), [and even including my simple REPL](https://svelte.dev/repl/a5122281148c4c458f40e317fc4be11e?version=3.44.2)).

**But! Dirty hacks to the rescue:** 😎💡

To tell the viewport that a route has a transition you must dispatch the event
`hasOutro` inside the `onMount` handler. Now that the viewport is aware of the
outro transition, it's going to await the route to finish its transition,
before switching to the next route.
Now that the router is awaiting the outro, at the end of the transition we have
to tell the viewport that it may switch further to the next route.
This is done by dispatching the another event called `outroDone`.
That's the trick!

> **Info:** Any mistaken dispatched event `outroDone` will be ignored by
the viewport, as it only listens for the event after the routers location
has changed. Meaning you may just dispatch this event on every outro
transition without worring.

> **Info:** Inside the route component be sure to call the `outroDone` event on the
longest outro transition on any element inside the component, as they have to finish
as well. For better understanding see the second example below 👇

> ⚠️ **Warning:** Be sure to fire the event `outroDone` after telling the viewport
to await the outro transition, otherwise the viewport will wait a indefinitely.

```svelte
<script lang='ts'>
import {onMount, createEventDispatcher} from 'svelte'
import {fade} from 'svelte/transition'
const dispatch = createEventDispatcher()
onMount(()=> {
dispatch('hasOutro')
})
</script>
<div class='page'
transition:fade={{duration: 400}}
on:outroend={()=> dispatch('outroDone')}>
<h1>Some content</h1>
<p>Lorem Impsum...</p>
</div>
```

##### A route containing a child transition
```svelte
<script lang='ts'>
import {onMount, createEventDispatcher} from 'svelte'
import {fade, fly} from 'svelte/transition'
const dispatch = createEventDispatcher()
onMount(()=> {
dispatch('hasOutro')
})
const custom =()=> ({
duration: 1000,
css: (t)=> (
`opacity: ${t};` +
`transform: rotate(${360 - 360 * t}deg);`
)
})
</script>
<!-- You may delay the actual route transition, otherwise it will already
fade out and the user will see a blank screen, where it's actually is still
processing a child outro. I set it to 600ms, because 1000ms of the longest
child transition (the heading) minus the 400ms route transition is a delay of 600ms. -->
<div class='page'
transition:fade={{duration: 400, delay: 600}}
on:outroend={()=> dispatch('outroDone')}>
<h1 transition:custom>
Some content
</h1>
<p style='display: inline-block;' transition:fly={{duration: 700}}>
Lorem Impsum...
</p>
</div>
```

---

### 🧩 Router Examples
You can find full router examples in [danielsharkov/svelte-router-examples](https://github.com/DanielSharkov/svelte-router-examples)

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@danielsharkov/svelte-router",
"version": "2.4.12",
"version": "3.0.0",
"description": "A simple, fast and easy to use svelte router.",
"license": "MIT",
"keywords": [
Expand Down
10 changes: 5 additions & 5 deletions src/RouteLink.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {SvelteComponentTyped} from 'svelte'
import type {RouteParams} from '.'

declare module 'RouteLink.svelte' {
interface RouteLinkProps {
to: string
params?: RouteParams
}
class RouteLinkComponent extends SvelteComponentTyped<RouteLinkProps, {change: CustomEvent<number>}, unknown> {}
class RouteLinkComponent extends SvelteComponentTyped<
{to: string, params?: RouteParams},
unknown,
unknown,
> {}
export default RouteLinkComponent
}
22 changes: 5 additions & 17 deletions src/Viewport.d.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import type {EasingFunction, TransitionConfig} from 'svelte/types/runtime/transition'
import {SvelteComponentTyped} from 'svelte'

declare module 'Viewport.svelte' {
type CustomTransition =(node: Element, {isIntro, duration, easing, delay}: {
isIntro?: boolean,
duration?: number,
easing?: EasingFunction,
delay?: number
}?)=> TransitionConfig

interface ViewportProps {
router: SvelteRouter
duration?: number
delay?: number
easing?: EasingFunction
transition?: CustomTransition
}

class ViewportComponent extends SvelteComponentTyped<ViewportProps, {change: CustomEvent<number>}, unknown> {}
class ViewportComponent extends SvelteComponentTyped<
{router: SvelteRouter},
unknown,
unknown,
> {}
export default ViewportComponent
}
77 changes: 28 additions & 49 deletions src/Viewport.svelte
Original file line number Diff line number Diff line change
@@ -1,51 +1,38 @@
{#if isActualView}
<div
in:transition={{isIntro: true, duration, easing, delay}}
out:transition={{isIntro: false, duration, easing, delay}}
{...attrs}
on:outroend={()=> {
updateComponent()
}}>
<svelte:component
{router}
this={current.component}
params={current.params}
urlQuery={current.urlQuery}
props={current.props}
/>
</div>
<svelte:component
{router}
on:hasOutro={hasOutro}
on:outroDone={outroDone}
this={current.component}
params={current.params}
urlQuery={current.urlQuery}
props={current.props}
/>
{/if}

<script>
import {setContext} from 'svelte'
import {fade} from 'svelte/transition'
import {linear} from 'svelte/easing'
import {setContext, tick} from 'svelte'
export let router
export let duration = 150 // 300ms in whole, intro + outro
export let delay = 0
export let easing = linear
export let transition = defaultTransition
function defaultTransition(
node, {isIntro, duration, easing, delay} = {},
) {
return fade(node, {
duration: duration,
easing: easing,
delay: delay,
})
}
setContext('svelte_router', router)
const attrs = Object.assign({}, $$props)
setContext('svelte_router', router)
if (!router || !router.subscribe) {
throw new Error(
'[SvelteRouter] <RouterViewport> is missing a router instance'
)
}
let viewHasOutro = false
function hasOutro() {
viewHasOutro = true
}
function outroDone() {
if (!isActualView) {
viewHasOutro = false
updateView()
}
}
let current = {
name: '',
path: '',
Expand All @@ -55,30 +42,22 @@
props: undefined,
}
function updateComponent() {
current = $router.location
}
updateComponent()
const compareParams =()=> (
JSON.stringify($router.location.params || {}) ===
JSON.stringify(current.params || {})
)
$:isActualView = (
$router.location.name === current.name &&
$router.location.component === current.component &&
compareParams() &&
$router.location.urlQuery === current.urlQuery
)
$:if (!isActualView && !viewHasOutro) {
updateView()
}
function removeAttrs() {
delete attrs.router
delete attrs.duration
delete attrs.delay
delete attrs.easing
delete attrs.transition
delete attrs.$$scope
async function updateView() {
await tick()
current = $router.location
}
removeAttrs()
</script>

0 comments on commit a116087

Please sign in to comment.