制作无障碍的星型评级控件

原文:Developing an Accessible Star Ratings Widget

作者:Thierry Koblentz

先睹为快,跳到演示页

许多电子商务网站、社交网络服务以及网上社区都包括评级或评估的功能。征集用户的意见已经成为一种商业模式,现在有许多网站致力于评价产品、服务、企业或其他。

最常见的显示投票的界面是“星级系统”,每个评论者给评审项目赋予一定数量的分值(通常以星表示)。很多网站都使用了这种模式,无论Amazon还是Yelp。

Examples of star rating systems

图A     Amazon 和 Yelp 的星型评级

正如图A所示,两者的视觉界面类似,但使得这两种方案变得有趣的是它们使用的基本标记。一个使用的是<map> ,另一个是<img> 。

你也许会认为大多数评级系统所使用的标记是具有语义的,并在多种用户代理下都可以操作 – 也就是说,这种评级系统应当基于一组特定的HTML元素和属性集来构造,使用JS和CSS来控制行为和样式。如果是这样的话就好了,但事实远非如此。作者使用的标签可谓是千奇百怪:

  • <a>,
  • <img>,
  • <span>,
  • <li>,
  • <map>,
  • <div>,
  • <input>,
  • and more…

微格式(Microformats)案例

在介绍几个基于图像的标记评级的技术之前,我认为值得提一下一个基本的和直接的方法( 来自微格式 ),使用字符 :

<abbr class="rating" title="3 stars">***</abbr>

优点

  • 它是直接的并具有语义。
  • 标记是最小的。
  • 该方法不依赖于CSS 。
  • 该方法不依赖于图像。
  • 没有HTTP请求。

缺点

  • 无法表示半数值(比如3.5星)
  • 只表示星号(“星级”)
  • 默认情况下,屏幕阅读器不能展开缩写(在这种情况下可能不是一个大问题)

注 :我选用“*”,而不是★(★),因为屏幕阅读器(至少JAWSNVDA )似乎忽略HTML实体。

基于图片的评级标记

当涉及到显示图片,作者有很多选择。

每一个级别使用一张图片

使用一张图片:

<img src="4stars.png" alt="4 out of five">
一星
1 out of five
两星
2 out of five
三星
3 out of five
四星
4 out of five
五星
5 out of five

优点

  • 每一个级别使用一张图片是直观的和具有语义。
  • 该方法不依赖于CSS 。
  • 最小的标记。

缺点

  • 它会创建多个HTTP请求,因为有多张不同的图片。
  • 除了性能问题,对于维护来说也是一场噩梦,需要耗费作者更多的资源(图片制作、图片投递到CDN、网站颜色改变时修改图片等)。
  • Opera下,文本选择(Text selection )是不可能的 (至少在版本9.52下)因为替代文本会被忽略。
一组使用一张图片

来自whatwg的工作草案

<img alt="4 out of 5" src="one-star.png">
<img alt="" src="one-star.png">
<img alt="" src="one-star.png">
<img alt="" src="one-star.png">
<img alt="" src="no-star.png">
一星
1 out of five
二星
2 out of five
三星
3 out of five
四星
4 out of five
五星
5 out of five

优点

  • 每次评级使用两个img元素减少了HTTP请求数量。
  • 该方法不依赖于CSS 。

缺点

  • 在Opera下,当图片被禁用时, 替代文本是不可选取的,以及(在小屏幕视图下),文字会带有边框(border)致使其清晰度降低。

请注意这个例子来自一个有争议的工作草案 。在我看来,这种方法是不能接受的,因为替代文字没有准确、简明地描述图像。此外,如果这种方法的依据是这些图片代表内容,那么为什么不给他们添加替代文本呢?

Ajaxian为例,作者为每一张图片都添加了替代文本,如果他认为每张图片都是内容的话,这将很有意义 :

<img [snip] alt="+" src="star1.png"/>
<img [snip] alt="+" src="star1.png"/>
<img [snip] alt="+" src="star1.png"/>
<img [snip] alt="-" src="star0.png"/>
<img [snip] alt="-" src="star0.png"/>

在任何情况下,使用多张图片与使用单一元素(img或者其他)相比,具有便于投票的优点——用户选择其中一颗星便完成投票。因此,我们应该记住这一点…

背景图片sprite

下面这个技术改编自一个技巧,最初由Yahoo! Music的开发人员实现:

标记

<span class="rating r1 stars">1 of 5</span>
<span class="rating r2 stars">2 of 5</span>
<span class="rating r3 stars">3 of 5</span>
<span class="rating r4 stars">4 of 5</span>
<span class="rating r5 stars">5 of 5</span>

CSS

