具有可访问性的SVG
可缩放矢量图形(SVG)作为当今新兴的图形格式,在Web应用中往往更受偏爱。你是否也放弃了icon font或者因为浏览器对SVG良好的支持使其取代了旧的jpg、gif以及png图像格式?接下来就让我们看看这将如何影响用户的辅助功能(AT)以及如何确保每个用户都可以拥有良好的用户体验。
图形以及可替换文本
在开始这篇文章之前,我们先考虑一下图形、可访问性以及可替换文本。
1. 图形是否需要可替换文本?
如果图形纯粹用于修饰,其并不需要可替换文本。
所有的<img>
标签均需要合法有效的alt
属性,但是属性值为空<img src="pathtofile.svg" alt="">
,仍可验证。
2.图形上下文以及文本设置?
如果图形旁边具有文本/内容显示了可替换文本,这时不需要额外添加文本提示。如:
那么什么情况下需要使用alt
属性进行图形的可替换文字的提示呢(参考示例4)?根据图形的内容,你可以采取不同方式进行处理:
3. 图形是否有功能?如果有的话也应该传送给用户
例如,并不是完全按照图标所表示的含义进行标记:
不好的示例:
<a href="http://codepen.io/username">
<img src="codepen_icon.png" alt="CodePen Logo">
</a>
呈现用户一些文本信息:
好的示例:
<a href="http://codepen.io/username">
<img src="codepen_icon.png" alt="See Picked Pens">
</a>
有关详细的信息,请阅读WebAIM的文章 -- 全面了解可访问图形的可替换文本以及WAI关于图像的可访问性的教程
以下示例的运行环境:
- 浏览器支持SVG: IE 10+, FF, Chrome 以及 Safari
- 常见的屏幕阅读器: Jaws, NVDA, VoiceOver (VO) 以及 Narrator
基本图像替换
SVG最基本的实现,有以下几种情况:
1. SVG 作为图像来源
检查浏览器是否需要更新。如果已经是最新版本(Safari桌面版本9.1.1 或 iOS版本9.3.2),或者可更新的最高版本。
如果大多数用户的浏览器为Safari或者iOS的旧版本,此时需要在<img src="linktofile.svg" alt="Pixels, my super cute cat" role="img"
>添加role="img"
这样就可以修复Safari/WebKit的bug!
这个示例仅仅是基本的图像替换,不允许我们使用AT或者 CSS/JS 获取SVG的内容。所以,如果想要对SVG拥有更多的控制权,需要将其直接内联在HTML之中。
2. 内联 SVG
使用<use>
或者<img>
添加SVG将提供更多可预测的结果以及可控制性。因为SVG源代码直接暴露在DOM之中可以使用API进行访问修改。
让我们直接使用SVG实现上述<img>
实现的效果,并且对眼睛添加动画效果。这里就可以直接使用JS对直接对HTML之中的SVG进行操作。
由于此SVG不包含任何的可见的文本描述图形,我们需要添加文本(不可见):
- 在
<svg>
内添加:<title>关于SVG的一个简短标题</title>
,必须是父元素的第一个子元素;指针设备进行移动时具有工具提示 - 若有必要添加相关说明:不可被解说员阅读(bug区 ~)
根据W3C规范,除了在SVG中添加<title>
以及可能的<desc>
———因为它们提供可访问API,我们不能做任何的更改。不幸的是,浏览器对其支持性并不理想(Chrome以及Safari仍会报错)。
所以确保AT可以获取<title>
以及<desc>
:
在<title>
以及<desc>
添加相应的ID:
<title id="uniqueTitleID">SVG标题</title>
<desc id="uniqueDescID">关于图像更长的详细的描述</desc>
** 在<svg>
标签添加:**
aria-labelledby="uniqueTitleID uniqueDescID"
(使用<title>
以及<desc>
的ID) ——两者的介绍均包含在aria-labelledby
中,因为其相较于aria-describedby
更具有屏幕阅读器的支持(tip #4)
还有:
- 在
<svg>
标签添加:role="img"
(使浏览器直接映射到SVG 相关的group role)
想不想添加动画效果,如眼睛的闪烁?
setInterval(function blinkeyes() {
var tl = new TimelineLite();
tl.to(".eye", .4, {scaleY:.01, repeat:3, repeatDelay:.4, yoyo:true, transformOrigin: "50% 70%", ease:Power2.easeInOut});
return tl;
}, 5000);
var master = new TimelineLite();
master.add(blinkeyes());
更新 标题/说明 使其对图像的描述更加准确。
<desc id="catDesc">An illustrated gray cat with bright green blinking eyes.</desc>
3.使用object或者iframe嵌入SVG
现在我会避免使用<object>
以及<iframe>
。因为对于屏幕阅读器使用者来说,它们是不可用的。
如下是我的做法:
选择你嵌入SVG的方法并添加 tabindex="0"
:
<object type="image/svg+xml"
data="/path-to-the-svg/filename.svg"
width="50%"
tabindex="0">
<img src="Fallback_image.jpg" alt="alt content here">
</object>
<iframe src="/path-to-the-svg/filename.svg"
width="65%"
height="500"
sandbox
tabindex="0">
<img src="Fallback_image.jpg" alt="alt content here">
</iframe>
关于内联SVG,最后我们需要做的就是将SVG中的role="img"
替换为role="group"
。
<svg id="cat" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-labeledby="pixels-title pixels-desc" role="group">
接下来就是收尾整理工作了...
在SVG中添加一个<text>
,其包含<title>
以及可能存在的<desc>
(对于NVDA)的内容:
<text id="nvda-title">A cute, gray cat with green eyes. Cat illustration by Heather Migliorisi.</text>
之后,添加一个类在视觉上隐藏文本,但是保持屏幕阅读器的内容可用性,我们可以设置font-size:0
.sr-only { font-size: 0; }
最后,书写了两个<title>
(可能包含<desc>
)以及<text>
包含了相同的内容以便于在JAWS和NVDA得以实现。
注意:
<object>
以及<iframe>
在Chrome中不工作。Chrome可以检测到回调的内容,所以你可以使用alt
,意味着这将是第三个(或第四个)存储相同信息的内容。- JAWS不支持
<text>
内容(除了aria-labelledby/describedby中声明的)
如果可能,我建议(基于浏览器/屏幕阅读器的支持)使用<img src="svg.svg>
。虽然不总是具有可用性,如object/iframe支持互动/动画,但是<img>
不具有此功能,并且回调函数具有一定的欺骗性。
Icons
有好几篇关于是否使用SVG替换icon font的文章。我在思考使用SVG创建icon是否更容易实现。意味着: 如果在SVG主要的代码中使用<use>
实现SVG,浏览器是否可以支持<title>
。不幸的是,答案是否定的。但是,icon本身是容易实现的,下面我们将要展示如何实现。
一旦创建的SVG文件中包含icons(这里我喜欢使用icomoon)以及文档,我们需要决定网站使用的模式(icon + link
, icon + text
, 还是仅仅icon, 等等)。从这些模式中,我们可以设计应用替代文本的适当方法。
开始,通常icon代码看起来就像是一个icon迭代器:
<svg>
<title>phone</title>
<use xlink:href="#icon-phone"></use>
</svg>
Example 1: 独立有意义的 Icon
有意义的图标需要可替换文本。这种方法十分类似于“基本图像替换,内联SVG示例.”
- 更新标题文本,提示图标一些信息。这里提示这是一个支持移动设备的服务。
- 在
<svg>
标签添加role="img"
(因为SVG映射不一致,所以不能总是被AT很好的识别。如,如下示例就不起作用:Mac - VoiceOver + Chrome or Safari, Windows - NVDA + FF)
HTML:
<svg role="img">
<title>Supports Mobile Devices</title>
<use xlink:href="#icon-phone"></use>
</svg>
同样的,检测当前浏览器的版本。是不是已经是最新版本(Chrome 49.1)或者更新版本。
然而,如果大多数的用户仍旧使用Chrome老版本,这时需要在<title>
添加 id="xxxx"
以及在<svg>
添加 aria-labelledby="xxxx"
。
这样就修复了Chrome的bug!
Example 2: 独立装饰性的 Icon
装饰性图标(图标重复了文本所要表达的信息或者不具有重要价值)不需要可替换文本,并且应该隐藏在屏幕阅读器中。下面示例使用aria-hidden="true"
隐藏了SVG。
<p>
<svg aria-hidden="true">
<title>checkmark</title>
<use xlink:href="#icon-checkmark"></use>
</svg>
Success! Your order went through.
</p>
Example 3: 没有文本的链接 Icon
对于没有文本匹配的链接图标,我们可以在<a>
上添加aria-label
提供描述性的,可替换文本。在元素上添加aria-label="See Picked Pens"
。
<a href="link" aria-label="See Picked Pens">
<svg>
<use xlink:href="#icon-codepen"></use>
</svg>
</a>
Example 4: 具有静态文本的链接 Icon
再次,对于匹配文本的链接图标,我们在元素上添加aria-label
提供描述性的,可替换的文本。
在锚标记上添加aria-label
,屏幕阅读器不显示文本内部的连接。在元素添加aria-label="See Picked Pens"
。
<a href="link" aria-label="See Picked Pens">
<svg>
<use xlink:href="#icon-codepen"></use>
</svg>
CodePen
</a>
Example 5: 具有动态文本的链接Icon
假设在链接文本 + 图标上具有动态的值。这里,我们就不应该在链接使用aria-label
,因为会导致动态文本值丢失。对于这个示例,我们可以采用<span>
以及屏幕外文本的方法。id="itemsInCart"
的span
内的数字值是动态添加的元素。
- 对于其余文本使用额外的
<span>
进行添加(如: “items in your shopping cart”) - 添加
class="offscreen-text"
,从视觉隐藏文本 - 在svg添加
aria-hidden="true"
HTML:
<a href="http://example.com" id="cart">
<span id="itemsInCart">0</span>
<span class="offscreen-text">items in your shopping cart</span>
<svg aria-hidden="true">
<use xlink:href="#icon-cart"></use>
</svg>
</a>
完整的icon示例列表:
复杂图像——可访问图表
使用SVGs替代PNGs以及JPGs是一个重大的突破,当涉及复杂的web内容时,尤其是图表。使用alt
属性,它可以提供相对完整的信息说明。但是对于图像(如: png/jpg)的可替换文本这将会是一件十分棘手的问题。使用SVG并且所有的文本均可直接被访问!
1. 设置文件
图层顺序:在Adobe Illustrator中,SVG中的图层将从底部被导出。这一点很重要,因为我们想按照一定顺序建立图层,从而使其在屏幕阅读器上可以按照一定的逻辑进行显示。在代码中,“Jaws”组应该位列第一列出,而在Illustrator中则是位居图层最底部。
图层命名:对图层进行命名是一个很好的主意,这样子我们可以在导出的SVG中按照图层id进行添加。如果id命名出现重复,也不需要担心,这种情况下id会进行递增。
图层分组:对图层进行分组是十分重要的。每一个图表变形(Jaws, NVDA等等)中都包含文本标签 + 关键项(对于颜色)以及图表中分栏。这种方式的建立是方便增强屏幕阅读器使用者的理解能力。在一些浏览器中,用户可以单击分栏,合并的文本信息就会显示出来或者高亮显示。
保存/导出:作为保障,我喜欢将SVG保存两个版本,一种方便在Illustrator中进行编辑,另外一种用于代码的编辑使用。所以我使用“save as”用于illustrator版本,“file > export > svg”用于简洁的web版本。
优化:手动编辑SVG前的最后一件事就是对其进行优化。Jake Archibald创建的一种工具 - SVGOMG,就是一个很好的选择。添加SVG,之后切换到“CODE”视图查看正在切换实现的功能。设置“prettify”,以便于后期的代码编辑,因为我们更希望它是可读的。
如果没有100%确定最终版本的SVG样式,最好不要手动对其进行编辑(添加一些可访问功能)。一旦我们对SVG进行手动编辑,若编辑器(Inkscape/Illustrator/etc)无意保存了显示添加的内容,将很难再次恢复到原来的版本。
源代码的管理:如果使用基于git的版本控制器(git, SourceTree等等)管理源代码,提交SVG。使用其中的一种进行文件管理,当使用Illustrator(或者其余可视化编辑器)进行手动编辑后再次保存,就可以显示相较于原来版本的更改。
2. 可访问性
可遍历屏幕读取器:在<svg>
中添加role="group"
确保SVG在所有的浏览器中均可遍历。根据最新的SVG规范,它们应该映射到graphics-document
role。然而规范还处于草案之中,浏览器对其还不支持。
标题和说明:自从SVG中拥有文本元素 - 其行为类似于标题和说明。我们使用aria-labelledby="graph-title"
以及aria-describedby="graph-desc"
与<svg>
进行连接。
内容清理:清理使用Illustrator创建的元素。如: 在<text>
中添加的一些<tspans>
。屏幕读取器可能会读取字母(“J” “a” “w” “s” “- 44%”)而不是单词(“Jaws - 44%”)。所以有必要清理掉单个字母周围的不必要的<tspans>
。
不好的示例:
<text class="cls-2" transform="translate(345.49 36.45)">
J
<tspan x="6.23" y="0">a</tspan>
<tspan x="14.22" y="0">w</tspan>
<tspan x="26.51" y="0">s - 44%</tspan>
</text>
修改后的示例:
<text class="cls-2" transform="translate(345.49 36.45)">
Jaws - 44%
</text>
调查中添加一个链接:如果图表基于一个调查,添加一个相关的链接。目前为止(SVG 2不作要求),在href
前添加xlink
.
<a xlink:href="http://webaim.org/projects/screenreadersurvey6/#used">
有关于xlink
的更多信息,请参考Dudley Storey的一篇文章 - 使用SVG进行命名以及XLink。
添加语义化 roles:在包含bars
,label
以及key
的群组中添加语义化role
。这时最好在群组中包含所有的列表,因为大多数的屏幕阅读器都会声明列表中的项目总数以及当前列表项的位置;
<g id="bars" role="list">
内部可选的列表可设置为listitem
:
<g id="Jaws" role="listitem">
在列表中添加一个标签:给AT用户提供一些信息便于互动。在包含列表的群组中添加aria-label="bar graph"
。
<g id="bars" role="list" aria-label="bar graph" transform="translate(0,58)">
测试以及修复:使用屏幕阅读器对其进行测试。不出所料,屏幕阅读器根据标题、desc以及列表项的关键值进行读取显示。当然,也会依据于y轴,每一个矩形以及线。
SVG关于辅助功能中隐藏元素的快速提示 - 在屏幕阅读器中SVG隐藏元素唯一的方式就是添加一个role="presentation"
。这样就会否定可访问API所映射的原本含义。不幸的是,如果你有多个项想要隐藏,你不能将每一个项使用<g>
进行包裹并添加role="presentation"
。那样子产生的结果将是隐藏AT中的元素,它的子元素仍旧可遍历。所以我们需要在每一个我们想要隐藏的可视的元素上添加role="presentation"
。另一方面,新的SVG可访问规范旨在减轻这个负担。元素,如没有alt文本的图形将会被视为没有role或者没有显示。
隐藏图形/线:通过在每一个元素上添加role="presentation"
实现在屏幕阅读器上隐藏图形元素。
隐藏文本元素:通过在每一个元素上添加role="presentation"
以及aria-hidden="true"
实现屏幕阅读器上隐藏令人困惑的文本元素(如:黄色高亮显示的 - y轴上的0-50%,x/y轴的线以及图表中的条形)
一些相关视频:
交互式图像
相较于可访问的图表和图形,更好地是交互式图像。让我们看一个简单的时间线图。突破性在于: 顶部的“title”文本部分和timeline部分。下面,时间轴分解为了标题,图像以及相关描述。
这个示例的灵感来自于codrops,但是我在思考是否可创建一个可访问版本。这个可爱的猫的图标来自于iconka。
让我们添加一些时间段以及一些动画处理。不是一次性显示所有的信息,我们只是显示时间以及相关活动名称。当用户与这两个区域进行交互时就会显示详细的相关信息。
1. 设置文件
首先遵循这篇文章的“设置文件”部分。从网站的优化版本开始,我们先忽略建立的CSS动画部分,深度挖掘使其可访问。
2. 可访问性
为了简化代码,下面示例中均移除了CSS样式,但存在于实际示例中。
屏幕阅读器可遍历 - 在<svg>
中添加role="group"
,确保SVG在所有的浏览器中可遍历。
<svg id="InteractiveSVG" role="group">
标题和说明 - 这个示例中,在<svg>
中添加aria-labelledby
,我们可以在SVG头部<g id="timeline-title">
添加文本作为标题和链接。
<svg id="InteractiveSVG" aria-labelledby="timeline-title" aria-describedby="timeline-desc" role="group">
之后,在<desc>
中添加一个id
,并对应于<svg>
中的aria-describedby
。
<desc id="timeline-desc">An Interactive Timeline</desc>
<svg id="InteractiveSVG" aria-labelledby="timeline-title" aria-describedby="timeline-desc" role="group">
添加语义化roles: 在包含时间线和时间段的组中添加一些语义化的role
。在所有的组中包含列表: <g id="timeline" role="list">
。
在列表中添加标签: <g id="timeline" role="list" aria-label="the timeline, from morning to night">
内部的单个时间段可以设置为listitems: <g id="play" role="listitem">
互动/键盘可访问性: 在每一个具有role="listitem"
的<g>
中添加<a xlink:href></a>
,使其包含整个组。目前,这是唯SVG添加交互性的唯一方式。
添加tabindex="0"
,确保在所有浏览器中获得焦点。
<a xlink:href="#play-group" tabindex="0" id="play-group"></a>
修复链接的语义化:注意这里的链接指向自身。这真的不是一个具有语义化的链接,因为它没有链接到别的地方,同时会使屏幕阅读者产生困惑。所以这里我们添加role="img"
表明其为一个图像而不是一个链接。
<a xlink:href="#play-group" role="img" id="play-group"></a>
使时间段内的文本可访问:添加img
role
,使AT停止其余元素的遍历,我们需要根据文本元素的id
添加aria-labelledby
使其按照原本的顺序被遍历。
<a xlink:href="#play-group" role="img" aria-labelledby="play-time play-text" tabindex="0" id="play-group"></a>
为图像添加隐藏性描述文本:使用具有offscreen
类的<tspan>
可以实现文本的隐藏,但是仍然存在于DOM之中。
<tspan class="offscreen" id="play-description">A gray kitten tangled in a ball of yarn.</tspan>
在具有aria-labelledby
的xlink
上 添加ID表明已读。
<a xlink:href="#play-group" role="img" aria-labelledby="play-time play-text play-description" tabindex="0" id="play-group"></a>
设置焦点CSS样式:当使用键盘进行导航时,需要设置一个可视化的焦点样式。我喜欢这个样式,所以当鼠标处于悬浮状态时,我也添加了相关的样式。
a:focus [class*="time-circle"],
a:hover [class*="time-circle"] {
stroke: black;
stroke-width: 5;
paint-order: stroke;
}
为窗口焦点添加JavaScript:使用SVGs,当你在列表项之间进行导航时,窗口并不总是移动以确保元素出现在视口之中。原因在于一些浏览器(bug已经提交,希望尽快可以得以修复)将<svg>
元素作为一个整体进行滚动而不考虑是否是屏幕外的子元素。所以让我们添加一些JavaScript代码确保滚动窗口时元素均可见。
有许多方式可以实现,这里仅仅是其中一个简单的示例;
$("#play-group").focus(function() {
window.scrollTo(250,350);
});
...
$("#cuddle-group" ).focus(function(){
window.scrollTo(250 , 1350);
});
一些相关视频:
SVGs 和高对比度模式
另外一个有用工具: Windows以及高对比度模式对于低视力患者人群来说,有助于其获取阅读内容。之所以说有用原因在于,功能激活,阅读时文本以及文档的本身会改变颜色,但是当用户选择不同的对比度模式时,SVG元素并不会改变。
图片来源于 http://disney.wikia.com/wiki/File:Alice-facepalm.jpg
好消息: 可以使用媒体查询处理这个问题。
如何修复图标的示例:
@media screen and (-ms-high-contrast: active) {
.icon svg {
/* select a color that will contrast
well on black or white because other
color modes can be chosen and you
need a color that will work with either
*/
fill: green;
}
}
/* black text on white background *.
@media screen and (-ms-high-contrast: black-on-white) {
.icon svg {
/* select a dark color that will
contrast on black
(#fff is too much contrast)
*/
fill: #333;
}
}
/* black text on white background */
@media screen and (-ms-high-contrast: white-on-black) {
.icon svg {
/* select a light color that will
contrast on white
(#000 is too much contrast)
*/
fill: #efefef;
}
}
总结
*确定是否需要可替换文本
- 如果不需要,使用
aria-hidden="true"
隐藏图像/SVG - 若需要:
- 添加/链接到标题和/或者SVG元素的描述
- 使用roles添加语义化值(e.g.
role="list"
,role="listitem"
) - 隐藏不应该被阅读的图形以及group元素w/
role="presentation"
- 使用
role="presentation"
以及aria-hidden="true"
隐藏不应该被阅读的文本元素
- 确认SVG是否可互动
- 若否定 - 什么都不需要做
- 若肯定:
- 使用
xlink + tabindex="0"
设置焦点 - 若链接不存在,添加一个role确保具有语义化
- 添加JS用户window焦点
- 对于
focus: outline
设置可视化CSS
- 使用
- 使用不同的屏幕阅读器以及浏览器进行测试。测试不同的对比度模式。测试键盘导航。
本文根据@HEATHER MIGLIORISI的《Accessible SVGs》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://css-tricks.com/accessible-svgs/。
如需转载,烦请注明出处:https://www.fedev.cn/svg/accessible-svgs.htmlZoom Kobe Venomenon V 5