跳到主要内容

主题方案

主流主题方案

1. data-theme属性

  • 如 data-theme="dark" 改为 data-theme="light"

  • 编写 CSS

       :root {
    --color: #000;
    }
    [data-theme="light"] {
    --color: #fff;
    }
    body {
    color: var(--color);
    }
  • JS 切换主题

    document.documentElement.setAttribute('data-theme', 'light');

2. 命名空间

  • body 添加 class="light"、class="dark"
  • 编写 CSS
.light {
--color: #fff;
}
body {
color: var(--color);
}
  • JS 切换主题
// 删除 class="light", 添加 class="dark"
document.body.classList.remove('light');
document.body.classList.add('dark');

3. 动态修改 CSS 颜色(粗暴)

  • 事先定义好 CSS 变量对应的颜色
const light = {
'primary': '#fff',
'secondary': '#000'
};

const dark = {
'primary': '#000',
'secondary': '#fff'
};
  • 运行时,通过 JS 获取到对应的颜色值, 并对index.css修改对应的颜色值。例如要将 primary 修改为 #000, secondary 修改为 #fff

其实就是将 #fff 替换为 #000, 将 #000 替换为 #fff

// 代码仅供展示思路,未经测试
for (const key in light) {
// 获取 index.css 内容
const css = fs.readFileSync('./index.css', 'utf-8');
// 将 index.css 内容中的 light 替换为 dark 对应的值
const newCss = css.replace(new RegExp(light[key], 'g'), dark[key]);

// 通过 link 标签将修改后的 CSS 内容插入到页面中
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'data:text/css;charset=utf-8,' + encodeURIComponent(newCss);
document.head.appendChild(link);
}
  1. 先把默认主题文件中涉及到颜色的 CSS 值替换成关键词:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L250-L274
  2. 根据用户选择的主题色生成一系列对应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/formula.json
  3. 把关键词再换回刚刚生成的相应的颜色值:https://github.com/ElementUI/theme-preview/blob/master/src/utils/color.js
  4. 直接在页面上加 style 标签,把生成的样式填进去:https://github.com/ElementUI/theme-preview/blob/master/src/app.vue#L198-L211

4. CSS 变量

  • 定义 CSS 变量
:root {
--primary-color: #fff;
}

body {
background-color: var(--primary-color);
}
``

- JS 切换主题

```js
document.documentElement.style.setProperty('--primary-color', '#000');

缺点:

  • 不可控:当两个同样是黄色,需要修改为不同的颜色,无法实现

实战

现有项目引用 element-ui ,并使用 scss 定义了变量和使用变量。所以需要修改两个地方

  • element-ui 主题
  • scss 变量

1. element-ui 修改主题方案

  • 通过 element-ui 官方工具 element-theme 生成一套主题文件 @/styles/blue-theme/index.css

    • 本地安装会依赖 node-sass,可能会遇到 node 版本和 node-sass 版本兼容问题 ,比较坑。建议使用在线主题编辑器生成下载
  • 通过 glup 打包添加命名空间 blue

    // gulpfile.js
    var path = require('path')

    var gulp = require('gulp')
    var cleanCSS = require('gulp-clean-css');
    var cssWrap = require('gulp-css-wrap');

    var blueThemeName = '.blue'

    gulp.task('css-wrap', function () {
    console.log('Running css-wrap task...');
    return gulp.src(path.resolve('./src/styles/blue-theme/index.css'))
    .pipe(cssWrap({ selector: blueThemeName }))
    .pipe(cleanCSS())
    .pipe(gulp.dest('./src/styles/blue-theme/'));
    });

    gulp.task('move-font', function () {
    console.log('Running move-font task...');
    return gulp.src(['./theme/fonts/**']).pipe(gulp.dest('dist/fonts'));
    });

    gulp.task('default', gulp.series(['css-wrap', 'move-font']));

  • main.js 中引入 import '@/styles/blue-theme/index.css'

  • 修改 body class

       const currentClassName = document.querySelector('body').className;
    if (currentClassName !== data) {
    document.querySelector('body').className = data;
    }

2. 通过 scss 导出变量供 JS 使用

// variables.scss
$primary-color: #fff;
:export {
primary-color: $primary-color;
}
:root {
--primary-color: $primary-color;
}

3. 编写 scss/css 时,使用 css 变量

body {
background-color: var(--primary-color);
}

4. 切换主题时,通过 JS 修改 css 变量

import scssVariables from './variables.scss';

document.documentElement.style.setProperty('--primary-color', scssVariables['primary-color']);

5. 用 vuex 和 localStorage 保存当前主题

localStorage.setItem("theme", "blue");
  • 初始化实例时,触发主题初始化
new Vue({
el: '#app',
i18n,
router,
store,
render: h => h(App),
beforeMount() {
store.commit("theme/SET_THEME", localStorage.getItem("theme"))
},
})

缺点:

  • 样式可能会被 element-ui 覆盖,可能在编写组件的样式前添加命名空间
// 使用命名空间来定义了主题覆盖后,该样式被 .blue el-container 覆盖了。因为css 选择器的优先级问题
// TODO: 如何全局添加该主题嵌套,保持优先级比主题高,而不被覆盖?
.blue,
.default {
.layout-container {
min-width: 1024px;
}
}

参考