Skip to content

Commit 5f4f2f7

Browse files
yeion7SaraVieira
authored andcommitted
Hide animations on prefers-reduced-motion (codesandbox#2802)
* global reduce motion * add hooks * refactor hooks and hide animation * refactor hooks and hide animation * hide animation * disable button animation * keep vertical steps * use interval * check server render
1 parent ed356ed commit 5f4f2f7

File tree

9 files changed

+354
-259
lines changed

9 files changed

+354
-259
lines changed

packages/homepage/src/css/global.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,13 @@ twitter-widget {
5050
margin-left: auto;
5151
margin-right: auto;
5252
}
53+
54+
@media screen and
55+
(prefers-reduced-motion: reduce),
56+
(update: slow) {
57+
* {
58+
animation-duration: 0.001ms !important;
59+
animation-iteration-count: 1 !important;
60+
transition-duration: 0.001ms !important;
61+
}
62+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { useMatchMedia } from './useMatchMedia';
2+
export { useInterval } from './useInterval';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
3+
export function useInterval(callback, delay) {
4+
const savedCallback = React.useRef();
5+
6+
React.useEffect(() => {
7+
savedCallback.current = callback;
8+
}, [callback]);
9+
10+
// eslint-disable-next-line consistent-return
11+
React.useEffect(() => {
12+
function tick() {
13+
if (savedCallback && savedCallback.current) {
14+
savedCallback.current();
15+
}
16+
}
17+
18+
if (delay !== null) {
19+
const id = setInterval(tick, delay);
20+
return () => clearInterval(id);
21+
}
22+
}, [delay]);
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
3+
const isServerRender = typeof window === `undefined`;
4+
5+
export function useMatchMedia(query, defaultMatch = false) {
6+
const mediaQueryList = !isServerRender && window.matchMedia(query);
7+
const [match, setMatch] = React.useState(
8+
isServerRender ? defaultMatch : mediaQueryList.matches
9+
);
10+
11+
React.useEffect(() => {
12+
const handleMatchChange = ev => setMatch(ev.matches);
13+
14+
mediaQueryList.addListener(handleMatchChange);
15+
16+
return () => {
17+
mediaQueryList.removeListener(handleMatchChange);
18+
};
19+
}, [mediaQueryList, query]);
20+
21+
return match;
22+
}

packages/homepage/src/screens/home/Animation/index.js

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Cubes from './Cubes';
2222
import Frameworks from '../Frameworks';
2323

2424
import getScrollPos from '../../../utils/scroll';
25-
25+
import { useMatchMedia, useInterval } from '../../../hooks';
2626
import media from '../../../utils/media';
2727

2828
const Container = styled(Centered)`
@@ -66,77 +66,65 @@ const Message = styled.h2`
6666

6767
const TEMPLATES = [parcel, react, vue, angular, gatsby, nuxt, next];
6868

69-
export default class Animation extends React.PureComponent {
70-
state = {
71-
templates: TEMPLATES.filter(tem => tem.showOnHomePage && tem.showCube),
72-
templateIndex: 0,
73-
templateSelected: false,
74-
canvas: null,
75-
};
76-
77-
componentDidMount() {
78-
this.startTimer();
79-
}
80-
81-
componentWillUnmount() {
82-
clearTimeout(this.timeout);
83-
}
84-
85-
startTimer = () => {
86-
this.timeout = setTimeout(() => {
87-
if (!this.state.templateSelected) {
88-
if (!window.scrolling && getScrollPos().y < window.innerHeight) {
89-
this.setState(state => ({
90-
templateIndex: (state.templateIndex + 1) % state.templates.length,
91-
}));
92-
}
93-
94-
this.startTimer();
69+
const Animation = () => {
70+
const templates = TEMPLATES.filter(tem => tem.showOnHomePage && tem.showCube);
71+
const reduceAnimation = useMatchMedia('(prefers-reduced-motion: reduce)');
72+
const [templateIndex, setTemplateIndex] = React.useState(0);
73+
const [templateSelected, setTemplateSelected] = React.useState(false);
74+
const canvas = React.useRef();
75+
76+
const changeTemplate = React.useCallback(() => {
77+
if (!templateSelected) {
78+
// @ts-ignore
79+
if (!window.scrolling && getScrollPos().y < window.innerHeight) {
80+
setTemplateIndex(index => (index + 1) % templates.length);
9581
}
96-
}, 6000);
97-
};
82+
}
83+
}, [templateSelected, templates.length]);
9884

99-
setCanvas = canvas => {
100-
this.setState({ canvas });
85+
const setCanvas = canvasToSet => {
86+
canvas.current = canvasToSet;
10187
};
10288

103-
selectTemplate = template => {
104-
this.setState(state => ({
105-
templateIndex: state.templates.indexOf(template),
106-
templateSelected: true,
107-
}));
89+
const selectTemplate = template => {
90+
setTemplateIndex(templates.indexOf(template));
91+
setTemplateSelected(true);
10892
};
10993

110-
render() {
111-
const template = this.state.templates[this.state.templateIndex];
112-
return (
113-
<Relative>
114-
<Fullscreen>
115-
<Background
116-
templateIndex={this.state.templateIndex}
117-
template={template}
118-
setCanvas={this.setCanvas}
119-
/>
120-
<Container horizontal>
121-
<HomeTitle template={template} />
94+
useInterval(changeTemplate, 6000);
95+
const template = templates[templateIndex];
96+
97+
return (
98+
<Relative>
99+
<Fullscreen>
100+
<Background
101+
templateIndex={templateIndex}
102+
template={template}
103+
setCanvas={setCanvas}
104+
/>
105+
<Container horizontal>
106+
<HomeTitle template={template} />
107+
{!reduceAnimation && (
122108
<Media query="(min-width: 1280px)">
123109
<Cubes
124-
canvas={this.state.canvas}
125-
templates={this.state.templates}
110+
canvas={canvas.current}
111+
templates={templates}
126112
template={template}
127-
setTemplate={this.selectTemplate}
113+
setTemplate={selectTemplate}
128114
/>
129115
</Media>
130-
</Container>
131-
</Fullscreen>
132-
<Centered horizontal>
133-
<Message>
134-
CodeSandbox is an online editor that helps you create web
135-
applications, from prototype to deployment.
136-
</Message>
137-
</Centered>
138-
<Frameworks templates={TEMPLATES.filter(tem => tem.showOnHomePage)} />
139-
</Relative>
140-
);
141-
}
142-
}
116+
)}
117+
</Container>
118+
</Fullscreen>
119+
<Centered horizontal>
120+
<Message>
121+
CodeSandbox is an online editor that helps you create web
122+
applications, from prototype to deployment.
123+
</Message>
124+
</Centered>
125+
<Frameworks templates={TEMPLATES.filter(tem => tem.showOnHomePage)} />
126+
</Relative>
127+
);
128+
};
129+
130+
export default React.memo(Animation);

0 commit comments

Comments
 (0)