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

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

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

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

切换方案

暴力刷新

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

一段平平无奇的伪代码:

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 开发者必不可少的技能之一。
131224 0
重磅下载 | Java 开发者必备手册《Spring Cloud Alibaba 从入门到实战》,阿里双11同款!
|
小程序 前端开发 安全
uniapp中解析markdown支持网页和小程序
对于`markdown`相信大家都不陌生,日常写文档或日常记录都用到的比较多,书写的是`markdown`的格式,实时预览的是转换后的`html`样式。本次实现的需求是在`uniapp`中转换`markdown`文本展示在不同的平台,主要平台是浏览器使用和微信小程序使用。
640 1
|
小程序 Java 关系型数据库
网球爱好者小程序的设计与实现
网球爱好者小程序的设计与实现
362 0
|
4月前
|
机器学习/深度学习 人工智能 资源调度
AI大模型训练管理工具:千亿参数时代的指挥中枢
本内容揭示了大模型训练中三大核心挑战:实验复现难、资源利用率低、合规风险高,并提出“三维控制塔”解决方案,涵盖实验管理、资源调度与合规追踪。推荐Immuta + 板栗看板等工具组合助力不同规模团队实现高效、合规、低成本的AI训练。
|
8月前
|
Linux
Linux系统ext4磁盘扩容实践指南
这个过程就像是给你的房子建一个新的储物间。你需要先找到空地(创建新的分区),然后建造储物间(格式化为ext4文件系统),最后将储物间添加到你的房子中(将新的分区添加到文件系统中)。完成这些步骤后,你就有了一个更大的储物空间。
684 10
|
Web App开发 安全 应用服务中间件
Burpsuite工具的代理抓包功能实验
Burpsuite工具的代理抓包功能实验
|
JavaScript 前端开发
如何将你的项目上传到 npm
如何将你的项目上传到 npm
817 0
|
12月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
269 0
|
XML JavaScript 前端开发
如何在JavaScript中设置多个样式属性?
【6月更文挑战第29天】如何在JavaScript中设置多个样式属性?
781 3
|
存储 算法 安全
软件系统设计步骤与原理
软件系统设计步骤与原理