换肤 - 一文看破常用的网站主题切换方式

简介: 现在大部分主流网站都不约而同的添加了暗色主题,让我们一起看看这些常用的切换方案、实现方式以及注意点。

网络异常,图片无法展示
|

现在大部分主流网站都不约而同的添加了暗色主题,让我们一起看看这些常用的切换方案、实现方式以及注意点。

切换方案

暴力刷新

该方案出现在最早期,开发者会在构建时针对每个主题构建一套样式文件,然后当用户切换主题后,网站会切换到另外的指向对应主题的地址,或是将主题存入本地缓存然后刷新后读取对应主题加载对应主题文件并进行切换,由于切换体验不佳,随着各种动态切换方案的出现,该方案已经很少看到。

一段平平无奇的伪代码:

html 中读取主题,加载对应文件。

<script>
    const theme = localStorage.getItem('theme');
    const themeFile = `${theme}.css`;
    const themeLink = document.createElement('link');
    themeLink.rel = 'stylesheet';
    themeLink.href = `/css/themes/${themeFile}`;
    document.head.appendChild(themeLink);
</script>
复制代码
const switchTheme = theme => {
    localStorage.setItem('theme', theme);
    location.reload();
};
复制代码

类名切换

在该方案中,开发者会将所有的主题文件打包在一处,然后通过在 rootbody 上增加类名来控制主题生效。

<body>
    <p>Some content</p>
</body>
复制代码
body {
    background: #fff;
}
body.theme-dark {
    background: #000;
}
复制代码
let currentTheme;
const switchTheme = theme => {
    currentTheme && document.body.classList.remove(`theme-${currentTheme}`);
    document.body.classList.add(`theme-${(currentTheme = theme)}`);
};
复制代码

虽然该方案实现了动态切换,但却导致样式文件体积过大,影响页面加载速度。

补丁文件

一般而言该方案会存在主主题和副主题之分,副主题会依赖于主主题声明,通常副主题只包含变更的部分,一般为颜色,而其它部分都只存在于主主题之中,这样可以减少副主题的体积。

在切换到对应副主题时,会将副主题作为补丁文件添加到页面中,覆盖主主题中的声明,从而达到切换主题的目的。

const switchTheme = theme => {
    const themeFile = `${theme}.patch.css`;
    const themeLink = document.createElement('link');
    themeLink.rel = 'stylesheet';
    themeLink.href = `/css/themes/${themeFile}`;
    document.head.appendChild(themeLink);
};
复制代码

该方案也有一定缺陷,由于副主题和主主题相互独立,在维护时很容易因为疏漏出现覆盖不全的问题,并且如果存在动态加载的其它样式,所有的样式都需要做对应处理,维护成本过高。

方案也存在一部分变种,比如直接通过变量生成多套全量主题,然后直接将全量副主题作为补丁文件覆盖,解决维护疏漏的问题,不过这样也减少了该方案的体积优势。

文件替换

该方案类似于全量补丁文件,但是全量补丁文件存在一定限制,必须要主题文件间需要完全一致,才能避免覆盖不全的问题,而一些库的第三方主题间并无法保证其一致性(如 bootstrap 主题),所以该方案中的改进是在新样式生效后,移除旧样式文件

const switchTheme = async theme => {
    const themeFile = `${theme}.css`;
    const themeLink = document.createElement('link');
    themeLink.rel = 'stylesheet';
    themeLink.href = `/css/themes/${themeFile}`;
    document.head.appendChild(themeLink);
    await themeLink.onLoad(); // 伪代码示意,监听 link 的 onload 事件
    const oldThemeLink = document.querySelector(`link[href*="${themeFile}"]`);
    oldThemeLink && oldThemeLink.remove();
};
复制代码

CSS in JS

在主流的 css-in-js 库中,基本都存在主题的定义,此时切换主题只需要更改主题变量的定义即可。

const theme = {
    background: '#fff',
    color: '#000',
    fontSize: '14px'
};
const darkTheme = {
    ...theme,
    background: '#000',
    color: '#fff'
};
const App = () => {
    const [theme, setTheme] = useState(theme);
    return (
        <ThemeProvider theme={theme === 'dark' ? darkTheme : theme} setTheme={setTheme}>
            <div></div>
        </ThemeProvider>
    );
};
复制代码

同样也可以将主题文件作为补丁来异步加载。不过该方案虽然方便,但是却依赖于团队的技术栈,如果团队中存在使用 css 来描述样式,就需要配合其它方案来实现。

css 变量

随着 css 变量的使用越来越广泛,在主题切换切换中,同样大放异彩。

:root {
    --background: #fff;
    --color: #000;
    --font-size: 14px;
}
复制代码

