网页实现主题切换(换肤)

前言

如果在开发过程中写死的css样式,在面对这样的需求的时候就比较痛苦了。
网页实现主题切换的本质: css的动态渲染
具体实现:如果网站有这样的需求规划,提前就需要定义一套css的环境变量体系

用到的库

名称 用途
vue 一套用于动态构建用户界面的渐进式 JavaScript 框架
normalize.css 初始化页面样式
sass 一种CSS扩展语言
sass-loader 解析sass语法(搭配webpack或其他web构建工具使用)
vant-ui 移动端网页常用的一套ui库

提示:以下是本篇文章正文内容,下面案例可供参考

环境准备

附上package.json, 注意: sass-loade安装需要指定版本 如果安装最新版本会报错 this.getOptions 这个方法未定义

{
  "name": "switch-skin",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.8.3",
    "normalize.css": "^8.0.1",
    "sass": "^1.63.4",
    "vant-ui": "^2.1.11",
    "vue": "^2.6.14",
    "vue-router": "^3.5.1",
    "vuex": "^3.6.2"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-plugin-router": "~5.0.0",
    "@vue/cli-plugin-vuex": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "@vue/eslint-config-standard": "^6.1.0",
    "eslint": "^7.32.0",
    "eslint-plugin-import": "^2.25.3",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^5.1.0",
    "eslint-plugin-vue": "^8.0.3",
    "sass-loader": "^10.1.0",
    "vue-template-compiler": "^2.6.14"
  }
}

项目(demo)目录:

src
├── App.vue
├── main.js
├── router
│   └── index.js  // 页面路由
├── store
│   └── index.js
├── style
│   ├── settings
│   │   └── variable.scss  // 样式变量定义文件
│   └── theme
│       ├── default
|       |     └── index.scss  // 默认样式变量定义文件
|       ├── old
|       |     └── index.scss  // 老年样式变量定义文件
|       |
│       ├── index.scss // 主题入口文件
└── views
    ├── Home.vue // 主题切换页面
    ├── List.vue
    └── Mine.vue

定义变量

需要提前把一些常用的主题色,字体大小,以及边距这种与视觉交互同事沟通好,然后定义对应的变量

小技巧:
定义的时候可以先定义一个基准变量 base-value; 然后其他状态的值可以依赖这个 base-value 进行变大变小。比如不同大小规模的字体可以采用这种方法

// 字体大小 浏览器默认16px
$font-size-base: 16px !default;
$font-size: 16px !default;
$font-size-lg: $font-size-base * 1.25 !default;
$font-size-slg: $font-size-base * 1.75 !default;
$font-size-sm: $font-size-base * 0.875 !default;

定义主题

项目demo有两套主题:
1、适老化主题(针对老年人用户需要放大系统字体,方便他们查看)
2、默认主题
3、…(后期支持扩展)

以定义适老化主题为例:

/* 老年主题色 */
@import "../../settings/variable.scss";
$theme-old: (
  color: $danger,
  font-weight: $font-weight-bold,
  font-size: $font-size-slg,
);

vue.config.js 配置项处理

正常情况,我们需要不断地在各个vue文件里引入我们的scss变量。
不想每次都引入 SCSS 变量,可以在配置项中利用 CSS 插件自动注入全局变量样式

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      scss: {
        // 注意: 在 sass-loader v8 中,这个选项是 prependData
        additionalData: `@import "@/style/theme/index.scss";`,
      },
    },
  },
};

主题色切换(核心逻辑)

1、在 App.vue 文件下的 mounted 中将 body 添加一个自定义的 data-theme 属性,并将其设置为 default

// app.vue
mounted () {
    document.getElementsByTagName('body')[0].setAttribute('data-theme', 'default')
  }

2、利用 webpack 自定义插件遍历主题目录文件,自动生成自定义主题目录数组

const fs = require('fs')
const webpack = require('webpack')

// 获取主题文件名
const themeFiles = fs.readdirSync('./src/style/theme')
const ThemesArr = []
themeFiles.forEach(function (item, index) {
  const stat = fs.lstatSync('./src/style/theme/' + item)
  if (stat.isDirectory() === true) {
    ThemesArr.push(item)
  }
})

module.exports = {
  css: {
    loaderOptions: {
      scss: {
        // 注意: 在sass-loader v8 中,这个选项是prependData
        additionalData: '@import "./src/style/theme/index.scss";'
      }
    }
  },
  configureWebpack: (config) => {
    return {
      plugins: [
        // 自定义webpack插件
        new webpack.DefinePlugin({
          THEMEARR: JSON.stringify(ThemesArr),
          THEMEFILES: JSON.stringify(themeFiles)
        })
      ]
    }
  }
}

3、切换 js 逻辑实现

// Home.vue
methods: {
    // 切换主题方法
    onConfirm(currentTheme) {
      this.currentTheme = currentTheme;
      this.showPicker = false;
      this.currentThemeIndex = this.themeValue.findIndex(
        (theme) => theme === currentTheme
      );
      document
        .getElementsByTagName("body")[0]
        .setAttribute("data-theme", THEMEARR[this.currentThemeIndex]);
    },
  },
  mounted() {
    //初始化页面的时候,获取到默认主题
    this.themeValue = THEMEARR;
    this.currentThemeIndex = this.themeValue.findIndex(
      (theme) => theme === "default"
    );
    this.currentTheme = this.themeValue[this.currentThemeIndex];
  },