.stars {
  background: transparent url(img/sprite.png) no-repeat;
}
.rating {
  font-size: 0;
  height: 19px;
  overflow: hidden;
  vertical-align: middle;
  width: 96px;
  display: block;
}
.r1 { background-position: -385px 0; }
.r2 { background-position: -288px 0; }
.r3 { background-position: -192px 0; }
.r4 { background-position: -96px 0; }

译者注:sprite图

优点

  • 这种方法只需要一个HTTP请求,因为它依赖一张sprite图片。
  • 最小的“脚印”(foot print)。

缺点

  • 在图片禁用时,无法揭示内容。
  • 页面打印时,无法显示评分(打印样式表需要考虑这个问题)。
  • 在Opera中,高对比度样式使所有的星星消失,高对比度模式优化(High Contrast Mode Optimization.)一文得到同样的结论 。
  • 文本可以被选取,但它并不明显(通过高亮显示)。
标记中的sprite

这种方法是基于TIP方法 ,即用一个sprite图片作为<img>元素而不是背景图片:

标记

<span title="1 of 5" class="rating r1"><img width="0" height="1" src="sprite.gif" alt=""/>1 out of 5</span>
<span title="2 of 5" class="rating r2"><img width="0" height="1" src="sprite.gif" alt=""/>2 out of 5</span>
<span title="3 of 5" class="rating r3"><img width="0" height="1" src="sprite.gif" alt=""/>3 out of 5</span>
<span title="4 of 5" class="rating r4"><img width="0" height="1" src="sprite.gif" alt=""/>4 out of 5</span>
<span title="5 of 5" class="rating r5"><img width="0" height="1" src="sprite.gif" alt=""/>5 out of 5</span>

CSS

.rating {
  position: relative;
  height: 1.6em;
  width: 8.1em;
  overflow: hidden;
  vertical-align: middle;
  display: block;
}
.rating img {
  position: absolute;
  width: 40.5em;
  height: 1.55em;
  top: 0;
  border: 1px solid #fff;
}
.r1 img { right: 0; }
.r2 img { left: -24.4em; }
.r3 img { left: -16.2em; }
.r4 img { left: -8.1em; }

优点

  • 这种方法只需要一个HTTP请求。
  • 这种技术是上面四种方法中唯一一个,在Firefox用户选择“隐藏图片”或“使图像不可见”(从开发者的工具栏)时, 仍能揭示内容。
  • 当图像不可用时,一个红色的“X”仅出现在最高等级(即5/5),而不是在每一个等级上,因为它和其他解决方案一样依赖img元素。

缺点

  • 这些图像的显示依赖于CSS 。

值得注意的是,它不像其他的图片替换技术,此方法允许:

  • 根据文本大小(text-size)缩放图片。
  • 图片可以被打印。
  • 当整幅图片高亮显示时,替代文字可以很容易地选定(火狐)。
  • 图像在高对比度设置/样式表下不会消失。
  • 在Opera下,替代文字可被选择(当图片被禁用时)。
  • 在Opera小屏幕视图下,替代文本没有边框(borderless)。

投票标记

机制

为了投票 ,我们需要一个低级别的投票机制,允许普通用户选择和提交。为此,我们可以使用表单:

标记

<fieldset>
  <legend>Rating</legend>
  <label><input type="radio" name="movie" value="1_5">1/5</label>
  <label><input type="radio" name="movie" value="2_5">2/5</label>
  <label><input type="radio" name="movie" value="3_5">3/5</label>
  <label><input type="radio" name="movie" value="4_5">4/5</label>
  <label><input type="radio" name="movie" value="5_5">5/5</label>
</fieldset>

结果

为了更好的可读性,我们添加<br>和空格。

标记

<fieldset>
  <legend>Rating</legend>
  <label><input type="radio" name="movie" value="1_5"> 1/5</label><br>
  <label><input type="radio" name="movie" value="2_5"> 2/5</label><br>
  <label><input type="radio" name="movie" value="3_5"> 3/5</label><br>
  <label><input type="radio" name="movie" value="4_5"> 4/5</label><br>
  <label><input type="radio" name="movie" value="5_5"> 5/5</label>
</fieldset>

结果

在标记中引入sprite图片

对于这个解决方案,我们使用一个比上面例子更小的sprite。是由两个星星组成 (“on”和“off”)。

我们把img元素放在label标签内。我们假定当不支持css时,img不具有属性值,因此我们通过设置特定的尺寸(width和height属性)来隐藏它们。请注意,将两个属性值都设为0在有些用户代理下,会出现一个borken图片。