dark.vars.css

:root {
    --background: #000;
    --color: #fff;
}
复制代码

切换时可通过补丁文件或嵌入 style 的方式来覆盖变量的定义,从而实现主题切换。

const switchTheme = theme => {
    const themeFile = `${theme}.vars.css`;
    const themeLink = document.createElement('link');
    themeLink.rel = 'stylesheet';
    themeLink.href = `/css/themes/${themeFile}`;
    document.head.appendChild(themeLink);
};
复制代码

然而由于某毒瘤的兼容性,想要使用并不容易,建议使用优雅降级,在毒瘤上直接关闭切换主题。

暴力滤镜

该方案借助 css filter,直接转换页面颜色,从而暴力实现主题切换。

const switchTheme = theme => {
    if (theme === 'dark') {
        document.body.style.filter = 'invert(100%)';
    } else {
        document.body.style.filter = 'invert(0%)';
    }
};
复制代码

由于过于暴力,展示效果往往会比较奇怪,并且一些特殊元素如图片、视频等也会被影响,需要特殊处理。当然如果哪天老板突然让你上个暗色主题,也可以用来应应急,毕竟 他快啊。

img:not([src*='.svg']),
video {
    filter: invert(100%);
}
复制代码

该方案一般用于一些特殊节日时为网站变灰使用,一段代码即可搞定。

:root {
    filter: grayscale(100%);
}
复制代码

总结

在主题切换中,各方案都存在自己的缺陷和自己的局限性,最终选择还是需要结合网站的受众、团队的技术栈、后续维护成本等因素,综合考虑最适合的方案。

相关文章
|
Dubbo Cloud Native Java
重磅下载 | Java 开发者必备手册《Spring Cloud Alibaba 从入门到实战》,阿里双11同款!
Spring Cloud Alibaba 脱胎于阿里中间件团队内部,经受了阿里多年海量业务场景的考验,是目前最成熟、功能最丰富也最有前景的 Spring Cloud 实现。相信在未来 Spring Cloud Alibaba 获得更多开发者的亲睐与应用,这也将成为 Java 开发者必不可少的技能之一。
131716 0
重磅下载 | Java 开发者必备手册《Spring Cloud Alibaba 从入门到实战》,阿里双11同款!
|
10月前
|
机器学习/深度学习 人工智能 资源调度
AI大模型训练管理工具:千亿参数时代的指挥中枢
本内容揭示了大模型训练中三大核心挑战:实验复现难、资源利用率低、合规风险高,并提出“三维控制塔”解决方案,涵盖实验管理、资源调度与合规追踪。推荐Immuta + 板栗看板等工具组合助力不同规模团队实现高效、合规、低成本的AI训练。
|
Web App开发 安全 应用服务中间件
Burpsuite工具的代理抓包功能实验
Burpsuite工具的代理抓包功能实验
|
JavaScript 前端开发
如何将你的项目上传到 npm
如何将你的项目上传到 npm
1007 0
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
443 0
|
JavaScript
Vue中 引入使用 vue-splitpane 实现窗格的拆分、调节
Vue中 引入使用 vue-splitpane 实现窗格的拆分、调节
2984 0
Vue中 引入使用 vue-splitpane 实现窗格的拆分、调节
|
云安全 存储 运维
首次全面解析云原生成熟度模型:解决企业「诊断难、规划难、选型难」问题
从“上云”到“云上”原生,云原生提供了最优用云路径,云原生的技术价值已被广泛认可。当前行业用户全面转型云原生已是大势所趋,用户侧云原生平台建设和应用云原生化改造进程正在加速。
3699 114
首次全面解析云原生成熟度模型:解决企业「诊断难、规划难、选型难」问题
|
机器学习/深度学习 数据采集 数据可视化
利用Python进行历史数据预测:从入门到实践的两个案例分析
利用Python进行历史数据预测:从入门到实践的两个案例分析
1321 1
|
前端开发 C# 图形学
从PRISM开始学WPF(三)Prism-Region?
原文:从PRISM开始学WPF(三)Prism-Region? 从PRISM开始学WPF(一)WPF? 从PRISM开始学WPF(二)Prism? 从PRISM开始学WPF(三)Prism-Region? 从PRISM开始学WPF(四)Prism-Module? 从PRISM开始学WPF(五...
3117 0
|
存储 人工智能 自然语言处理
领域知识图谱-中式菜谱知识图谱:实现知识图谱可视化和知识库智能问答系统(KBQA)
领域知识图谱-中式菜谱知识图谱:实现知识图谱可视化和知识库智能问答系统(KBQA)
领域知识图谱-中式菜谱知识图谱:实现知识图谱可视化和知识库智能问答系统(KBQA)