前置知识(CSS 版本如何实现主题色切换)

CSS 也是可以支持变量的,这就为声明css变量提供了基础。他通过在变量之前加上前缀 – 来实现,css也支持一些函数,例如:var,calc

首先想到的就是给标签添加自定义主题属性 data-theme,再通过 css 属性选择器+css类名来找到指定的元素并替换不同的主题色。这里采用的 t-文件名-含义类名来命名css类名,防止样式冲突。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    :root {
      --foo: #7f583f;
      --size: 16px;
      --bar: #f7efd2;
    }
    [data-theme="default"] .t-list-title {
       color: var(--foo);
       font-weight: 400;
       font-size:   calc(var(--size)* 3);
    }
  </style>
</head>
<body>
  <div data-theme="default" class="t-list-title">海康威视</div>
</body>
</html>

效果图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VEf0hJVe-1687169924342)(null)]

前置知识(Scss 版本如何实现主题色切换)

使用 sass 之前,需要知道一些知识点(可以在这个网址可以看编写的scss最终生成的css)

1、@each 循环
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OStBfg4L-1687169924249)(null)]

2、循环一个 map:类名为 icon-primary、icon-success、icon-info 等,但是他们的值又都是变量,写法如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E6BkXCeG-1687169924309)(null)]

3、map-get:map-get(map,key) 函数的作用是根据 key 参数,返回 key 在 map 中对应的 value 值。如果 key 不存在 map 中,将返回 null 值。此函数包括两个参数:map:定义好的 map;key:需要遍历的 key
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOt6fhb7-1687169924278)(null)]

4、map-merge:合并两个 map 形成一个新的 map 类型,即将 map2 添加到 map1的尾部

$font-sizes: ("small": 12px, "normal": 18px, "large": 24px);
$font-sizes2: ("x-large": 30px, "xx-large": 36px);

$font-size: map-merge($font-sizes, $font-sizes2);
// 结果: "small": 12px, "normal": 18px, "large": 24px,"x-large": 30px, "xx-large": 36px

5、使用&嵌套覆盖原有样式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ii8Cgvr-1687169924221)(null)]

6、@content: @content 用在 mixin 里面的,当定义一个 mixin 后,并且设置了 @content;@include 的时候可以传入相应的内容到 mixin 里面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yPHbPPXR-1687169924374)(null)]

综合使用:

// 定义一套状态颜色,主要用来展示比如成功失败等各种状态
$primary: #0d6efd !default;
$success: #52c41a !default;
$info: #17a2b8 !default;
$warning: #fadb14 !default;
$danger: #dc3545 !default;
// 字体大小 浏览器默认16px
$font-size-base: 16px !default;
$font-size: 16px !default;
$font-size-lg: $font-size-base * 1.25 !default;
$font-size-slg: $font-size-base * 1.75 !default;
$font-size-sm: $font-size-base * 0.875 !default;
// 字重
$font-weight-normal: 400 !default;
$font-weight-medium: 500 !default;
$font-weight-bold: 600 !default;
$font-weight-super-bold: 700 !default;
// 默认主题
$theme-default: (
  color: $info,
  font-weight: $font-weight-bold,
  font-size: $font-size-lg,
);
// 老年主题
$theme-old: (
  color: $danger,
  font-weight: $font-weight-bold,
  font-size: $font-size-slg,
);
// 主题map
$themes: (
  default: $theme-default,
  old: $theme-old,
);
$theme-map: null;
// 第三步: 定义混合指令, 切换主题,并将主题中的所有规则添加到theme-map中
@mixin themify() {
  @each $theme-name, $map in $themes {
    // & 表示父级元素  !global 表示覆盖原来的
    [data-theme="#{$theme-name}"] & {
      $theme-map: () !global;
      // 循环合并键值对
      @each $key, $value in $map {
        $theme-map: map-merge(
          $theme-map,
          (
            $key: $value,
          )
        ) !global;
      }
      // 表示包含 下面函数 themed()
      @content;
    }
  }
}

@function themed($key) {
  @return map-get($theme-map, $key);
}
// 运用
 .t-home-title,
  .t-home-sub-title,
  .t-home-info {
    @include themify() {
      color: themed("color");
      font-weight: themed("font-weight");
      font-size: themed("font-size");
    }
  }

生成的css为:

[data-theme=default] .t-home-title,
[data-theme=default] .t-home-sub-title,
[data-theme=default] .t-home-info {
  color: #17a2b8;
  font-weight: 600;
  font-size: 20px;
}
[data-theme=old] .t-home-title,
[data-theme=old] .t-home-sub-title,
[data-theme=old] .t-home-info {
  color: #dc3545;
  font-weight: 600;
  font-size: 28px;
}

实现的项目demo效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jr5TGVmR-1687169924181)(null)]

你可能感兴趣的:(vue2,javascript,前端,vue.js,网页实现主题切换(换肤),主题切换,换肤)