用Alpinejs完成主题切换功能
终于把网站的主题切换功能做好了,这次的实现相当满意。
并且我联动了几个主题颜色比较特别的博友,他们分别有:雅余(黄色)、昱行(粉色)、风清(蓝色),大家可以在本站左侧菜单栏选择自己喜欢的配色浏览本站,欢迎给出意见和分享感受。
之前虽然也写过主题切换功能,但是思路没有这次完整,所以索性写一篇博文介绍一下整体实现思路和流程。
这次11ty-book我采用了Alpinejs作为网页的主js框架(真的挺好用的...),这个框架有中文文档 ,官方地址为:https://alpinejs.dev/。
- Alpine.js 通过很低的成本提供了与 Vue 或 React 这类大型框架相近的响应式和声明式特性。
- 你可以继续操作 DOM,并在需要的时候使用 Alpine.js。
- 可以理解为 JavaScript 版本的 Tailwind。
js部分
在主js文件中使用import的方式引入alpinejs文件,并将主题要用到的json数据对象放入Alpine.data对象中(data是alpinejs的数据主要传输方式)。
具体代码作用见下方代码片断。
import Alpine from "alpinejs";
//将Alpine注册进windows对象中
window.Alpine = Alpine;
//存放名为theme的数据对象
Alpine.data("theme", () => ({
// localStorage.name为我存在本地缓存的主题中文名字
themeName: localStorage.name,
// 设置主题切换函数为下方定义的changeTheme
changeTheme: changeTheme,
// 设置设置主题名称函数
setName: function () {
this.themeName = localStorage.name;
},
}));
// 主题切换函数
// @theme 为主题英文名称,用于css切换
// @name 为主题的中文名称,用于显示在页面上
function changeTheme(theme, name) {
// 判断是不是auto
if (theme == "auto") {
// 如何是则使用 prefers-color-scheme 自适应系统颜色
document.documentElement.setAttribute("class", "");
const prefersDarkScheme = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
//同步artalk主题切换
if (window.artalk) window.artalk.setDarkMode(prefersDarkScheme);
//不是auto则切换成对应主题
} else {
// 切换网站的主题类名
document.documentElement.setAttribute("class", theme);
// 如果有artalk则且同步切换artalk主题,这部分好像还可以优化一下。
if (window.artalk)
window.artalk.setDarkMode(theme === "dark" ? true : false);
}
// 同时变更本地的主题名称和中文名称
localStorage.theme = theme;
localStorage.name = name;
}
js 部分
css部分
我使用了sass来编写主题文件,在并定义了多个主题,通过 @include
引入进css文件,届时只需要切换html标签的 class
名称为对应的主题名称即可切换不同的主题配色。
:root {
@include theme-light;
--icon-filter: drop-shadow(10000px 0 0 var(--body-font-color));
}
@media (prefers-color-scheme: dark) {
:root {
@include theme-dark;@include theme-dark;
}
}
html.dark{
@include theme-dark;
}
html.light{
@include theme-light;
}
html.yayu{
@include theme-yayu;
}
html.yuhang{
@include theme-yuhang;
}
html.herblue{
@include theme-herblue;
}
css 部分
我定义的几个主题
// Themes
@mixin theme-light {
--gray-100: #f8f9fa;
--gray-200: #e9ecef;
--gray-500: #adb5bd;
--gray-700: #6c757d;
--color-link: #0055bb;
--color-visited-link: #0055bb;
--body-background: white;
--body-font-color: black;
--btn-color: red;
--btn-bg: blue;
--hint-color-info: #6bf;
--hint-color-warning: #fd6;
--hint-color-danger: #f66;
}
@mixin theme-dark {
--gray-100: rgba(255, 255, 255, 0.1);
--gray-200: rgba(255, 255, 255, 0.2);
--gray-500: rgba(255, 255, 255, 0.5);
--gray-700: rgba(255, 255, 255, 0.7);
--color-link: #84b2ff;
--color-visited-link: #84b2ff;
--body-background: #181818;
--body-font-color: #e9ecef;
--btn-color: red;
--btn-bg: blue;
--hint-color-info: #6bf;
--hint-color-warning: #fd6;
--hint-color-danger: #f66;
}
@mixin theme-yayu {
--gray-100: rgba(227,204,148, 0.1);
--gray-200: rgba(227,204,148, 0.2);
--gray-500: rgba(227,204,148, 0.5);
--gray-700: rgba(227,204,148, 0.7);
--color-link: #e2c274;
--color-visited-link: #e2c274;
--body-background: #222;
--body-font-color: #e3cc94;
--btn-color: red;
--btn-bg: blue;
--hint-color-info: #6bf;
--hint-color-warning: #fd6;
--hint-color-danger: #f66;
}
@mixin theme-yuhang {
--gray-100: rgba(246,248,250, 0.1);
--gray-200: rgba(255, 255, 255, 0.2);
--gray-500: rgba(255, 255, 255, 0.5);
--gray-700: rgba(255, 255, 255, 0.7);
--color-link: rgb(235, 153, 161);
--color-visited-link: rgb(235, 153, 161);
--body-background: #44403c;
--body-font-color: #f6f8fa;
--btn-color: red;
--btn-bg: blue;
--hint-color-info: #6bf;
--hint-color-warning: #fd6;
--hint-color-danger: #f66;
}
@mixin theme-herblue {
--gray-100: rgba(166,180,210, 0.1);
--gray-200: rgba(166,180,210, 0.2);
--gray-500: rgba(166,180,210, 0.5);
--gray-700: rgba(166,180,210, 0.7);
--color-link: #7b88c8;
--color-visited-link: #7b88c8;
--body-background: #0B0C1B;
--body-font-color: #a6b4d2;
--btn-color: red;
--btn-bg: blue;
--hint-color-info: #6bf;
--hint-color-warning: #fd6;
--hint-color-danger: #f66;
}
css 部分
模板部分
首先需要在网页的head部分增加以下代码,作用是在网页加载之前初始化好主题的状态,逻辑为:
- 获取本地设置的主题
- 如果本地设置中有主题设置则直接应用
- 如果上面这个判断失败,如theme为空,则设置为自适应,
这段代码似乎还可以优化?
<script>
const theme = localStorage.theme;
if (localStorage.theme != 'auto') {
document.documentElement.classList.add(theme);
localStorage.theme = theme;
}
localStorage.name = localStorage.name || '自适应';
localStorage.theme = theme || 'auto';
</script>
然后设置主题列表。
在Alpinejs中,我们使用 x-data
属性标识一个标签是动态标签,我这里设置 <ul class="book-theme" x-data="theme">
标签为主动态标签,并且这里还可以指定这个标签需要用到的数据对象,这里我将 x-data
设为上面js中设置的 theme
既js代码中 Alpine.data("theme", () => ({...));
定义的数据部分。
设置完后给再span标签设置 x-text
属性为 themeName
,这个 themeName
我们在js中添加的theme对象中设置的,完整代码为 <span x-text="themeName"></span>
,在页面初始化时Aplinejs会用这个变量初始化该标签的text属性,既显示的文本内容。
之后我这里用liquid的模板语法将我在11ty中设置的主题列表遍历出来,通过 {{ theme.name }}
将遍历输出到网页上。这部分可以根据个人情况来操作,你可以用其他模板框架生成,也可以直接写死在网页上。
并用 @click
属性给他绑定一个点击事件,并调用在js文件中注册的 changeTheme
函数,并传入主题英文名称和中文名称,并调用 setName
函数更新 <span x-text="themeName"></span>
中显示的主题中文名称。
完整代码:
<ul class="book-theme" x-data="theme">
<div>主题</div>
<li >
<input type="checkbox" id="theme" class="toggle">
<label for="theme" class="flex justify-between">
<a role="button" class="flex align-center">
<img src="{{ '/assets/svg/theme.svg' | absoluteUrl | cacheBust }}" class="book-icon" alt="change theme">
<span x-text="themeName"></span>
</a>
</label>
<ul>
{% for theme in themes %}
<li>
<a @click="changeTheme('{{ theme.name }}','{{ theme.desc }}');setName();">
{{ theme.desc }}
</a>
</li>
{% endfor %}
</ul>
</li>
</ul>
完整代码:
完成
Enjoy~!
加入评论