09月30, 2017

CSS中:visited链接伪类的怪癖

在对锚点元素 a 设置样式时,一般我们都知道 Love or hate 规则,即必须按照如下的顺序写选择器:

a { ... }
a:link { ... }
a:visited { ... }
a:hover { ...  }
a:active { ... }

背后的原理其实很简单,即按照CSS层叠规则,后出现的样式会覆盖先出现的样式。这里我就不多说了。

原本以为记住 Love or hate 规则就没啥问题了,今天被同事一问,才发现还有东西没搞清楚呢。

问题是这样的,假如有如下HTML代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    a {
      font-size: 3em;
    }
    a:link {
      color: red;
    }
    a:visited {
      color: blue;
      background-color: red;
      font-size: 30px;
    }
    a:hover {
      color:black;
      background-color: gray;
      font-size: 30px;
    }
    a:active {
      color:orange;
      background-color: blue;
      font-size: 30px;
    }
  </style>
</head>
<body>
  <a href="#">Hello</a>
</body>
</html>

意图是让访问过的链接背景色变为红色,字体大小变为30px。然而,打开浏览器预览,点击链接之后,结果却是如下这样子:

alt

访问过的链接的背景色和字体大小设置都不起作用,而字体颜色设置却起作用了。

然而,如果在 a 或者 a:link规则中设置background-color,那么a:visited规则中设置的background-color就会起作用。不过,即使在 a 或者 a:link规则中设置了font-size,a:visited规则中的font-size也不会起作用。而对于a:hover和a:active规则却没有这些奇怪的现象。

带着疑问,打开了CSS3选择器规范:Selectors Level 3。有关链接伪类的也只有一小段:

alt

其中有一段话引起了我们的注意:

注意:样式表的作者可能会在没有用户同意的情况下,滥用 :link 和 :visited 伪类,以判断用户访问过哪些网站。因此,用户代理可能会把所有链接当作未访问的链接,或者实现其它措施,以保护用户隐私,同时以不同方式渲染访问过的以及未访问过的链接。

难道这玩意还跟用户隐私保护有关系?可是规范也太简单了,根本没法理解。去 MDN 查一下,看有没有详细解释。在 MDN CSS参考指南中对 :visited 的解释中发现了一段话:

alt 中文翻译过来就是:

注意:出于隐私的考虑,浏览器严格限制用此伪类选择的元素应用的样式:只能应用color, background-color, border-color, border-bottom-color, border-left-color, border-right-color, border-top-color, outline-color, column-rule-color, fill 和 stroke。还要注意,alpha 组件会被忽略,会用非visited规则的alpha组件替换(除了透明度为 0 时,此时整个颜色都被忽略,并用非 visited规则之一)。

虽然颜色可以被修改,不过方法 getComputedStyle 会撒谎,总是返回非 visited 颜色值。 前一句的意思还能明白,就是:a:visited 规则上只能设置上面指出的几种属性,font-size 不在其中,所以在 a:visited 规则上设置 font-size 是不起作用的。好了,这个明白了。

不过,明明是允许设置 background-color,为嘛一定要在 a 或者 a:link 上设置了 background-color 后,a:visited 上设置的 background-color 才会生效呢?是不是跟这个什么 alpha 组件有关系呢?

这个 alpha 组件是什么意思呢?我们知道CSS3中颜色可以用RGBA表示法。其中,R代表红色值,G代表绿色值,B代表蓝色值,而A代表alpha值。这个alpha值用来表示颜色的透明度,取值是0到1之间,0代表透明,1代表不透明。那么“alpha 组件会被忽略,会用非visited规则的alpha组件替换(除了透明度为 0 时,此时整个颜色都被忽略,并用非 visited规则之一)” 的意思就是:如果在:visited规则上设置的颜色透明度为0,即transparent,那么就会用未访问过的规则完全替换它;如果颜色透明度不为0,那么就会用未访问过的规则的颜色透明度来替换这个alpha值。

我们用代码来证明一下。设置四个链接,分别设置未访问过的链接的颜色透明度为0,0,1,1,对应的访问过的链接的颜色透明度0,1,0,1。完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    a:link.a1 {
      background-color: rgba(0,1,0,0);
    }
    a:visited.a1 {
      background-color: rgba(255,0,0,0);
    }
    a:link.a2 {
      background-color: rgba(0,255,0,0);
    }
    a:visited.a2 {
      background-color: rgba(255,0,0,1);
    }
    a:link.a3 {
      background-color: rgba(0,255,0,0.2);
    }
    a:visited.a3 {
      background-color: rgba(255,0,0,0);
    }
    a:link.a4 {
      background-color: rgba(0,255,0,1);
    }
    a:visited.a4 {
      background-color: rgba(255,0,0,1);
    }
  </style>
</head>
<body>
  <a href="#" class="a1">Hello1</a>
  <a href="#" class="a2">Hello2</a>
  <a href="#" class="a3">Hello3</a>
  <a href="#" class="a4">Hello4</a>
</body>
</html>

在浏览器中预览一下,点击各个链接一次,最后效果如下:

alt

结果表明:

1)如果未访问过的链接的颜色是透明的,那么访问过的链接的颜色也一定是透明的,所以链接a1、a2的背景色是透明的。

2)如果未访问过的链接的颜色是不透明的,而访问过的链接的颜色是透明的,那么原本访问过的链接背景色是不会出来的,但是因为alpha值会被未访问过的链接的alpha值替换,所以背景色也出来了(注意:FireFox会用未访问过的链接的RGB完全替换访问过的链接的RGB,而Chrome、Safari依然采用访问过的链接的RGB)。

3)如果未访问过的和访问过的链接的颜色都是不透明的,那么采用的是访问过的链接的RGBA中alpha值会被未访问过的链接的RGBA的alpha值替换,RGB保持不变。

至此问题就都搞清楚了。

总结:

1)浏览器根据隐私策略,会限制:visited链接伪类样式规则中出现的属性,只有下列的属性才能被应用到已访问链接:

  • color
  • background-color
  • border-color (及其子属性)
  • outline-color
  • fill 和 stroke 属性的颜色部分

2)如果要给已访问链接添加颜色属性,未访问链接必须添加对应的颜色属性,并且其透明度不能为0。而已访问链接的最终颜色,跟浏览器(FireFox与Chrome、Safari、IE有所不同)的处理方式有关,详见上述解释。

本文链接:http://www.xiaojichao.com/post/visited.html

-- EOF --

Comments