如果在开发过程中写死的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文件里引入我们的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也支持一些函数,例如: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)]
使用 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;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jr5TGVmR-1687169924181)(null)]