方案1: link rel=alternate

原文https://www.zhangxinxu.com/wordpress/2019/02/link-rel-alternate-website-skin/

一、大多数开发人员的实现

大多数前端开发人员实现网站皮肤更换功能大致下面两种方法:

  1. 一个全局class控制样式切换;

  2. 改变皮肤link元素的href地址。例如:

    1
    <link id="skinLink" href="skin-default.css" rel="stylesheet" type="text/css">

    换皮肤的时候JS改变href属性值:

    1
    skinLink.href = 'skin-red.css';

    例如我10年前模拟腾讯网首页的换肤功能做的demo页面就是这么实现的。

都不完美。全局class控制样式提高了样式优先级,如果换肤样式很多,代码会非常啰嗦,不利于维护;使用JS改变href属性会带来加载延迟,样式切换不流畅,体验不佳。

实际上,浏览器有原生特性,非常适合实现网站换肤功能。

二、原生HTML特性下的网站换肤

此方法借助HTML rel属性的alternate属性值实现。示意HTML如下:

1
2
3
4
5
<link href="reset.css" rel="stylesheet" type="text/css">

<link href="default.css" rel="stylesheet" type="text/css" title="默认">
<link href="red.css" rel="alternate stylesheet" type="text/css" title="红色">
<link href="green.css" rel="alternate stylesheet" type="text/css" title="绿色">

上面4个<link>元素,共出现了3中不同性质的CSS样式文件加载:

  • 没有title属性,rel属性值仅仅是stylesheet的<link>无论如何都会加载并渲染,如reset.css;
  • 有title属性,rel属性值仅仅是stylesheet的<link>作为默认样式CSS文件加载并渲染,如default.css;
  • 有title属性,rel属性值同时包含alternate stylesheet的<link>作为备选样式CSS文件加载,默认不渲染,如red.css和green.css;

这里有个非常有趣的特性,那就是rel="stylesheet"<link>如果有title属性并有值,性质上就变成了一个可以控制其渲染或者不渲染的特殊元素了。

如何控制呢?

一种说是浏览器自己会有样式切换菜单,查看→页面,选择title属性值对应的样式。我猜这是10年前的说法了吧,现在浏览器根本没有这些菜单选项,至少我找了好久,各个浏览器都找了个遍没找到。

另外一种就是使用JS进行控制了,使用JavaScript代码修改<link>元素DOM对象的disabled值为false,可以让默认不渲染的CSS开始渲染。注意,必须是DOM元素对象的disabled属性,而不是HTML元素的disabled属性,<link>元素是没有disabled属性的。

例如:

1
2
// 渲染red.css这个皮肤
document.querySelector('link[href="red.css"]').disabled = false;

因此,要实现换肤功能,只要在页面上方几个换肤按钮,点击的时候改变对应<link>元素DOM对象的disabled值就可以了。

眼见为实,我专门做了demo页面,您可以狠狠地点击这里:link rel alternate实现网站换肤功能demo

结果如下GIF截图所示(截自IE浏览器):

换肤效果示意

demo页面是使用单选框模拟实现的,HTML和JS代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<input id="default" type="radio" name="skin" value="default.css" checked>
<input id="red" type="radio" name="skin" value="red.css">
<input id="green" type="radio" name="skin" value="green.css">
var eleLinks = document.querySelectorAll('link[title]');
var eleRadios = document.querySelectorAll('[type="radio"]');
[].slice.call(eleRadios).forEach(function (radio) {
radio.addEventListener('click', function () {
var value = this.value;
[].slice.call(eleLinks).forEach(function (link) {
link.disabled = true;
if (link.getAttribute('href') == value) {
// 该样式CSS文件生效
link.disabled = false;
}
});
});
});

3大优点:

  1. 兼容性非常好。IE9+(IE8我没测,理论也支持),Chrome和Firefox均支持这种更原生的换肤效果实现。
  2. 语义非常好。用户,开发者,尤其搜索引擎或者其他辅助阅读设备能够准确识别网站还有其他替换CSS样式。(alternate的语义就是可替换的)
  3. 交互体验更好。rel=alternate方法实现的换肤功能在网站样式变换的时候是瞬间切换,完全无感知。因为浏览器已经把换肤的CSS文件预加载好了,比JS改变href地址的体验要更好。配合http2.0,几乎可以说是完美无瑕的解决方案了。

为什么之前没有人提过这个方法?

此方法我也是最近在学习rel属性值的时候知道的,看到MDN上关于Alternative_style_sheets文档学到的,之前并不知晓还有这么给力的实现。

Firefox4就开始支持这一特性,说明此方法很久远了,我搜索了下,还是有少数前辈知道并使用这种方法换肤的,但是,至少对于新人前端(如果按照我在例会上的统计)几乎无人知晓。

什么原因造成这种现象呢?

首先,换肤是小众需求;
其次,有符合常规认知的替换方案,最终效果也能接受;
然后,都在学习高大上的东西,什么HTML基础知识根本无人问津;
最后,缺少有影响力的的入口进行科普。

酒香也怕巷子深,好的技术实现也要多多曝光才能让更多人知道,提高整个行业的水平。所以如果你觉得此方法确实不错,可以转发分享让更多人知道。

最后,感谢你的关注和阅读!