<form ...>
  <fieldset>
    <legend>Rating</legend>
    <label class="one" title="1 out of 5"><input name="LandOf" value="1" checked="checked" type="radio"> 1/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
    <label class="two" title="2 out of 5"><input name="LandOf" value="2" type="radio"> 2/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
    <label class="three" title="3 out of 5"><input name="LandOf" value="3" type="radio"> 3/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
    <label class="four" title="4 out of 5"><input name="LandOf" value="4" type="radio"> 4/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
    <label class="five" title="5 out of 5"><input name="LandOf" value="5" type="radio"> 5/5<img src="star-sprite.gif" alt="" height="0" width="0"></label>
  </fieldset>
</form>

请注意,使用上面的标记,(在大多数浏览器)下我们通过选择label可以选择field。

无障碍考虑

不幸的是 ,这种标记至少在两个屏幕阅读器下存在问题: JAWSNVDA (见这个bug的测试案例 )。这个问题与使用title属性和空字符串替代文本相关。

不使屏幕阅读器用户感到迷糊的解决方法是使用“星”(stars)作为替代文本( alt ),并在 鼠标悬停时使用JavaScript插入title。

更好的标记
<fieldset>
  <legend>Rating</legend>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="1_5"> 1/5</label><br>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="2_5"> 2/5</label><br>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="3_5"> 3/5</label><br>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="4_5"> 4/5</label><br>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="5_5"> 5/5</label>
</fieldset> 

结果

样式化

通过CSS设置图像尺寸

我们使用em ,使图片根据字体大小(font-size)放大或缩小。

标记

不变

CSS

img {
  width:2.8em;
  height:1.4em;
}

结果

正如您所看到的,点击一张图片时对应的单选按钮也被选中。这种行为无需使用添加隐式标记(implicit labeling)的脚本就可以实现(IE除外)。

从流中删除图像

为label设置position:relative并给图片设置position:absolute、top/left值就可以将input和文字隐藏在label内。

标记

不变

CSS

label {
  position:relative;
}
img {
  width:2.8em;
  height:1.4em;
  position:absolute;
  top:0;
  left:0;
}

结果

每个label显示一颗星

我们将label的尺寸设为一颗星的高度和宽度。

标记

不变

CSS

label {
  position:relative;
  height:1.4em;
  width:1.4em;
  overflow:hidden;
  display:block;
}
img {
  width:2.8em;
  height:1.4em;
  position:absolute;
  top:0;
  left:0;
}

结果

水平方式显式星星

我们移除br 并将label浮动。

标记

不变

CSS

br {
  display:none;
}
label {
  position:relative;
  height:1.4em;
  width:1.4em;
  overflow:hidden;
  display:block;
  float:left;
}
img {
  width:2.8em;
  height:1.4em;
  position:absolute;
  top:0;
  left:0;
}

结果

根据评级显式sprite图片
如果要设置三星级,我们对最后两个label应用一个相同的类。这个class用来改变图片在label中的位置。

标记

<fieldset>
<legend>Rating</legend>
<label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="1_5"> 1/5</label><br>
<label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="2_5"> 2/5</label><br>
<label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="3_5"> 3/5</label><br>
<label class="no_star"><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="4_5"> 4/5</label><br>
<label class="no_star"><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="5_5"> 5/5</label>
</fieldset>

CSS

br {
  display:none;
}
label {
  position:relative;
  height:1.4em;
  width:1.4em;
  overflow:hidden;
  float:left;
}
img {
  width:2.8em;
  height:1.4em;
  position:absolute;
  top:0;
  left:0;
}
.no_star img {
  left:-1.4em;
}

结果

不要单独依赖图片传递信息

当图片无法获取时,为星星设置替代文本是至关重要的。这是因为label和单选按钮的样式被设为重叠了。一个简单的解决方法是将input和文本移出屏幕(比如使用text-indent:-999em )和给label设置背景颜色。

标记

没有变化

CSS

br {
  display:none;
}
label {
  position:relative;
  height:1.4em;
  width:1.4em;
  overflow:hidden;
  float:left;
  background:teal;
  margin-right:1px;
  text-indent:-999em;
}
img {
  width:2.8em;
  height:1.4em;
  position:absolute;
  top:0;
  left:0;
}
.no_star {
  background:#ccc;
}
.no_star img {
  left:-1.4em;
}

注意 :

text-indent还修复了一个每当控件获得焦点时图片向上跳的问题。

右边距(margin-right)是为了确保背景颜色呈正方形而不是长方形(相邻的label使用相同的背景颜色时会出现这种情况)。

结果

画龙点睛

用伪类:hover创造出翻转效果,

隐藏fieldset的边框(border),

隐藏legend,

为光标(cursor)设置样式。

标记

不变

CSS

