小白篇 -- 暗黑(主题)模式几种实现方式探究

576 阅读2分钟

该 demo 基于:

​ react:17.0.2

​ less:4.1.1(如果使用了 less)

1. 动态增加标签自定义属性(html,body......)

app.tsx

import "./styles.less";

export default function App() {
	// 切换主题
	const changeTheme = (type: string) => {
		const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)")
			.matches; // 获取系统是否是暗黑模式

		if (type === "auto") {
			document.documentElement.setAttribute(
				"data-prefers-color",
				prefersDarkMode ? "dark" : "light"
			);
		} else {
			document.documentElement.setAttribute("data-prefers-color", type);
		}
	};

	return (
		<div className="App">
			<h1>Dark Light Demo</h1>
			<div>
				<button
					className="btn"
					onClick={() => {
						changeTheme("dark");
					}}
				>
					change Dark
				</button>
			</div>
			<div>
				<button
					className="btn"
					onClick={() => {
						changeTheme("light");
					}}
				>
					change Light
				</button>
			</div>
			<div>
				<button
					className="btn"
					onClick={() => {
						changeTheme("auto");
					}}
				>
					change Auto
				</button>
			</div>
		</div>
	);
}

var.less

@c-bg: #fff;
@c-bg-dark: #141414;
@c-text: #141414;
@c-text-dark: rgba(255, 255, 255, 0.65);
@c-border: #d9d9d9;
@c-border-dark: #434343;

styles.less

根据属性修改对应样式 html[data-prefers-color="dark"]

@import "./var.less";

body {
	background: @c-bg;

	[data-prefers-color="dark"] & {
		background: @c-bg-dark;
	}
}

.App {
	font-family: sans-serif;
	text-align: center;
	color: @c-text;

	[data-prefers-color="dark"] & {
		color: @c-text-dark;
	}
}

.btn {
	margin-top: 10px;
	padding: 4px 15px;
	font-size: 14px;
	border-radius: 2px;
	color: rgba(0, 0, 0, 0.85);
	color: @c-text;
	border: 1px solid @c-border;
	user-select: none;
	cursor: pointer;
	background: @c-bg;

	[data-prefers-color="dark"] & {
		color: @c-text-dark;
		border: 1px solid @c-border-dark;
		background: @c-bg-dark;
	}
}

1.gif

2. 使用CSS自定义属性(变量)

css 自定义属性介绍:developer.mozilla.org/zh-CN/docs/…

app.tsx

// 将 less 替换为 css,其余代码保持一致。

- import "./styles.less";
+ import "./styles.css";

...

styles.css

html {
	--c-bg: #fff;
	--c-text: #141414;
	--c-border: #d9d9d9;
}

html[data-prefers-color="dark"] {
	--c-bg: #141414;
	--c-text: rgba(255, 255, 255, 0.65);
	--c-border: #434343;
}

body {
	background: var(--c-bg);
}

.App {
	font-family: sans-serif;
	text-align: center;
	color: var(--c-text);
}

.btn {
	margin-top: 10px;
	padding: 4px 15px;
	font-size: 14px;
	border-radius: 2px;
	color: rgba(0, 0, 0, 0.85);
	color: var(--c-text);
	border: 1px solid var(--c-border);
	user-select: none;
	cursor: pointer;
	background: var(--c-bg);
}

还可以通过 js 来修改: document.documentElement.style.setProperty(‘--themeColor‘, '#333');

3. 动态修改 <link>

假设有存在以下主题目录:

theme
├── dark.css
├── light.css
├── blue.css
├── red.css
└── yellow.css
onClick = (theme) => {
  let styleLink = document.getElementById('theme-style');
  if (styleLink) {
    // 假如存在id为theme-style 的link标签,直接修改其href
      styleLink.href = `/theme/${theme}.css`; // 切换主题
    }
  } else {
    // 不存在的话,则新建一个
    styleLink = document.createElement('link');
    styleLink.type = 'text/css';
    styleLink.rel = 'stylesheet';
    styleLink.id = 'theme-style';
    styleLink.href = '/theme/light.css';
    }
    document.body.append(styleLink);
  }
};

4. 使用 less.modifyVars()

color.less

@c-bg: #fff;
@c-text: #141414;
@c-border: #d9d9d9;

index.html

<!DOCTYPE html>
<html lang="en">
  
  ...
  
	<head>
		<link rel="stylesheet/less" type="text/css" href="/color.less" />
	</head>
  
  ...
  
</html>

app.tsx

import "./styles.less";

const loadScript = (src: string) => {
	return new Promise((resolve, reject) => {
		const script = document.createElement("script");
		script.type = "text/javascript";
		script.src = src;
		script.onload = resolve;
		script.onerror = reject;
		document.head!.appendChild(script);
	});
};

let lessLoaded = false;

export default function App() {
	// 切换主题
	const changeTheme = (type: string) => {
    // 这里就没有去完善根据类型自动配置对应主题了,可自行添加
    
		// const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)")
		// 	.matches;

		const changeColor = () => {
			(window as any).less
				.modifyVars({
					"@c-bg": "#141414",
					"@c-text": "rgba(255, 255, 255, 0.65)",
					"@c-border": "#434343"
				})
				.then((res) => {
					alert("修改主题成功!");
				});
		};

		const lessUrl =
			"https://gw.alipayobjects.com/os/lib/less/3.10.3/dist/less.min.js";

		if (lessLoaded) {
			changeColor();
		} else {
			(window as any).less = {
				async: true,
				javascriptEnabled: true
			};
			loadScript(lessUrl).then(() => {
				lessLoaded = true;
				changeColor();
			});
		}
	};

	return (
		// ... 同上 ...
	);
}

  • 以上方式都没有进行初始化,如需初始化,请自行调用切换主题函数。
  • 上述方式各有利弊,酌情选择,若有更优解求分享。🤣🤣🤣