基于Css Variable的主题切换完美解决方案(推荐) 

当接到这个需求的时候,百度到业界关于主题切换的方案还挺多的,css链接替换、className更改、less.modifyVars、css in js等等,但每一种方案听起来都是又累又贵。有没有那种代码侵入低,小白无脑又好维护的方案呢?那自然是有的,确切的说是css它本身就支持。

Css3 Variable

定义一个全局颜色变量,改变这个变量的值页面内所有引用这个变量的元素都会进行改变。好简单是不是?

// base.less
:root {
  --primary: green;
  --warning: yellow;
  --info: white;
  --danger: red;
}

// var.less
@primary: var(--primary)
@danger: var(--danger)
@info: var(--info)

// page.less
.header {
  background-color: @primary;
  color: @info;
}
.content {
  border: 1px solid @danger;
}
// change.js
function changeTheme(themeObj) {
  const vars = Object.keys(themeObj).map(key => `--${key}:${themeObj[key]}`).join(';')
  document.documentElement.setAttribute('style', vars)
}

本文结束

个P,它不支持 IE 啊!!0202年还要兼容IE吗?是的,就是要兼容IE。

基于Css Variable的主题切换完美解决方案(推荐) 

css vars ponyfill

是的,还真有polyfill能兼容IE: css-vars-ponyfill 。它搞定IE的方式大概是这样子的

+-------------------------+
|   获取页面内style标签内容  |
|     请求外链css内容       |
+-------------------------+
  |
  |
  v
+-------------------------+  是   +-------------------------+
|      内容是否含有var()    | ----> |        标记为src         |
+-------------------------+       +-------------------------+
  |                                 |
  | 否                              |
  v                                 v
+-------------------------+       +-------------------------+
|       标记为skip         |       |   将var(*)替换为变量值,  |
|                         |       |  新增style标签添加到head  |
+-------------------------+       +-------------------------+

效果大概是这个样子的

基于Css Variable的主题切换完美解决方案(推荐) 

简单粗暴又不失优雅,在支持css var的浏览器中不会进行处理,所以不需要担心性能问题( 是IE的问题,不是我的问题

)。 我们来改造一下代码

// store/theme.js
import cssVars from 'css-vars-ponyfill'

export default {
  state: {
    'primary': 'green',
    'danger': 'white'
  },
  mutations: {
    UPDATE_THEME(state, payload) {
      const variables = {}
      Object.assign(state, payload)
      Object.keys(state).forEach((key) => {
        variables[`--${key}`] = state[key]
      })
      cssVars({
        variables
      })
    }
  },
  actions: {
    changeTheme({ commit }, theme = {}) {
      commit('UPDATE_THEME', theme)
    }
  }
}

// router.js
// 因为路由跳转后的页面会按需加载新的css资源,重新转换
const convertedPages = new Set()
router.afterEach((to) => {
  if (convertedPages.has(to.path)) return
  convertedPages.add(to.path)
  context.store.dispatch('theme/changeTheme')
})

SSR项目闪屏问题优化

在SSR项目中用上述方案你可能会在IE中看到这样的情况

基于Css Variable的主题切换完美解决方案(推荐) 

因为 css-vars-ponyfill

是依赖dom元素来实现转换的,在node中无法使用,所以从server直出未转换的css代码到client加载js文件转换css间存在一段样式空档。

+- - - - - - - - - - - - - - - - - - - -+
                 ' 样式空窗期:                             '
                 '                                       '
+----------+     ' +----------------+     +------------+ '     +-------------+
| 发起请求  | --> ' |  SSR直出页面     | --> | 加载js依赖  | ' --> |  替换css变量 |
+----------+     ' +----------------+     +------------+ '     +-------------+
                 '                                       '
                 +- - - - - - - - - - - - - - - - - - - -+

解决这个问题也很简单,只需要在每个用到 css var 的地方加上一个兼容写法

@_primary: red
@primary: var(--primary)

:root{
  --primary: @_primary
}

.theme {
  color: @primary;
}

// 改为
.theme {
  color: @_primary;
  color: @primary;
}

在不支持css var的浏览器上会渲染默认颜色 red ,等待js加载完毕后ponyfill替换样式覆盖。

Webpack插件开发

手动在每个用到的地方添加兼容写法既幸苦又不好维护,这个时候我们需要了解一些 webpack 生命周期以及插件开发相关的知识,我们可以通过手写一个webpack插件,在 normalModuleLoader ( v5版本被废弃,使用NormalModule.getCompilationHooks(compilation).loader )的hooks中为所有css module添加一个loader来处理兼容代码。

笔者项目使用了less,注意webpack中loader执行顺序是 类似栈的先进后出 ,所以我需要把转换loader添加到less-loader之前,确保我们处理的是编译后的css var写法而非less变量。

// plugin.js
export default class HackCss {
  constructor (theme = {}) {
    this.themeVars = theme
  }

  apply(compiler) {
        compiler.hooks.thisCompilation.tap('HackCss', (compilation) => {
          compilation.hooks.normalModuleLoader.tap(
            'HackCss',
            (_, moduleContext) => {
              if (/\.vue\?vue&type=style/.test(moduleContext.userRequest)) {
                // ssr项目同构会有2次compiler,如果module中存在loader则不继续添加
                if (hasLoader(moduleContext.loaders, 'hackcss-loader.js')) {
                  return
                }

                let lessLoaderIndex = 0
                // 项目用了less,找到less-loader的位置
                moduleContext.loaders.forEach((loader, index) => {
                  if (/less-loader/.test(loader.loader)) {
                    lessLoaderIndex = index
                  }
                })
  
                moduleContext.loaders.splice(lessLoaderIndex, 0, {
                  loader: path.resolve(__dirname, 'hackcss-loader.js'),
                  options: this.themeVars
                })
              }
            }
          )
        })
      }
    })
}

// loader.js
const { getOptions } = require('loader-utils')

module.exports = function(source) {
  if (/module\.exports/.test(source)) return source
  const theme = getOptions(this) || {}
  return source.replace(
    /\n(.+)?var\(--(.+)?\)(.+)?;/g,
    (content, before, name, after = '') => {
      const [key, indent] = before.split(':')
      const add = after.split(';')[0]
      return `\n${key}:${indent}${theme[name]}${after}${add};${content}`
    }
  )
}

至此,我们可以愉快自如的切换主题了。

基于Css Variable的主题切换完美解决方案(推荐) 

后记

通过如何“懒得写更多代码”来吸收新知识会更加有趣, 希望这篇文章能够帮助到你。

标签:
css,Variable的主题切换,css,主题切换,css,Variable

免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
岱庙资源网 Copyright www.zgmyg.com

稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!

昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。

这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。

而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?