前言
- 2019年多品牌产品适配总结 Vue项目主题色适配,看这一篇文章就够了
- H5页面老年化适配
主题切换
在业务中遇到需要变换页面主题样式的需求,常见的实现方案有:替换css链接,修改className,使用less.modifyVars,修改css变量不同的场景使用不同的改方案,也可以将几种方案融合。
1.方案一:在body上添加特殊的class名
实现原理:通过修改body
标签的class名称,使用css作用域来进行主题的切换
1.1实现步骤
1.1.1创建页面接口和样式
本例用Umi搭建项目,假设“圣诞活动”为主题,html代码如下
// pages/index/index.tsx
import './index.less';
export default function IndexPage() {
return (
<div className="page-index">
<div className="banner"></div>
<section className="content">
<h1 className="title">换新特惠</h1>
<p className="desc">
新年焕新 新春特惠 全城狂欢
官网价4299 现活动立送1000元现金劵,
可当场消费
全场手机可以分期付款0首付0利息
以旧换新,最高可低4000元
联系电话 18103414888
专卖店喜迎新春
凭身份证进店领100元话费,
回家过年。详情进店咨询。
</p>
<button className="button">立即加入</button>
</section>
</div>
);
}
样式代码如下
.page-index {
.banner {
height: 257px;
width: 100%;
background-size: cover;
background-color: #cccccc;
background-repeat: no-repeat;
background-position: center;
}
.content {
padding: 20px;
}
.button {
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: block;
width: 100%;
font-size: 16px;
}
}
1.1.2提取样式文件
将需要定制的background-image,background-color,color等创建主题文件,已圣诞节为例
// assest/theme/normal.less
.normal {
background-color: #f1f1f1;
.banner {
background-image: url('../images/normal.jpeg');
}
.content{
.title {
color: #000;
}
color: #333333;
.button{
background-color: #4caf50;
}
}
}
// assest/theme/christmas.less
.christmas {
background-color: lightpink;
.banner {
background-image: url('../images/christmas.jpeg');
}
.content{
.title {
color: #fff;
}
color: #ffffff;
.button{
background-color: red; /* Green */
}
}
}
1.1.3 在global.less中引入主题文件
// src/global.less
@import './assest/theme/normal';
@import './assest/theme/christmas';
1.1.4 在项目入口app.ts中给html中的body添加一个class
// app.ts
// 预设了normal和christmas两种主题,修改className的值切换主题
const className = 'christmas'
document.getElementsByTagName('body')[0].className = className;
1.2页面结果预览
className = 'normal' className = 'christmas'
1.3总结:
- 优点:实现简单,容易理解
- 缺点:
- 当页面较多时需要编写多个主题文件,需要注意样式命名冲突问题
- 不能使用CSS Modules,或者说不能使用在className加哈希
思考:既然用了less为什么不用变量呢?
2.方案二:使用less变量
2.1 实现步骤
2.1.1 修改方案一中主题文件
将颜色值改成变量
// noemal.less
@primary-color: #4caf50;
@bg-color: #f1f1f1;
@title-color: #000000;
@text-color: #333333;
@banner-images: url('../images/normal.jpeg');
.normal {
background-color: @bg-color;
.banner {
background-image: @banner-images;
}
.content {
color: @text-color;
.title {
color: @title-color;
}
.button {
background-color: @primary-color;
}
}
}
// christmas.less
@primary-color: red;
@bg-color: lightpink;
@title-color: #ffffff;
@text-color: #ffffff;
@banner-images: url('../images/christmas.jpeg');
.christmas {
background-color: @bg-color;
.banner {
background-image: @banner-images;
}
.content {
.title {
color: @title-color;
}
color: @text-color;
.button {
background-color: @primary-color;
}
}
}
这样和之前并没有本质上的区别,只是将值写成了变量
思考:如果可以像js那样动态的改变变量值就完美了!
2.1.2 重新规划编写主题文件theme.less
// theme.less
@primary-color: red;
@bg-color: lightpink;
@title-color: #ffffff;
@text-color: #ffffff;
@banner-images: url('../images/christmas.jpeg');
body{
background-color: @bg-color;
}
.banner {
background-image: @banner-images;
}
.content {
.title {
color: @title-color;
}
color: @text-color;
.button {
background-color: @primary-color;
}
}
这个theme.less文件放在什么地方呢?
为了在项目打包编译的时候不被webpack编译,我们将样式文件放在根目录public里面(public/style/theme.less),并且将public设置为静态资源库,以Umi为例: 需要在umirc.ts中修改
export default defineConfig({
...
publicPath: '/public/'
...
})
我们再创建一个主题汇总的public/style/index.less,方便不同页面样式导入
// index.less
// 其他主题文件
// @import "./night.less";
@import "./theme.less";
在HTML文件中引入index.less样式文件(Umi修改默认HTML模板请参考官方文档HTML模板
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui" />
<title>Your App</title>
</head>
<body>
<link rel="stylesheet/less" type="text/css" href="../../public/style/index.less" />
<div id="root"></div>
</body>
</html>
去修改变量值
2.1.3 js控制变量值
安装less依赖npm install less,创建一个切换主题的工具函数utils/index.ts
import less from 'less';
export const changeTheme = (themeOption: Record<string, string>) => {
less.modifyVars({ // 调用 `less.modifyVars` 方法来改变变量值
...themeOption,
}).then(() => {
console.log('修改成功');
})
}
预设几套主题的json对象,在适当的时候调用changeTheme函数,已app.ts中使用为例:
import {changeTheme} from './utils/index';
// 默认
const defaultTheme = {
'@primary-color': '#4caf50',
'@bg-color': '#f1f1f1',
'@title-color': '#000000',
'@text-color': '#333333',
'@banner-images': "url('../images/normal.jpeg')",
}
// 圣诞节
const christmasTheme = {
'@primary-color': 'red',
'@bg-color': 'lightpink',
'@title-color': '#ffffff',
'@text-color': '#ffffff',
'@banner-images': "url('../images/christmas.jpeg')",
}
// 黑暗模式
const darkTheme = {
'@primary-color': 'gold',
'@bg-color': '#000000',
'@title-color': '#ffffff',
'@text-color': '#FFFFFF',
'@banner-images': "url('../images/normal.jpeg')",
}
// 在适当的时候调用changeTheme方法
changeTheme({
...christmasTheme
})
2.2页面效果预览
2.3 总结:
优点:
- 嘉善可以灵活的控制颜色
- 可预设几套常规的主题搭配,也支持用户自定义 缺点:
- 项目需要提前规划主题样式,主题变量值需要跟一般样式文件分开,开发和维护成本高
思考:如何更优雅的切换主题?
- 主题文件使用CDN,在特定的时候变更CDN文件
- 后端接口返回色值变量的json
- 利用时间戳在固定时间切换主题
// 圣诞节2021-12-24 23:59:59的时间戳 1640361599000
let currentTheme = defaultTheme
if (new Date().valueOf() > 1640361599000) {
currentTheme = christmasTheme
} else {
currentTheme = defaultTheme
}
changeTheme(currentTheme)
- 获取当前时间判断是白天还是黑夜
let currentTheme = defaultTheme
const hours = new Date().getHours()
if (hours > 8 && hours < 20) {
currentTheme = defaultTheme
} else {
currentTheme = darkTheme
}
changeTheme(currentTheme)
5.根据当前系统模式
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
function darkModeHandler() {
if (mediaQuery.matches) {
console.log('现在是深色模式')
changeTheme(darkTheme)
} else {
console.log('现在是浅色模式')
changeTheme(defaultTheme)
}
}
// 判断当前模式
darkModeHandler()
// 监听模式变化
mediaQuery.addListener(darkModeHandler)
3.方案三:原生css变量,
老年化适配为例
3.1实现步骤
3.1.1 页面html结构和样式
H5页面像素自适应(rem方案),在global.css中添加
:root {
--font-size-28PX: 2.8rem;
--font-size-32PX: 3.2rem;
--font-size-36PX: 3.6rem;
--font-size-40PX: 4.0rem;
--220PX: 22rem;
--514PX: 51.4rem;
}
html {
font-size: 31.25%;
}
body{
font-size: var(--font-size-28PX);
}
创建一个文件pages/eleder/index.tsx
import style from './index.less';
import {ClockCircleOutlined} from '@ant-design/icons'
import {Button} from 'antd'
export default function IndexPage() {
return (
<div className={style.pageElder}>
<div className={style.banner}></div>
<section className={style.content}>
<h1 className={style.title}>换新特惠</h1>
<p className={style.desc}>
新年焕新 新春特惠 全城狂欢 官网价4299 现活动立送1000元现金劵,
可当场消费 全场手机可以分期付款0首付0利息 以旧换新,最高可低4000元
联系电话 18103414888 专卖店喜迎新春 凭身份证进店领100元话费,
回家过年。详情进店咨询。
</p>
<ul className={style.list}>
<li className={style.item}><ClockCircleOutlined style={{fontSize: '38px'}}/></li>
<li className={style.item}><ClockCircleOutlined style={{fontSize: '38px'}}/></li>
<li className={style.item}><ClockCircleOutlined style={{fontSize: '38px'}}/></li>
<li className={style.item}><ClockCircleOutlined style={{fontSize: '38px'}}/></li>
<li className={style.item}><ClockCircleOutlined style={{fontSize: '38px'}}/></li>
<li className={style.item}><ClockCircleOutlined style={{fontSize: '38px'}}/></li>
</ul>
<button className={style.button}>立即加入</button>
</section>
</div>
);
}
创建一个样式文件pages/eleder/index.tsx
.pageElder {
.banner {
height: var(--514PX);
width: 100%;
background-size: cover;
background-color: #cccccc;
background-repeat: no-repeat;
background-position: center;
background-image: url("../../../public/images/normal.jpeg");
}
.content {
padding: 20px;
color: #333333;
.title{
color: #000000;
font-size: var(--font-size-40PX);
}
.desc {
font-size: var(--font-size-28PX);
}
.list{
list-style: none;
display: flex;
padding: 0;
margin: 20px 0;
flex-wrap: wrap;
.item {
border: 1px dashed #ccc;
display: inline-block;
margin: 10px 0;
width: var(--220PX);
text-align: center;
}
}
}
.button {
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: block;
width: 100%;
font-size: var(--font-size-32PX);
background-color: #4caf50;
}
}
3.1.2 创建修改css变量的工具函数
在utils/index.ts中添加
// 修改css变量
export const setCssVar = (themeOption: { [x: string]: any; }) => {
const root = document.querySelector(':root') as HTMLScriptElement;
Object.keys(themeOption).forEach((key)=> {
root.style.setProperty(key, themeOption[key]);
})
}
3.1.3 预设变量值,在适当的时候切换
在pages/eleder/index.ts添加如下代码
import {setCssVar} from "@/utils"; // 引入工具函数
import { useState } from 'react';
// 预设变量值
const normal = {
'--font-size-28PX': '2.8rem',
'--font-size-32PX': '3.2rem',
'--font-size-36PX': '3.6rem',
'--font-size-40PX': '4.0rem',
'--220PX': '22rem',
'--514PX': '51.4rem'
}
const elder = {
'--font-size-28PX': '4.0rem',
'--font-size-32PX': '4.8rem',
'--font-size-36PX': '5.0rem',
'--font-size-40PX': '5.6rem',
'--220PX': '30rem',
'--514PX': '73.4rem'
}
export default function IndexPage() {
const [toggle, setToggle] = useState<boolean>(true) // 切换
const change = () => {
toggle ? setCssVar(elder):setCssVar(normal)
setToggle(!toggle)
}
return (
<div className={style.pageElder}>
<Button size="small" onClick={change}>主题切换</Button>
<div className={style.banner}></div>
...
</div>
);
}
3.2 页面效果预览
3.3 总结: 优点: 1. css原生支持 2. 可以使用CSS Modules 3. 不需要引用其他插件4.小程序主题切换
小程序的主题切换和H5页面有所不同,小程序没有window和document属性,所以之前的document.getElementsByTagName('body')[0].className = className的方式不能使用
使用less小程序编译后识别的是acss,所以无法使用less变量来处理。
4.1 技术原理
我们使用css原生支持的变量,先看下面代码
// index.acss
.title {
color: var(--title-color, #000000);
}
// index.axml
<view className="title" style="--title-color: #FFFFFF">换新特惠</view>
我们可以给一个字体颜色加一个变量“--title-color”,并给他一个默认值#000000, 然后我们通过改变style内嵌的方式改变--title-color的值来改变字体颜色
4.2 实现步骤
我们使用的支付宝小程序框架miniu创建项目,具体创建请参看支付宝miniU官方文档
4.2.1创建页面index.axml
// index.axml文件,通过themeStyle改变颜色变量
<view style={{themeStyle}} className="page-index" >
<view className="banner"></view>
<view className="content">
<view className="title">换新特惠</view>
<view className="desc">
新年焕新 新春特惠 全城狂欢 官网价4299 现活动立送1000元现金劵,
可当场消费 全场手机可以分期付款0首付0利息 以旧换新,最高可低4000元
联系电话 18103414888 专卖店喜迎新春 凭身份证进店领100元话费,
回家过年。详情进店咨询。
</view>
<button className="button">立即加入</button>
</view>
</view>
4.2.2页面样式index.less
.page-index {
.banner {
height: 257px;
width: 100%;
background-size: cover;
background-color: #cccccc;
background-repeat: no-repeat;
background-position: center;
background-image: var(--banner-images, url('https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/70967f6ec4ac40bb9b17a673849e9fc6~tplv-k3u1fbpfcp-watermark.image?'));
}
.content {
padding: 20px;
.title {
color: var(--title-color, #000000);
}
.desc {
color: var(--text-color, #333333);
margin-bottom: 50rpx;
}
}
.button {
border: none;
color: white;
text-align: center;
text-decoration: none;
display: block;
width: 100%;
background: var(--primary-color, #4caf50);
}
}
页面中我们定义了--banner-images,--title-color,--text-color,--primary-color等几个变量
4.2.3 数据流
我们使用MiniU Data数据流处理方式,引用使用全局变量
// index.js
import { createPage } from '@miniu/data';
Page(
createPage({
mapGlobalDataToData: {
themeStyle: (globalData) => {
return globalData.themeStyle;
},
},
})
);
使用themeStyle前需要在app.js中定义,并且在onLaunch里面改变颜色值
import {createApp, setGlobalData} from '@miniu/data';
import {getSkinSettings} from './service/index';
App(
createApp({
defaultGlobalData: {
count: 50,
themeStyle: '', // 定义全局变量themeStyle
},
async onLaunch(options) {
const res = await getSkinSettings(); // 模拟从后端获取颜色值
this.setThemeStyle(res.data); // 改变色值
},
setThemeStyle({
primaryColor = '#4caf50',
bgColor = '#f1f1f1',
titleColor = '#000000',
textColor = '#333333',
bannerImages = 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/70967f6ec4ac40bb9b17a673849e9fc6~tplv-k3u1fbpfcp-watermark.image?',
}) {
setGlobalData((globalData) => {
globalData.themeStyle = `
--primary-color: ${primaryColor};
--bg-color: ${bgColor};
--title-color: ${titleColor};
--text-color: ${textColor};
--banner-images: url('${bannerImages}');`;
});
// 设置页面背景色
my.setBackgroundColor({
backgroundColor: bgColor,
});
},
})
);
4.2.4 模拟接口请求
// service/index.js
export const getSkinSettings = () => {
const themeData = {
primaryColor: 'red',
bgColor: '#ffb6c1',
titleColor: '#ffffff',
textColor: '#ffffff',
bannerImages: 'https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd9dd0126a404dcb8a943dfab864c1cd~tplv-k3u1fbpfcp-watermark.image?',
};
return new Promise((resolve, reject) => {
// 模拟后端接口访问,暂时用500ms作为延时处理请求
setTimeout(() => {
const resData = {
code: 200,
data: {
...themeData,
},
};
// 判断状态码是否为200
if (resData.code === 200) {
resolve(resData);
} else {
reject({ code: resData.code, message: '网络出错了' });
}
}, 500);
});
};
在页面的根节点中插入变量值
正在子节点中使用变量
4.3 效果预览
彩蛋
一行代码实现灰暗模式(国家公祭日,国难日, 512纪念日,为烈士哀悼)
例如:
html {
-webkit-filter: grayscale(.95);
}
源码地址
小程序源码: gitee.com/ssjuanlian/…