减少运动以提高可访问性
最初发布于a11ywithlindsey.com。
嘿朋友们!在这篇文章中,我将带您了解一个较新的媒体查询(对我来说)prefers-reduced-motion
:。
坦白说:我了解 CSS 的基础知识,但对新出的东西却相当落后。因为我通常关注可访问性,所以我更关注 HTML 和 JavaScript。当我关注 CSS 时,它确保了正确的颜色对比度或自定义焦点状态。有时我会使用 CSS 来实现复选框和键盘可访问性。我总是把媒体查询与响应式设计联系在一起。我从未想过媒体查询可以增强可访问性。
在这篇文章中,我们将通过以下方式更新我的博客:
- 添加
prefers-reduced-motion
查询 - 添加用户控制的设置以减少运动。
理解prefers-reduced-motion
对于患有前庭功能障碍的人来说,动画、缩放和平移操作可能会带来麻烦。这些功能障碍会导致晕动病和眩晕。这些不舒服的感觉你绝对不想经历,更不用说在网站上了。据我所知,前庭系统位于你的内耳,负责控制平衡。
根据vestibular.org 的数据,美国 40 岁及以上的成年人中,高达 35% 患有某种形式的前庭功能障碍。所以,这不是一个小问题。
从网络可访问性角度来看,我的主要收获是:
- 制作动画时要小心。
- 小心处理你的 gif。
- 使用
prefers-reduced-motion
。 - 允许用户控制减少运动。
你怎么做
实现查询非常简单:
@media screen and (prefers-reduced-motion: reduce) {
/* Reduced Motion Code */
}
在一些地方,我的链接有一些动画。
首先,我的链接有一个底部边框,当你将鼠标悬停在它上面时,它会向下移动。
然后是我的行动号召链接,当我们将鼠标悬停在它上面时,它会放大 1.1 倍。
我和安迪·贝尔 (Andy Bell)进行了交谈,他给了我一些实施方面的建议。
@media screen and (prefers-reduced-motion: reduce) {
* {
animation-play-state: paused !important;
transition: none !important;
scroll-behavior: auto !important;
}
}
实施更改后,我们有了悬停效果,但没有任何过渡。
这个策略从技术上来说效果不错。不过,我想完全移除悬停效果,保留链接的下划线。我可能还会调整一下比例。
@media screen and (prefers-reduced-motion: reduce) {
* {
animation-play-state: paused !important;
transition: none !important;
scroll-behavior: auto !important;
}
a {
padding-bottom: 0;
border-bottom: none;
text-decoration: underline;
}
}
经过这一改变,现在我所有的链接都只是一个简单的下划线。
如果没有过渡动画,行动号召链接在悬停时从scale(1)
到会显得有点突兀。所以我把它改成了。scale(1.1)
scale(1.05)
@media screen and (prefers-reduced-motion: reduce) {
* {
animation-play-state: paused !important;
transition: none !important;
scroll-behavior: auto !important;
}
a {
padding-bottom: 0;
border-bottom: none;
text-decoration: underline;
}
.blog__more-link a {
text-decoration: none;
}
.blog__more-link a:hover {
transform: scale(1.05);
}
.hero__cta a {
text-decoration: none;
}
.hero__cta a:hover {
transform: scale(1.05);
}
}
如何在 Mac 上测试
此设置主要适用于 macOS。
- 前往“系统偏好设置”
- 前往“辅助功能”
- 转至显示
- 勾选“减少运动”
非常简单!这篇文章发布后,你就可以在我的博客上测试一下了!
创建用户控制选项以减少运动
Andy Bell 的暗黑模式帖子启发了我,让我添加了用户控制选项。我们希望用户的偏好设置优先。我们也希望考虑到那些无法使用这些设置的用户。
我们将这样做:
- 创建一个带有“减少运动”标签的复选框。
- 在我的 Gatsby 应用中添加一个
checked
状态和一种切换该状态的方法。 - 使用该状态来控制
data-user-reduced-motion
属性。 - 使用上述属性应用 CSS。
- 将其存储在中
localStorage
,以便我们保留用户设置。
创建<ReduceToggle />
组件
此组件是一个带有标签的 HTML 复选框。声明一下,我使用的是class
组件,而不是钩子。我有时仍然喜欢写类,而且它更利于我的思维。请关注钩子版本!
import React from 'react'
class ReduceToggle extends React.Component {
render() {
return (
<div className="toggle">
<input id="reduce-motion" type="checkbox" />
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
我在这里唯一做的事情就是创建一个复选框输入框,并关联一个表单标签。你可能已经注意到,React 使用的是 ,而不是 for htmlFor
。
之后,我把它放在<Header />
菜单上方的组件里。以后我会再考虑样式的优化;我知道它会破坏我的布局,但没关系。我们现在只关心功能。
添加州
我们希望继续checked
向我们的构造函数添加一个状态。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
render() {
return (
<div className="toggle">
<input id="reduce-motion" type="checkbox" />
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
现在我们要将该状态添加到复选框本身。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
render() {
const { checked } = this.state
return (
<div className="toggle">
<input
id="reduce-motion"
type="checkbox"
checked={checked}
/>
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
接下来,我们要toggleChecked
为该onChange
事件添加一个方法。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
toggleChecked = event => {
this.setState({ checked: event.target.checked })
}
render() {
const { checked } = this.state
return (
<div className="toggle">
<input
id="reduce-motion"
type="checkbox"
checked={checked}
onChange={this.toggleChecked}
/>
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
我总是喜欢用React Developer Tools仔细检查状态是否正常工作。具体方法如下:
- 我检查元素
- 转到“React”选项卡
- 查找
ReduceToggle
组件 - 确保状态正常工作!
现在我们知道状态已经生效了。让我们切换data-user-reduced-motion
上的属性值documentElement
。我打算在componentDidUpdate
生命周期方法中添加这个操作。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
componentDidUpdate() {
const { checked } = this.state
if (checked) {
document.documentElement
.setAttribute('data-user-reduced-motion', true)
} else {
document.documentElement
.setAttribute('data-user-reduced-motion', false)
}
}
toggleChecked = event => {
this.setState({ checked: event.target.checked })
}
render() {
const { checked } = this.state
return (
<div className="toggle">
<input
id="reduce-motion"
type="checkbox"
checked={checked}
onChange={this.toggleChecked}
/>
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
将 CSS 添加到data-user-reduced-motion
提醒一下。直接跳到 CSS 代码里复制粘贴所有内容很诱人。我建议一步一步来。我犯了一个错误,试图一次性完成所有操作,结果花了比预期更多的时间进行调试。所以,首先让我们回到我们想要的目标。
我们希望用户偏好高于系统偏好。我们将逐步增强系统偏好设置。
Gatsby 是一个静态网站生成器,所以即使 JavaScript 无法加载,我的大部分静态网站内容也应该能够加载。但是,如果 JavaScript 无法加载,我们希望在data-user-reduced-motion
属性不存在时,使用系统偏好设置进行回退。因此,我们将在第一部分关于媒体查询本身的查询中添加一些内容。我们使用:not()
CSS 伪类来实现这一点。
@media screen and (prefers-reduced-motion: reduce) {
* {
:root:not([data-user-reduced-motion]) * {
animation-play-state: paused !important;
transition: none !important;
scroll-behavior: auto !important;
}
a {
:root:not([data-user-reduced-motion]) a {
padding-bottom: 0;
border-bottom: none;
text-decoration: underline;
}
.blog__more-link a {
:root:not([data-user-reduced-motion]) .blog__more-link a {
text-decoration: none;
}
.blog__more-link a:hover {
:root:not([data-user-reduced-motion]) .blog__more-link a:hover {
transform: scale(1.05);
}
.hero__cta a {
:root:not([data-user-reduced-motion]) .hero__cta a {
text-decoration: none;
}
.hero__cta a:hover {
:root:not([data-user-reduced-motion]) .hero__cta a:hover {
transform: scale(1.05);
}
}
然后我们在查询之外添加 CSS,以防万一data-user-reduced-motion="true"
。
:root[data-user-reduced-motion='true'] * {
animation-play-state: paused !important;
transition: none !important;
scroll-behavior: auto !important;
}
:root[data-user-reduced-motion='true'] a {
padding-bottom: 0;
border-bottom: none;
text-decoration: underline;
}
:root[data-user-reduced-motion='true'] .blog__more-link {
text-decoration: none;
padding: 12px 14px;
border: 2px solid;
}
:root[data-user-reduced-motion='true'] .blog__more-link:hover {
transform: scale(1.05);
}
:root[data-user-reduced-motion='true'] .hero__cta__link {
text-decoration: none;
padding: 12px 14px;
border: 2px solid;
}
:root[data-user-reduced-motion='true'] .hero__cta__link:hover {
transform: scale(1.05);
}
为了测试,我做了以下操作:
- 关闭 macOS 上的所有减少运动设置
- 取消选中“减少切换”后,确保所有动画仍然存在。
- 选中“减少切换复选框”并查看所有减少运动的 CSS 更改是否有效。
- 在元素检查器中,转到
<html>
文档并找到data-user-reduced-motion
。删除该属性。这里我们模拟该属性从未加载过。 - 前往系统偏好设置,勾选“减少动画效果”。我们应该修改 CSS 来减少动画效果!
添加localStorage
现在我们已经完成了这些工作,我们想开始尝试一下localStorage
。我们希望保留用户的偏好设置,以备将来使用。每次访问时都重新选择设置并不是最佳的用户体验。如果您不知道什么localStorage
是最佳的用户体验,我建议您在这里暂停一下,浏览一下文档。如果您喜欢视频示例,可以观看Wes Bos 的 JS30 教程。
我们要做的第一件事就是localStorage
设置componentDidMount
。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
componentDidMount() {
let reduceMotionOn = localStorage.getItem('reduceMotionOn')
console.log(reduceMotionOn)
// if we haven't been to the site before
// this will return null
}
// All other code stuff
render() {
return (
<div className="toggle">
<input id="reduce-motion" type="checkbox" />
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
现在,我们要做的是,如果reduceMotionOn
为空,则为用户创建一个默认的 localStorage 状态。我将其设置为false
。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
componentDidMount() {
let reduceMotionOn = localStorage.getItem('reduceMotionOn')
// Just a way to get around localStorage being
// stored as a string and not a bool
if (typeof reduceMotionOn === 'string') {
reduceMotionOn = JSON.parse(reduceMotionOn)
}
if (reduceMotionOn === null) {
localStorage.setItem('reduceMotionOn', false)
}
}
// All other code stuff
render() {
return (
<div className="toggle">
<input id="reduce-motion" type="checkbox" />
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
挂载组件后,我要做的最后一件事是设置应用程序中的状态。我想确保我的应用程序的状态与 相同localStorage
。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
componentDidMount() {
let reduceMotionOn = localStorage.getItem('reduceMotionOn')
if (typeof reduceMotionOn === 'string') {
reduceMotionOn = JSON.parse(reduceMotionOn)
}
if (reduceMotionOn === null) {
localStorage.setItem('reduceMotionOn', false)
}
this.setState({ checked: reduceMotionOn })
}
// All other code stuff
render() {
return (
<div className="toggle">
<input id="reduce-motion" type="checkbox" />
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
在 Chrome 开发者工具中,前往“应用程序”>“本地存储”(在 Firefox 中,前往“存储”>“本地存储”)。然后,清除reduceMotionOn
存储。刷新后,您应该会看到值为reduceMotionOn
false。如果您前往 React 开发者工具并转到该<ReduceToggle />
组件,您会发现选中状态与 reduceMotionOn localStorage 项匹配。
toggleChecked
这还不是全部!我们必须在React 组件的方法中切换 localStorage 。
import React from 'react'
class ReduceToggle extends React.Component {
constructor(props) {
super(props)
this.state = {
checked: false,
}
}
// All other code stuff
toggleChecked = event => {
localStorage.setItem('reduceMotionOn', event.target.checked)
this.setState({ checked: event.target.checked })
}
render() {
return (
<div className="toggle">
<input id="reduce-motion" type="checkbox" />
<label htmlFor="reduce-motion">Reduce Motion</label>
</div>
)
}
}
export default ReduceToggle
现在,如果我选中“减少运动”并离开网站,我的用户控制偏好设置将被保留!
结论
感谢大家的参与,我逐步完善了博客的无障碍功能!希望大家在过程中有所收获。感谢 Andy 的鼓励,让我写下了这篇文章!
无论您使用什么框架,以下是本文的要点:
- 制作动画时要小心,并为患有前庭疾病的人提供选择。
- 用户控制 > 系统偏好设置
- 拥有渐进式增强的系统偏好设置
- 使用
localStorage
对您有利的方式,以便保留用户设置!
如果您想尝试一下,我已经为您制作了一个 CodeSandbox!
保持联系!如果你喜欢这篇文章:
- 在Twitter上告诉我,并和你的朋友们分享这篇文章!另外,如果你有任何后续问题或想法,也欢迎随时在 Twitter 上给我留言。
- 在Patreon上支持我!如果您喜欢我的作品,可以考虑每月捐赠 1 美元。如果您捐赠 5 美元或更高,您将可以对以后的博客文章进行投票!我每月还会为所有赞助人举办“问我任何问题”活动!
- 抢先了解我的帖子,了解更多无障碍趣味!
干杯!祝你度过愉快的一周!
鏂囩珷鏉ユ簮锛�https://dev.to/lkopacz/reducing-motion-to-improve-accessibility-1c0i