link rel=alternate网站换肤功能最佳实现

这篇文章发布于 2019年02月25日,星期一,23:31,归类于 Web综合。 阅读 50521 次, 今日 5 次 47 条评论

 

换脸换肤占位图

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

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

  1. 一个全局class控制样式切换;
  2. 改变皮肤link元素的href地址。例如:
    <link id="skinLink" href="skin-default.css" rel="stylesheet" type="text/css">

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

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

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

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

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

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

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

<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属性的。

例如:

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

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

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

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

换肤效果示意

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

<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;
            }
        });
    });
});

三、link rel=alternate方法实现优点

3大优点:

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

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

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

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

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

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

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

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

(本篇完)

分享到:


发表评论(目前47 条评论)

  1. 来自银河系的光说道:

    张大大牛逼,我可是你的十年老粉了,大学学CSS的时候就是看你的博客

  2. 皇大大说道:

    是啊 都在搞看起来高级东西,说起来牛逼的东西。

  3. geo说道:

    都在学习高大上的东西

  4. 一拳超人说道:

    大佬 怎么结合vue使用呢

    • 皇大大说道:

      这和 vue 有什么关系 ? 你用啥前端框架都行啊, 框架只是在基础上封装了一层。

  5. z0nka1说道:

    为什么我去掉alternate也一样可以呢?

  6. 大哥你玩摇滚说道:

    为什么要先true一下,才会生效呢

  7. 少林功夫好耶说道:

    张老师,在link rel alternate实现网站换肤功能demo的代码里,为什么要在eleRadios和eleLinks 遍历之前用slice方法将其值给到新数组啊,用本身遍历有什么影响吗

    • 大海说道:

      昨天正好看到张老师这篇文章,也有和你一样的问题,经过研究后整理成文章,不知半年后的你还是否需要这份答案:https://wy310.cn/2019/12/20/%e7%90%86%e8%a7%a3array-prototype-slice-call/

    • 小溪说道说道:

      NodeList是类数组,没有forEach方法.
      slice方法本意是从一个数组里截取返回一个新数组,eleRadios和eleLinks通过call借用数组的slice方法,将 eleRadios和eleLinks 转换成了数组,这时候就可以使用forEach了.

      这种实现方式很多,大部分情况下没必要聊什么性能,for循环也可以,ES6 中的reset/Array.from(xxx) 都可以,这样逼格高一点,并且是ES5写法,兼容性也更好

      • 白懂说道:

        高版本浏览器中Nodelist的实例有forEach方法,https://developer.mozilla.org/zh-CN/docs/Web/API/NodeList。我猜这个写法是为了在ie中测试,文中指出这种换肤可以兼容ie9+

  8. 泽泽说道:

    之前必须先true一下吧,测试上来就disabled false没有效果,参考你的这个文章做了个博客夜间模式

  9. 铭铭说道:

    好像没什么用啊,用了alternate并不会在一开始的时候全部加载,只会在需要的时候再加载,那跟我用js更换href地址有什么分别???

  10. Jazmin说道:

    2月21号面试的时候还被问到了换肤的思路~

  11. ...说道:

    作为一名普通网民,可是知道了。
    IE中:Alt+V——样式——…

    • ...说道:

      火狐也有同样的菜单:Alt+V — 页面样式(Y)

      不过谁会看到这个呢?何况 Android,iOS 有哪个有菜单栏的?!

  12. [].slice.call说道:

    为什么这么写呢? 直接eleRadios.forEach可以吗

  13. vergilbeats说道:

    这广告做的十分高大上了,好评

  14. 刘宁说道:

    很少做到这功能

  15. arnold说道:

    可以,很sao

  16. 新疆小鬼说道:

    崇拜,支持一下

  17. mt说道:

    最大缺点是没应用也下载。

  18. Orange说道:

    可以 挺不错的
    总结一下:
    title属性是为了选择到要替换的背景样式元素
    alternate 相当于告诉浏览器是备用的样式 其实也可以 不使用 直接在alternate的样式用disabled属性禁用了

  19. storm说道:

    和体验相比,明显流量更值钱,都加载好了几个样式文件,浪费了多少流量。

    • mfk说道:

      手机端确实流量比较重要。电脑端可以用下。
      而且可以懒加载+缓存。不影响首屏渲染(页面加载完后再用js添加这种alternative的css)。

  20. 我来看看说道:

    性能上的话,会不会有影响呢?如果皮肤比较多,并且可配置的情况下,是否可行呢

  21. someone说道:

    这里有个非常有趣的特性,那就是rel=”stylesheet”的如果有title属性并有值,性质上就变成了一个可以控制其渲染或者不渲染的特殊元素了。
    ——————————————
    这句话的含义是啥,没看懂

  22. 就这样吧说道:

    个人觉得写在同一css,结合less、sass等更方便管理和修改,通过改变body的class来换肤,免得开几个文件来改,仅供参考。

  23. Better说道:

    又涨知识了

  24. 土豆土豆啊说道:

    get到了~

  25. gdh1995说道:

    补充一下,.disabled和.disabled已经是deprecated了,规范建议用.sheet.disabled 和 .sheet.disabled ,不过.sheet应该是需要等CSS资源加载好,才不是null,所以不很方便

    https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_link_element.idl?l=26&rcl=0f2f1bf9845aa76f05d82113365d898b10613e39

    • 而井说道:

      .sheet是CSSStyleSheet类型,IE9+开始支持,如果要兼容IE8的话,还是用.disabled目前兼容性会好些,当然也可以检测一下是否支持CSSStyleSheet来决定逻辑

  26. 依韵说道:

    补充: IE8下此方案可行

  27. 芬达说道:

    学习了

  28. silence说道:

    有影响力的入口lei le~

  29. 陈纪庚说道:

    可以,学习了

  30. 土豆说道:

    涨姿势了,之前有个需求换肤,就是通过改变最外层ciass控制