基于 link alternate 的一键换肤方案

703 阅读3分钟

背景

很多网站都有一键换肤的功能非常炫酷,多主题切换针对不同的系统也很方便,本文来探讨下基于 vue 的方案

开胃菜--暗黑模式

image.png

自从 macOS的暗黑模式 Dark Mode 出现以后就火起来了,具体效果如下

dark.gif

核心依赖就是 prefers-color-scheme 实践代码如下

@media (prefers-color-scheme: dark) {
    body { background: #333; color: white; }
}
/* 浅色模式 */
@media (prefers-color-scheme: light) {
    body { background: white; color: #333; }
}

css 级联变量 variables

mdn 上的自定义属性(有时候也被称作CSS变量或者级联变量)是由CSS作者定义的。声明一个自定义属性,属性名需要以两个减号(--)开始,属性值则可以是任何有效的CSS值。

<template>
    <div>
        <header id="header">
            <h1>我换肤了啊😄</h1>
        </header>
        <button id="btn-red">red</button>
        <button id="btn-black">black</button>
        <button id="btn-blue">blue</button>
    </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  mounted () {
       const btns = document.querySelectorAll("button");
       const btnArray = Array.from(btns);
       btnArray.forEach( node => {
           node.addEventListener("click",function(e){
                var ele = e.target||e.srcElement;
                var styles = getComputedStyle(ele);
                var value = styles.getPropertyValue("--"+ele.id);
                document.documentElement.style.setProperty("--theme-color",value);
            })
       })
  },
  created () {
  }
}
</script>

css 部分

:root {
    --theme-color: #989898;
}
#header {
    width: 100%;
    height: 50px;
    line-height: 50px;
    background-color: var(--theme-color);
    margin-bottom: 30px;
}
#header h1 {
    color: #fff;
}
button {
    width: 100px;
    height: 30px;
    color: #fff;
    border: none;
}
#btn-red {
    --btn-red:  #ff9a9e;
    background-color: var(--btn-red);
}
#btn-black {
    --btn-black: #a1c4fd;
    background-color: var(--btn-black);
}
#btn-blue {
    --btn-blue: #a18cd1;
    background-color: var(--btn-blue);
}

效果

colors.gif
核心就是去设置 document.documentElement.style.setProperty("--theme-color",value);

css in js

对于大多数 vue 的开发人员来说不是很友好,但也不失位一种思路,核心依赖是 styled-components 的语法 具体代码如下

import styled from 'vue-styled-components';
const btnProps = { primary: Boolean };
const StyledButton = styled('button', btnProps)`
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
  background: ${props => props.primary ? '#a1c4fd' : 'white'};
  color: ${props => props.primary ? 'white' : '#a1c4fd'};
`;
export default StyledButton;
<s-button>我是一个普通的按钮</s-button>
<s-button primary>我是一个主题色按钮</s-button>

效果就不放了,可以看出有点类 jsx 写法。 对于 react 用户挺友好 配合 styled-components提供ThemeProvider 来切换主题,效果非常好

link alternate (重点)

方法借助HTML rel属性的alternate属性值实现

<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="blue.css" rel="alternate stylesheet" type="text/css" title="蓝色"

通过上面我们可以看出有 title 和 无 title 以及 alternate 的区别

无 title有 titlealternate
无论如何都会加载并渲染默认样式CSS文件加载并渲染备选样式CSS文件加载,默认不渲染
reset.cssdefault.cssred.css、blue.css

接下来放下实战

<template>
    <div>
        <header id="header">
            <h1>我换肤了啊😄</h1>
        </header>
        <button id="btn-red" @click="change('红色')">red</button>
        <button id="btn-blue" @click="change('蓝色')">blue</button>
    </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  methods: {
      change(data) {
        console.log(data, "data")
        var eleLinks = document.querySelectorAll('link[title]');
        [].slice.call(eleLinks).forEach(function (link) {
            link.disabled = true;
            if (data === link.title) {
                link.disabled = false;
            } 
        });
      }
  }
}
</script>

link.gif

看到这是不是感觉有种熟悉的感觉,有点类似动态切换 **link href **的感觉,使用JS改变href属性会带来加载延迟,样式切换不流畅,体验不佳。使用这个有下面两种好处

  1. 兼容性好, Chrome、Firefox、ie8 均支持
  2. 体验更好,瞬间切换平滑过过渡。我们已经先加载了 css 不需要再发请求,缺点就是多份主题在没有上 http2 的网站上可能效果不是最佳,毕竟队头阻塞。建议配合 http 使用更佳

element-ui

既然用了 vue 就不得不提一下使用最多的 ele ui 库,首先来看看他们的 官方效果 思路如下

image.png 他们的方案很明显,命名什么都是有规律可言,需要提前定好规则

总结

其实还有很多其他方案,比如基于 sass 、less 变量这种,很多文章讲的都很通透,这边我就不重复讲了。总体而言我个人还是更喜欢 link alternate 模式对于老工程侵入性不是特别强,对于新的工程规范还是挺重要还是 ele 那套方案比较好用

彩蛋

安利一个渐变色的网站传送门

image.png