br {
  display:none;
}
label {
  position:relative;
  height:1.4em;
  width:1.4em;
  overflow:hidden;
  float:left;
  background:teal;
  margin-right:1px;
  text-indent:-999em;
}
input {
  position:absolute;
  left:-999em;
  top:.5em;
}
img {
  width:2.8em;
  height:1.4em;
  position:absolute;
  top:0;
  left:0;
  cursor: pointer;
}
.no_star {
  background:#ccc;
}
.no_star img {
  left:-1.4em;
}
label:hover {
  opacity:.5;
  filter:alpha(opacity=50);
}
fieldset {
  border:0;
}
legend {
  text-indent:-999em;
}

注:IE6会忽略label:hover以及在Opera下背景颜色会从图片中出血。在演示页,我使用了另一张含有四颗星的sprite而不是使用不透明度(opacity)。

结果

展示评级且用户不可交互

我们可以通过为对应的input添加disabled和checked属性,使评级“只读”。

标记

<fieldset>
  <legend>Rating</legend>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="1_5" disabled> 1/5</label><br>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="2_5" disabled> 2/5</label><br>
  <label><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="3_5" checked="checked"> 3/5</label><br>
  <label class="no_star"><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="4_5" disabled> 4/5</label><br>
  <label class="no_star"><img src="img/small-sprite.gif"  width="0" height="1" alt="stars"><input type="radio" name="movie" value="5_5" disabled> 5/5</label>
</fieldset> 

CSS

:hover规则已被删除

结果

更多思考

在这一点上,无需脚本支持就可完成投票,但对明眼用户来说对于他们的选择没有提示。因此,我们使用JavaScript来:

将用户的选择反馈给他们

为键盘用户提供视觉提示,便于他们在单选按钮之间导航。

与此同时,我们使用脚本增加title属性,当用户悬停在label或星星上时会出现工具提示(tooltips)。

由于不使用JavaScript会缺乏对选择的反馈,如果有脚本支持,我们可以为label和表单控件设置样式。为此,我们使用JavaScript在html元素上设置一个标志(flag),然后我们创建一个规则,基于包含那个钩子(hook)的后代选择器。如果不含有标志,这条规则就不应用,并且元素不被设置样式。

这是演示页 ,最终的作品。要了解这个解决方案根据不同设置是怎样表现的,您可能要使用你最喜欢的开发工具,来增加文字大小,更改图片路径,禁用JavaScript,关闭CSS等等…

总结

提出一个“可接受”的解决办法,需要识别用户的需求,用户代理的特殊性、用户代理的设置等 – 这意味着需要广泛的测试。

在这个过程中,用户的反馈非常重要,因为下面的最佳做法并不一定总是正确的。例如,如前面提到的,为label标签内的图片设置空值的title属性似乎是安全的,但事实证明,它至少在两个屏幕阅读器下存在问题(见测试案例)。

此外,辅助设备使用用户的建议是可以忽略一些验证错误信息——Firefox Accessibility 工具栏报告(根据http://bestpractices.cita.uiuc.edu/html/nav/form/ )。

本文的目的并不是解决一切问题。我的工作重点之一是能够不使用指点设备完成投票,改善解决方案在opera禁用图片时的外观和感觉并不是我的重点。

这个“征途”最有趣的部分是使解决方案对不同条件的用户无障碍,解决以下问题:

  • 图像禁用,
  • JavaScript禁用,
  • CSS禁用,
  • 以上的组合。

这个技术使用img元素,而不是背景图片,这使得星星可以:

  • 根据用户的设置调整大小,
  • 在高对比度模式下显式,
  • 在默认情况下打印(不同于背景图片)。

所有这一切都没有牺牲性能,因为这个解决方案只使用了一张sprite图片:stars

之后发现

我最近发现亚马逊公司为其投票页面建立的系统。有趣的是他们根据是否支持脚本提供了不同的解决方案。如果支持脚本,他们使用一个图像<map>(有趣的方法),如果不支持脚本则使用单选按钮 。在这两种情况下,解决办法对键盘用户都是无障碍的 ,这有助于最大限度地使用一个功能,这是亚马逊平台的核心区别。

请注意,他们并不使用JavaScript将单选按钮用<map>代替,而是使用noscript元素包含单选按钮。

“开箱即用”的解决方案

Dreamweaver®
Spry Rating Widget
YUI
Star Rating Script for YUI
Star Rating script with YUI
JQuery
Half-Star Rating Plugin
jQuery Ajax Rater
Simple Star Rating System
5 star rating system in PHP, MySQL and jQuery
WordPress
GD Star Rating System for WordPress
GD Star Rating
Star Rating for Reviews
Flash
5 Star rating system component
Misc.
How a star rating should be
Starry widget 2

特别感谢

特别感谢Victor Tsaran和Todd Kloots提出的宝贵意见。

《制作无障碍的星型评级控件》有2个想法

  1. 我也写过一个类似的文章.
    介绍得很到位, 不过 sprite 有点蛋疼, 其实前五颗亮的, 后五颗灭的就足够了.

发表评论