木灵鱼儿
阅读:96
利用typescript实现原生css变量的主题切换功能类
前言
主题的切换研究过好久了,个人认为目前最好的两种实现:
- 原生css变量实现,性能好,但是不太兼容旧设备
- 利用预处理scss这种做一个主题类名,通过最上层类名变化从而改变嵌套的子类名的颜色,兼容性好,但是需要把所有的类都抽出来单独设置。
本着简单+性能的目标,我采用了第一种方案,这种方案也是大佬张鑫旭提供了,文章是:《link rel=alternate网站换肤功能最佳实现》
有兴趣弄的话可以先看看大佬的文章了解原理。
封装
主题的链接引入采用如下格式:
<!-- theme -->
<link href="<?php $this->options->themeUrl('/static/css/theme/light.css');?>" rel="stylesheet" type="text/css" title="light">
<link href="<?php $this->options->themeUrl('/static/css/theme/dark.css');?>" rel="alternate stylesheet" type="text/css" title="dark">
title用于区分是什么主题。
封装一个Theme类,使用懒汉单例模式
/** 主题map对象 */
interface ThemeMap {
[key: string]: HTMLLinkElement;
}
class Theme {
/** 静态实例 */
private static interface: Theme;
/** link元素数组 */
private themeMap: ThemeMap = {};
private activeTheme = "";
private constructor() {
const linkList = Array.from(document.querySelectorAll<HTMLLinkElement>('link[type="text/css"][title]'));
linkList.forEach((link) => {
const title = link.getAttribute("title");
if (!title) return;
const rel = link.getAttribute("rel");
this.themeMap[title] = link;
if (rel && !rel.includes("alternate")) {
this.activeTheme = title;
}
});
const localTheme = localStorage.getItem("theme");
if (localTheme && localTheme !== this.activeTheme) {
this.switchTheme(localTheme);
}
}
/** 获取当前主题 */
public getActiveTheme() {
return this.activeTheme;
}
/** 切换指定主题 */
public switchTheme(theme: string) {
if (theme === this.activeTheme) return;
Object.keys(this.themeMap).forEach((key) => {
const link = this.themeMap[key];
link.disabled = true; // 先禁用,不管是否启用
if (key === theme) link.disabled = false; // 启用指定主题
});
this.activeTheme = theme;
localStorage.setItem("theme", theme);
}
/** 获取主题实例 */
public static getInterface() {
if (!Theme.interface) {
Theme.interface = new Theme();
}
return Theme.interface;
}
}
export default Theme;
使用
import Theme from "@/utils/theme";
const themeInterface = Theme.getInterface();
themeInterface.switchTheme("dark");
做的比较简单,因为业务上用不到其他的,如果从安全的角度想,我们是不是需要验证切换的主题是不是合法的主题名呢,以及缓存的主题是否也需要判断一下,如果不合法手动删除等,但是我就不想这么麻烦了。
从开发的角度怎么说呢,不要过度的设想不存在的场景,加快开发进度才是正事。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
ts实战:给没有类型声明的第三方库编写一个类型声明文件
前情提要在github找了一个原生代码写的Toast库,安装后发现没有ts类型声明,通过安装@types/库名 也没有现成的对应的类型声明库,于是想着自己写一份声明文件用,也是第一次,所以找了好些教程,这里做个笔记。npm仓库: Toastify说实话这个库不太行,不支持多个toast,权当练手了。类型声明文件在项目中,比如我是在src目录下创建一个types目录,专门存放这些类型声明文件,创建一个名为Toastify.d.ts的文件。内容如下:declare module "toastify" { /** toast定位 */ export type Toa...

利用发布订阅模式封装一个IntersectionObserver
前言用于判断当前元素是否出现在视口区域,以此来实现懒加载已经是日常业务必须的东西了,但是如何去判断这个元素是否出现在视口中,是一个非常头疼的地方。很久以前,我们通过dom.offsetTop的方式,不断的累加自身和父级元素的offsetTop值,得到元素具体文档顶部的距离,然后判断这个距离是否小于等于当前视窗的高度+滚动条scrollTop,如果是的话,说明用户已经滚动到了,或者滚过去了,这个时候就得触发图片懒加载。但是这种方式十分痛苦,需要递归计算offsetTop的值,而且性能不是很好。后来浏览器又提供了getBoundClientRect的方法,这个方法会返回当前元素距离视窗的四个...
typecho 自定义分类和分类高亮处理
前言目前个人认为最合适的一个判定了,但是我的做法只支持二级分类,如果你有多级分类,就需要自行调整了。先上个实际html渲染图:可以看到,我进入的是HTML子分类,那么对应的子分类class上已经有active类名了,而它的父级分类前端,在类名上也存在了active。教程不多说,直接上源码[hide]<div class="nav-list"> <?php $this->widget('Widget_Metas_Category_List')->to($category);?> <?php while ($categor...

ts封装一个localStorage和sessionStorage的类
前言希望存储一个对象的时候能自动JSON转成字符串,存储一个string类型的值时,不需要再JSON化了,不然又多两引号,和原生存储使用会有出入,我的宗旨就是能和原生效果保持一致的情况下增加一些方便的处理。拿取的时候就会有一些顾虑了,因为拿到的值就是字符串类型,所以我加了一个额外的参数来判断是否需要JSON解析,默认是需要的,这个配置用于这个值我可能自己手动转成字符串存的,它的源值是一个对象,我不希望取值的时候被解析出来,我就要它原样给我,应对这种情况加了一个配置判定。在拿取的时候返回的值类型是any,显然这不是我想要的,我希望能准确判定这个类型,于是通过泛型的方式进行约束。另一个考量是...
typecho主题开发:基于webpack5的多页面打包项目
前言一直想把基于webpack5的多页面打包整理一下,做成一个通用的typecho主题开发架子,之前在JJ主题上虽然以及由部分实现了,但是不是很理想,因为是第一次弄,所以还是有些粗糙。现在离职后得了空闲,于是把这个架子给搭起来了,基于这个架子做前端开发还是很方便的,起码各种框架都能通过按照包的方式使用,能用上先进的打包机还是很棒的!仓库地址github地址:webpack-multiple-entry觉得有用的话麻烦点个Star吧。如果有什么问题可以提交Issues或者在该文章下面留言

让typecho支持特殊字符
起因由于最近在了解前端js中length不准的问题,其中文章带有一些特殊字符,而typecho和mysql都只是支持UTF-8字符,但是这个utf-8并不是真正意义上的utf-8,它最多支持3个字节的内容,也就是24bit,而我文章中的特殊字符是需要4个字节来存储的,这就导致文章无法正常的展示和保存。这个特殊字符和现在移动端常用的emoji表情差不多,emoji也是一种特殊字符,所以该教程同样适用于如何让typecho支持emoji表情。教程打开宝塔的phpmyadmin -> 找到typecho的数据库 -> 操作 -> 滑动到最底部找到排序规则 -> 选择ut...

正确使用vue3的ts类型声明
前言使用了ts最头疼的是什么,除了类型声明应该没有第二家了,那么在vue3中如何正确的声明ts类型,代表着我们踏出了认识vue3的第一步,这非常重要,所以为此水个文章,分享给有需要的人。Volar 插件一开始我对于Volar并没有太大的需要,因为一直使用的Vetur,而且这个插件刚出来时并不完善,各种视频up讲的那个一键分屏功能其实也并不好用,虽然是个很有意思的东西,但是没有那种非要使用它的点,所以当时的我怀着这么一个疑问?为什么要用Volar ?现在我就通过两张图告诉你,它有多香!我们在template里面写代码,绑定变量最烦的是什么,就是我们写了个对象,但是忘了它的属性有哪些啊,使用...

映射类型,对象属性批量设置约束
批量设置属性全部只读interface ObjInterface { a: number; b: number; c: number; } //全部设为只读 type readonlyObj = Readonly<ObjInterface>;鼠标移动到readonlyObj可以发现所有的属性全部设置为只读了。type readonlyObj = { readonly a: number; readonly b: number; readonly c: number; }我们按住ctrl+鼠标左键点击Readonly;可以看到...
索引类型,key的约束
再for循环中我们经常遇到类似这种写法const obj = { a: 1, b: 2, c: 3 }; //遍历obj拿到指定key对应的val数组 function getObjVal(obj:any,keys:string[]) { return keys.map(key => obj[key]); } console.log(getObjVal(obj, ["a", "b"])); console.log(getObjVal(obj, ["d", "e"]...
关于ts 属性“xxx”没有初始化表达式,且未在构造函数中明确赋值的解决方案
ts默认要求class中声明的属性必须初始化,所以,如果存在没有被初始化的属性,就会报这个错误。如果我们让他或者等于null,那么在调用属性的时候又会提示值可能为null,极度麻烦class A { data: { name: string } | null = null; } const a = new A(); console.log(a.data.name);然后你又不得不搞个非空断言。console.log((a.data!).name);每次都这么写非常麻烦。赋值方案既然他要赋值那么我们就赋值class A { data: { name: string }; c...
