Skip to content

Commit c7c4672

Browse files
committed
Respect reduced motion
1 parent 45fdafa commit c7c4672

File tree

5 files changed

+52
-15
lines changed

5 files changed

+52
-15
lines changed

README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
# sveltekit-shared-element-transitions-codelab
1+
# sveltekit-shared-element-transitions
22

3-
SvelteKit version of the [Shared Element Transitions Google CodeLab](https://codelabs.developers.google.com/create-an-instant-and-seamless-web-app#5). Only works in Chrome Canary with the documentTransition API flag enabled and may break at any time due to the API changing.
3+
SvelteKit version of the [Shared Element Transitions Google CodeLab](https://codelabs.developers.google.com/create-an-instant-and-seamless-web-app#5). Only works in Chrome Canary with the `chrome://flags/#document-transition` flag enabled and may break at any time due to the API changing.
44

5-
Bugs that I need to look into:
5+
## Additional features
66

7-
- layout shifting during transition
7+
In porting this to Svelte, I also implemented some additional features.
8+
9+
- Also transition the fruit page heading, in addition to the image
10+
- Transition _back_ to the list of fruits
11+
- Respect reduced motion by not playing the transition if reduced motion is enabled
12+
- Transition when the back/forward buttons are clicked (the original demo only used a link click as the trigger)
13+
14+
## Bugs
15+
16+
- layout shifts during transition
817
- sveltekit:prefetch causes the page to transition from the top-right corner instead. not sure why.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "sveltekit-shared-element-transitions-codelab",
2+
"name": "sveltekit-shared-element-transitions",
33
"version": "0.0.1",
44
"scripts": {
55
"dev": "svelte-kit dev",

src/lib/utils/page-transition.js src/lib/page-transition.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { afterNavigate, beforeNavigate } from '$app/navigation';
2-
import { setContext, getContext, hasContext } from 'svelte';
2+
import { setContext, getContext, hasContext, onDestroy } from 'svelte';
33
import { writable } from 'svelte/store';
4+
import reducedMotion from './reduced-motion';
45

56
const contextKey = 'transition';
67

@@ -9,22 +10,19 @@ export function initTransitionContext() {
910
return setContext(contextKey, writable({}));
1011
}
1112

12-
export function getTransitionContext() {
13+
function getTransitionContext() {
1314
if (!hasContext(contextKey)) {
1415
return initTransitionContext();
1516
}
1617
return getContext(contextKey);
1718
}
1819

19-
// Call this hook on this first page before you start the page transition.
20-
// For Shared Element Transitions, you need to call the transition.start()
21-
// method before the next page begins to render, and you need to do the
22-
// Document Object Model (DOM) modification or setting of new shared
23-
// elements inside the callback so that this hook returns the promise and
24-
// defers to the callback resolve.
2520
export const preparePageTransition = () => {
2621
const transitionStore = getTransitionContext();
2722
let unsub;
23+
let isReducedMotionEnabled;
24+
25+
let unsubReducedMotion = reducedMotion.subscribe((val) => (isReducedMotionEnabled = val));
2826

2927
function updateStore(key, value) {
3028
transitionStore.update((current) => ({
@@ -38,7 +36,7 @@ export const preparePageTransition = () => {
3836
unsub?.(); // clean up previous subscription
3937

4038
// Feature detection
41-
if (!document.createDocumentTransition) {
39+
if (!document.createDocumentTransition || isReducedMotionEnabled) {
4240
return;
4341
}
4442

@@ -55,6 +53,8 @@ export const preparePageTransition = () => {
5553

5654
afterNavigate(({ to }) => {
5755
const transitionKey = to.pathname;
56+
// we need to subscribe to prevent race conditions
57+
// sometimes this runs before the store is updated with the new transition
5858
unsub = transitionStore.subscribe((transitions) => {
5959
const transition = transitions[transitionKey];
6060
if (!transition) {
@@ -64,4 +64,9 @@ export const preparePageTransition = () => {
6464
resolver();
6565
});
6666
});
67+
68+
onDestroy(() => {
69+
unsub?.();
70+
unsubReducedMotion();
71+
});
6772
};

src/lib/reduced-motion.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { readable } from 'svelte/store';
2+
import { browser } from '$app/env';
3+
4+
const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
5+
6+
const getInitialMotionPreference = () => {
7+
if (!browser) return false;
8+
return window.matchMedia(reducedMotionQuery).matches;
9+
};
10+
11+
export default readable(getInitialMotionPreference(), (set) => {
12+
if (browser) {
13+
const setReducedMotion = (event) => {
14+
set(event.matches);
15+
};
16+
const mediaQueryList = window.matchMedia(reducedMotionQuery);
17+
mediaQueryList.addEventListener('change', setReducedMotion);
18+
19+
return () => {
20+
mediaQueryList.removeEventListener('change', setReducedMotion);
21+
};
22+
}
23+
});

src/routes/__layout.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import '../app.css';
33
import Navbar from '$lib/Navbar.svelte';
44
import Footer from '$lib/Footer.svelte';
5-
import { initTransitionContext, preparePageTransition } from '$lib/utils/page-transition';
5+
import { initTransitionContext, preparePageTransition } from '$lib/page-transition';
66
77
initTransitionContext();
88
preparePageTransition();

0 commit comments

Comments
 (0)