MoreRSS

site icon李瑞豪修改

一名前端開發工程師,hugo-fixit 的創建者,經常在 菠菜眾長 1 和 FixIt2 上撰寫文章和文檔。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

李瑞豪的 RSS 预览

让 Mermaid 图表自动跟随系统深浅色

2025-08-16 12:37:49

featured image

Mermaid 是目前最流行的「文本即图表」渲染库,但它对「系统深浅色自动切换」的支持一直暧昧不明。官方 Roadmap 里偶有提及,却始终没有一个简单、稳定、文档化的 API。
不过社区里已有大量站点(mermaid.live、Obsidian、Notion-like 产品等)实现了丝滑的 Light/Dark 自适应。本文把目前能落地的三条路线一次性梳理出来,并给出最小可运行示例,方便你按需取用。

背景:Mermaid 主题机制与痛点

flowchart LR
 A(["Start"])
 A --> B{"Decision"}
 B -->|Yes| C["Option A"]
 B -->|No| D["Option B"]
flowchart LR
 A(["Start"])
 A --> B{"Decision"}
 B -->|Yes| C["Option A"]
 B -->|No| D["Option B"]
flowchart LR
 A(["Start"])
 A --> B{"Decision"}
 B -->|Yes| C["Option A"]
 B -->|No| D["Option B"]

Mermaid 在初始化时通过 theme 字段选定配色,例如:

1
mermaid.initialize({ theme: 'dark' });

但这条配置 只在首次渲染时生效。当用户在操作系统层面切到 Light/Dark,或者网页本身提供手动开关时,Mermaid 并不会自动重绘。这就导致:

  • 暗黑系统 + 浅色图表 ⇒ 刺眼
  • 浅色系统 + 暗黑图表 ⇒ 同样刺眼

社区 Issue #2644 早在 2022 年就提出希望官方支持 prefers-color-scheme,但至今(2025-08)仍未合入主干。

顺便提一下,主题的切换一般都有两种主流方式:

  1. 利用 prefers-color-scheme 感知系统级别主题变化,matchMedia('(prefers-color-scheme: dark)')change 事件。
  2. 通过 dark class 手动切换,这种方式通常会在 html/body 上设置一个 class 或者 data-theme="dark" 这样。

方案 1️⃣ Reinitialize

切换主题,重新 initialize + 重绘,这是目前来看大多数人的做法。

思路:

  1. 备份原始的 Mermaid code
  2. 设置 startOnLoad: false 避免 Mermaid 自动渲染,然后使用 mermaid.initialize({ theme: theme }) + mermaid.run() 完成初始化。
  3. 重新渲染时移除 data-processed,替换 .mermaid 的内容为原始 Mermaid code,使用 mermaid.initialize({ theme: newTheme }) + mermaid.run() 重新渲染

这份做法在 Issue #1945 里有完整代码,下面给出精简版:

优点是:

  • 利用现有官方 API

缺点是:

  • 每次切换都需要重新渲染,大图会闪一下
  • 如果页面里图表很多,性能损耗不可忽视
  • 还有异步处理带来的一系列问题

方案 2️⃣ CSS 滤镜

作为支持 prefers-color-scheme 和手动切换的一种简单的解决方案,可以使用 CSS 反转滤镜(inverthue-rotate 等)来实现。

例如:

1
2
3
4
5
6
7
8
9
[data-theme='dark'] .mermaid {
 filter: invert(0.88);
}

@media (prefers-color-scheme: dark) {
 :root:not([data-theme='light']) .mermaid {
 filter: invert(0.88);
 }
}

这条技巧是我在 Issue #2644 中看到的。

优点是:

  • 一行 CSS,无 JS
  • 不重新渲染,零延迟

缺点是:

  • 只是「反色」,并非官方暗黑主题
  • 对于红色、绿色等语义色会完全失真
  • 如果背景不是纯黑纯白,观感会很奇怪

由于太过简单,效果也很粗糙,适合做 Demo 或内部工具,不建议面向终端用户。

方案 3️⃣ 配置热替换

mermaid.live 站点能在用户切主题时瞬间完成切换,且颜色完全与官方暗黑主题对齐。从 源码 和 DevTools 推测,它大概做了三件事:

  1. 自己维护一份 themeCSS 字符串(而非仅用名字 'dark'
  2. prefers-color-scheme 变化时,直接把新的 CSS 注入到 <svg> 里的 <style> 节点
  3. 通过 mermaid.render('id', code) 拿到 SVG string 后,用正则替换掉旧 <style>,再 DOMParser 塞回页面

Issue #2644 中有提到,在 mermaid.live 中,目前是根据配色方案在配置中切换主题。

优点:

  • 无闪烁
  • 完全复用官方配色

缺点:

  • 需要内部维护主题 CSS,Mermaid 每升级一次都要同步
  • 实现细节依赖私有 API,官方一旦改动就会崩

如果你极度追求体验,可以照着源码抄一份,但要做好长期维护的心理准备。

#2644 中提到,在 mermaid.live 中,目前正在根据配色方案在配置中切换主题。这种方案暂时未找到更多的细节披露。我又懒得去深扒 mermaid.live 的实现细节。

方案 4️⃣ Reinitialize + CSS 结合

在我的实践中,FixIt 主题 是通过 data-theme 的方式手动切换网站主题的,我一开始走的思路和方案 1️⃣总体一致,为了处理这个方案的缺点,我多次迭代,有了最终的版本:

首先通过 type=module 引入 Mermaid:

1
2
3
4
5
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.js';
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml/dist/mermaid-zenuml.esm.min.mjs';
await mermaid.registerExternalDiagrams([zenuml]);
mermaid.startOnLoad = false;
window.mermaid = mermaid;

然后在主题的切换逻辑中处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
initMermaid() {
 if (!this.config.mermaid) return;

 const themes = this.config.mermaid.themes ?? ['default', 'dark'];
 let processing = false;
 let delayTask = null;

 const loadMermaid = async () => {
 processing = true;
 // https://mermaid.js.org/config/schema-docs/config.html
 window.mermaid.initialize({
 startOnLoad: false,
 darkMode: this.isDark,
 theme: this.isDark ? themes[1] : themes[0],
 securityLevel: this.config.mermaid.securityLevel,
 look: this.config.mermaid.look,
 fontFamily: this.config.mermaid.fontFamily,
 altFontFamily: this.config.mermaid.fontFamily
 });
 await window.mermaid.run({
 querySelector: '.mermaid',
 suppressErrors: true,
 });
 processing = false;
 if (delayTask && typeof delayTask === 'function') {
 delayTask();
 delayTask = null;
 // console.log('Delayed task executed');
 }
 };

 const reloadMermaid = async () => {
 await this.util.forEach(document.querySelectorAll('.mermaid[data-processed]'), (el) => {
 el.removeAttribute('data-processed');
 el.parentElement.replaceChild(el.nextElementSibling.content.cloneNode(true), el);
 });
 await loadMermaid();
 };

 const waitForMermaid = () => {
 return new Promise((resolve) => {
 const timer = setInterval(() => {
 if (window.mermaid && window.mermaid.initialize) {
 clearInterval(timer);
 resolve();
 }
 }, 100);
 });
 };

 waitForMermaid().then(() => {
 loadMermaid();
 this.switchThemeEventSet.add(() => {
 if (processing) {
 console.warn('Mermaid is still processing, delaying the reload.');
 delayTask = reloadMermaid;
 return;
 }
 // console.log('reload immediately');
 reloadMermaid().catch(console.error);
 });

 this.beforeprintEventSet.add(() => {
 // Optionally set theme to 'neutral' for printing if required
 });
 })
}

为了缓解闪屏问题,利用 CSS 增加一个 Loading 效果,过度一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.mermaid {
 position: relative;
 overflow: hidden !important;

 &[data-processed] {
 text-align: center;
 }

 &:not([data-processed])::before {
 content: '';
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 backdrop-filter: blur(0.5rem);
 background-position: center;
 background-repeat: no-repeat;
 background-image: var(#{$rootPrefix}loading-img);
 background-size: 60px;
 }

 svg {
 max-width: 100%;
 height: auto; 
 }
}

说实话,虽然勉强达到了目的,这里的 delayTaskwaitForMermaid() 算得上妥妥的 Dirty Hack。也属实是无奈之举。


睡觉前我灵光乍现,为了避免每次切换主题时都要重新渲染 Mermaid 图表,我尝试一开始直接把 Mermaid 的 Light 和 Dark 主题的两个图都渲染了,然后由 data-theme 控制显示哪个图表。

尝试后发现 Mermaid 在渲染图时,如果这个元素是 display: none; 则会报错。

于是,我改成初始化时只渲染 Light/Dark SVG,等到主题切换时才渲染 Dark/Light SVG,并隐藏另一个 SVG。

这样同一个图只需要渲染两次,后续多次主题切换,就能够通过 CSS 非常丝滑的控制切换了,狠狠戳这里查看效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Mermaid Light/Dark Mode switching</title>
 <style>
 .diagrams-container {
 display: flex;
 justify-content: space-evenly;
 }
 .mermaid-container {
 .mermaid-dark {
 display: none;
 }

 [data-theme="dark"] & {
 .mermaid {
 display: none;
 }
 .mermaid-dark {
 display: block;
 }
 }
 }
 </style>
</head>
<body>
 <script>
 function toggleTheme() {
 const currentTheme = document.body.dataset.theme;
 const newTheme = currentTheme === 'dark' ? 'default' : 'dark';
 document.body.dataset.theme = newTheme;
 }
 </script>
 <button id="toggler">切换主题</button>
 <div class="diagrams-container">
 <div class="mermaid-container">
 <pre class="mermaid">graph TD;
 A[开始] --> B{是否完成?};
 B -- 是 --> C[结束];
 B -- 否 --> D[继续];
 D --> B;
 </pre>
 <pre class="mermaid-dark">graph TD;
 A[开始] --> B{是否完成?};
 B -- 是 --> C[结束];
 B -- 否 --> D[继续];
 D --> B;
 </pre>
 </div>
 </div>

 <script type="module">
 import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';

 async function loadMermaid(theme) {
 const isDark = theme === 'dark';
 const querySelector = isDark ? '.mermaid-dark' : '.mermaid';
 mermaid.initialize({ startOnLoad: false, darkMode: isDark, theme });
 // Mermaid run 函数内部会跳过 data-processed 的元素,所以这样不会重复渲染
 await mermaid.run({ querySelector });
 }

 await loadMermaid('default')

 document.getElementById('toggler').addEventListener('click', () => {
 const currentTheme = document.body.dataset.theme || 'default';
 const newTheme = currentTheme === 'dark' ? 'default' : 'dark';
 document.body.dataset.theme = newTheme;
 loadMermaid(newTheme);
 });
 </script>
</body>
</html>

小结与选型建议

毋庸置疑,想要完美的体验,方案 3️⃣ 是最优选择, 方案 4️⃣ 作为方案 1️⃣ 的升级版,整体体验也相对较好。

方案 实现成本 体验 是否官方可维护
Reinitialize 中等(闪屏)
CSS invert 极低
mermaid.live 热替换 极佳
Reinitialize + CSS 较好
  • 内部文档 / 博客 / 小工具 ⇒ 方案 4️⃣
  • 赶时间的 MVP ⇒ 方案 2️⃣
  • 面向 C 端、对体验极端敏感 ⇒ 方案 3️⃣,但要准备长期跟进

黑盒出关・三把钥匙定江湖

2025-08-05 11:31:52

featured image

江湖传言,西土浏览器界有隐世高人,名曰“原生老祖”。老祖膝下三徒,各执一柄钥匙,开得了黑盒,锁得住乾坤。今日大话,诸位英雄且把耳洞放大,听我一一道来!

第一折・黑盒现世

昔日,React 少侠仗虚拟 DOM 之剑,Vue 剑仙携响应式绫罗,双雄争霸,血溅前端。

忽一日,电闪雷鸣,Chrome 山、Firefox 谷、Safari 崖三地同时金光乍现——一只乌漆嘛黑的小盒破空而出,盒上无门无派,只刻八字:

不拜山头,自成一派。

盒盖一开,三股真气冲天而起,惊得 React 剑锋一抖,Vue 绫罗乱颤。众修士齐呼:

“此乃何物?”

盒中悠悠传出一声:

“Web Components——浏览器亲儿子,江湖诨号:黑盒扫地僧。”


第二折・三把钥匙镇山门

铸兵符・Custom Elements

凡得此符者,可铸自家神兵。

1
<my-dog food="hotpot" mood="happy"></my-dog>

今日起,标签随你姓,语义随你编,浏览器照单全收,不查户口。

影分身・Shadow DOM

此术一开,样式、DOM、事件皆入黑屋,外头 CSS 千军万马,休想踏进半步。

“兄弟,你的 !important 呢?”
“抱歉,进了影分身,天王老子也得排队。”

袖里乾坤・HTML Templates & Slots

袖中一抖,模板千军万马;插槽轻点,内容各就各位。

无需编译,无需打包,一颗 <template> 漂洋过海,落地即插即用。

这是真正意义上的“一次编写,到处运行”——比 Java 当年喊的口号还真。


第三折・风云再起

黑盒既出,江湖格局瞬变:

  • 微前端:Vue2、Vue3、React18、Angular 同屏共舞,互不打脸。
  • 设计系统:按钮、输入框、LOGO 化身“原子暗器”,任何门派伸手即取。
  • 长尾奇袭:Chrome 插件、VS Code 插件、微信小程序、低代码山寨,皆呼“真香”。
  • 长寿秘籍:框架蜜月三年,黑盒随浏览器升级十年,npm 弃坑它不弃。

第四折・范式转移・浏览器登堂入室

当日头西斜,江湖忽然风起。众侠回头一看,浏览器老馆主身披龙袍、脚踏赤霄,一步跨上金銮殿。

旧朝遗诏:从“虚拟机”到“原生执政”

过去二十年,前端史是一部“夺权史”:

  • jQuery 夺的是 DOM 的刀;
  • Angular 夺的是模块的印;
  • React 夺的是渲染的剑;
  • Vue 夺的是状态的符。

四把大印加身,浏览器反成“空壳天子”。

而今,老馆主一声令下:“朕即框架,诸卿退班!”

三权分立:新标准下的江湖秩序

权柄 归属 职责 口号
立法权 WHATWG/W3C 写圣旨(HTML、CSS、DOM 标准) “凡入典章,万世不易。”
执法权 浏览器内核 掌御林军(渲染管线、沙箱、安全) “有朕一日,天下无刀兵。”
行政权 开发者 & 工具链 管民生(DX、脚手架、调试器) “百姓只用敲锣,不必造炮。”

Web Components 正是老馆主钦点的 “锦衣卫”

去框架化的三重暗涌

  1. 编译终点迁移

    昨日:Babel/Vite → React/Vue 运行时;

    今日:Babel/Vite → Web Components 原生指令。

    框架退居“DX 大臣”,不再染指最终字节码。

  2. 生态颗粒度下沉

    UI 库不再打包成“全家桶”,而是 CDN 单文件组件:

    1
    
    https://unpkg.com/@ui/button.js

    按需即取,HTTP 缓存即版本管理,npm install 沦为可选项。

  3. 生命周期归一

    React 的 useEffect、Vue 的 onMounted、Svelte 的 onMount

    最终都得翻译成同一套浏览器生命周期:

    connectedCallbackdisconnectedCallbackattributeChangedCallback

    框架语法糖越甜,底层 API 越收敛,直至“糖衣”可有可无。

未来图景:十年后的登基大典

  • 2027:浏览器内置 signals 提案落地,状态管理回归原生;
  • 2029:CSS @scope + @state 双剑合璧,Shadow DOM 自带响应式;
  • 2031:HTTP/4 多路复用 + Import Map 2.0,让“一行 <script type=importmap> 即 CDN 全图”成为标配。

届时,开发者只需写:

1
2
<my-app></my-app>
<script type="module" src="app.js"></script>

框架?

“哦,那是旧朝遗老,偶尔进宫讲史罢了。”

老馆主抚须长笑:
“昔日你们借我地基起高楼,今日我把高楼收归国有。

范式逆流,不是革命,是回家。”


尾声・血雨腥风

“老衲不挑框架,不拒工具,但有一语相赠:
十年之后,你迁移的是框架,还是我?”

江湖血雨腥风,黑盒已开。

要么守着旧山门,十年后再为迁移埋单;

要么此刻随扫地僧下山,让代码像 HTML 一样长青。

下回分解

“5 分钟,一指定乾坤——纯原生撸一只可复用计数器,再扔进 React、Vue、Svelte 乱炖!”


注意

哈哈哈哈哈哈哈,以抖机灵的形式简单聊了一下 Web Components 的发展历程和未来趋势。

我对 Web Components 充满了浓厚的兴趣,决定花点时间研究研究。
剩余的内容在 Web Components 系列文章 将会持续更新,敬请期待!

Code Playground

2025-08-04 11:37:24

以下是常见的在线代码演示和开发环境服务,适合不同场景使用:

名称 特点与适用场景 网址
CodeSandbox 支持前后端全栈开发,内置 Docker,支持 React、Vue、Node、Python 等,适合复杂项目 https://codesandbox.io
CodePen 专注于前端小效果演示,社区活跃,适合分享和展示 HTML/CSS/JS 片段 https://codepen.io
JSFiddle 轻量级前端代码片段运行环境,适合快速测试和分享小 demo https://jsfiddle.net
JS Bin 类似 JSFiddle,支持实时协作和分享,适合调试和教学 https://jsbin.com
Playcode.io 无需登录即可运行 JS/TS,界面类似本地 IDE,适合快速原型开发 https://playcode.io
Replit 支持多语言(如 Python、Java、C++),适合教育用途和全栈开发 https://replit.com
Gitpod 基于 VS Code 的云端 IDE,适合 GitHub 项目快速启动和协作 https://gitpod.io
码上掘金 国内版轻量 Playground,支持 React、Vue 等框架,适合中文用户 https://code.juejin.cn

A custom web component that embeds caniuse.com browser compatibility data for a specific feature.

2025-07-22 11:40:45

<caniuse-embed> Element

npm version License

A lightweight, customizable web component that embeds caniuse.com browser compatibility data for specific web features. Built with Lit and designed to seamlessly integrate into any web project.

🌟 Live Demo

✨ Features

  • 🎯 Easy Integration: Drop-in web component that works with any framework or vanilla HTML
  • 🎨 Theme Support: Auto, light, and dark themes that adapt to your design
  • 📱 Responsive: Automatically adjusts height based on content
  • Lightweight: Built with Lit for minimal bundle size
  • 🛠️ Customizable: Configure data source, time range, and appearance
  • 🔒 Type Safe: Full TypeScript support with comprehensive type definitions

🚀 Quick Start

CDN (Recommended)

Add the script tag to your HTML:

1
<script src="https://unpkg.com/@cell-x/caniuse-embed-element/dist/caniuse-embed-element.iife.js"></script>

Then use the component:

1
<caniuse-embed feature="css-grid"></caniuse-embed>

NPM Installation

1
npm install @cell-x/caniuse-embed-element
1
import '@cell-x/caniuse-embed-element'

📖 Usage Examples

Basic Usage

1
<caniuse-embed feature="css-grid"></caniuse-embed>

With Custom Configuration

1
2
3
4
5
6
7
<caniuse-embed
 feature="flexbox"
 theme="dark"
 past="3"
 future="2"
 origin="https://caniuse.lruihao.cn"
></caniuse-embed>

Framework Integration

Here’s an example using Vue.js. For more framework integration examples, see FRAMEWORK_INTEGRATION.md.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<script setup>
import '@cell-x/caniuse-embed-element'
</script>

<template>
 <div>
 <caniuse-embed
 feature="css-grid"
 theme="dark"
 :past="3"
 :future="2"
 />
 </div>
</template>

⚙️ API Reference

Attributes/Properties

Attribute Type Default Description
feature string '' Required. The caniuse feature identifier (e.g., ‘css-grid’, ‘flexbox’)
past 0 - 5 2 Number of past browser versions to display
future 0 - 3 1 Number of future browser versions to display
origin string 'https://caniuse.lruihao.cn' Base URL of the caniuse embed service
theme 'auto' | 'light' | 'dark' 'auto' Color theme for the embedded content
loading 'eager' | 'lazy' 'lazy' Loading strategy for the iframe (eager or lazy)
meta string auto-generated Unique identifier for the embed instance

Finding Feature Names

Feature names correspond to the identifiers used on caniuse.com. You can find them in:

  • The URL path: https://caniuse.com/css-grid → feature name is css-grid
  • The search results on caniuse.lruihao.cn
  • The caniuse-db repository

Common Feature Examples

  • css-grid - CSS Grid Layout
  • flexbox - Flexible Box Layout
  • arrow-functions - Arrow Functions
  • webp - WebP Image Format
  • css-variables - CSS Custom Properties
  • async-functions - Async/Await Functions

CSS Classes

  • .ciu-embed-iframe - The embedded iframe element
  • .ciu-embed-empty - Empty state when no feature is specified

🌐 Browser Support

This web component works in all modern browsers that support:

  • Custom Elements v1
  • Shadow DOM v1
  • ES2015+ features

🔧 Development

Prerequisites

  • Node.js 20+
  • pnpm 10+

Setup

1
2
3
4
5
6
7
8
9
# Clone the repository
git clone https://github.com/Lruihao/caniuse-embed-element.git
cd caniuse-embed-element

# Install dependencies
pnpm install

# Start development server
pnpm dev

Build

1
2
3
4
5
6
7
# Build all formats
pnpm build:all

# Build specific formats
pnpm build:lib # ES modules and types
pnpm build:iife # IIFE for CDN usage
pnpm build # Demo build

Scripts

  • pnpm dev - Start development server
  • pnpm build - Build demo
  • pnpm build:lib - Build library (ES modules + types)
  • pnpm build:iife - Build IIFE bundle for CDN
  • pnpm build:all - Build all formats
  • pnpm lint - Run ESLint
  • pnpm preview - Preview built demo

📦 Distribution

The package provides multiple build formats:

  • ES Modules (dist/) - For modern bundlers
  • IIFE Bundle (dist/caniuse-embed-element.iife.js) - For CDN usage
  • TypeScript Definitions (dist/types/) - For TypeScript projects

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License. See the LICENSE file for details.

🙏 Acknowledgements


Made with ❤️ by Lruihao

CSS @layer:构建更高效、更可维护的样式层级

2025-06-18 10:35:47

在现代前端开发中,CSS 的样式管理一直是一个令人头疼的问题。随着项目的不断扩展,样式规则的冲突、覆盖以及维护成本的增加,都给开发者带来了巨大的挑战。幸运的是,CSS 的 @layer 规则为我们提供了一种全新的解决方案,帮助我们更好地管理样式层级,提升代码的可维护性和可读性。本文将深入探讨 @layer 的背景、作用以及语法,带你一探究竟。

1 @layer 的背景

在 CSS 的发展历程中,样式的优先级规则一直是核心概念之一。默认情况下,CSS 样式按照选择器的优先级(如内联样式 > ID 选择器 > 类选择器 > 元素选择器)以及代码的书写顺序来决定最终的样式效果。然而,这种简单的优先级规则在大型项目中常常会引发问题:

  1. 样式冲突:多个样式规则可能同时作用于同一个元素,导致样式冲突,开发者需要不断调整选择器的优先级来解决问题。
  2. 维护困难:随着项目的复杂度增加,样式文件变得庞大且难以维护。开发者很难快速定位和修改特定的样式规则。
  3. 组件化开发的挑战:在组件化开发中,不同组件的样式可能会相互干扰,导致样式管理混乱。

为了解决这些问题,CSS 工作组引入了 @layer 规则。@layer 允许开发者显式地定义样式的层级关系,从而更好地组织和管理样式规则。

2 @layer 的作用

2.1 定义样式层级

@layer 的核心作用是允许开发者显式地定义样式的层级关系。通过将样式规则分层,我们可以明确地控制样式的优先级顺序。例如,我们可以将基础样式定义在较低的层级,而将特定组件的样式定义在较高的层级,从而避免样式冲突。

1
2
3
4
5
6
7
@layer base {
 body {
 font-family: Arial, sans-serif;
 margin: 0;
 padding: 0;
 }
}

2.2 提升代码可维护性

使用 @layer 可以让样式文件的结构更加清晰。开发者可以按照功能或模块将样式规则分层,便于后续的修改和扩展。同时,分层的样式规则也更容易被团队成员理解和协作。

2.3 支持组件化开发

在组件化开发中,@layer 可以帮助我们更好地隔离组件的样式。每个组件可以定义自己的样式层级,从而避免组件之间的样式相互干扰。这大大提升了组件的复用性和可维护性。

例如:

1
2
3
4
5
6
7
8
9
@layer components {
 .cell-button {
 background-color: blue;
 color: white;
 padding: 10px;
 border: none;
 cursor: pointer;
 }
}

在业务代码中,我们可以无视组件 CSS 的优先级,直接进行重置:

1
2
3
.cell-button {
 background-color: red; /* 覆盖组件样式 */
}

眼见为实:

可以理解为 @layer 定义的层级会整体下降一个优先级,这样便于分开业务代码和组件代码的样式管理。

3 @layer 的语法

3.1 基本语法

1
2
3
@layer layer-name? {rules}
@layer layer-name;
@layer layer-name, layer-name, layer-name;

简而言之,@layer 规则可以用来定义一个样式层级或者改变现有层级的优先级

  • @layer layer-name? {rules}; 这种形式用于定义一个新的样式层级,如果名称为空,则为匿名层级。
  • @layer 后接一个或多个层级名称,用于指定样式层级的优先级顺序,按照前后顺序。

3.2 让整个 CSS 文件变成 @layer

如果我们希望将整个 CSS 文件作为一个层级,可以使用以下语法:

@import 中使用:

1
@import (utilities.css) layer(utilities);

<link> 元素引用 (*):

警告

该用法有待考证,在 MDN 上尚未找到明确文档。

1
2
3
<link rel="stylesheet" href="utilities.css" layer="utilities">
<!-- 样式引入到一个匿名级联层中 -->
<link rel="stylesheet" href="utilities.css" layer>

3.3 嵌套层级

@layer 也支持嵌套定义,这使得我们可以在一个层级中进一步细分样式规则。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@layer base {
 @layer typography {
 h1 {
 font-size: 24px;
 }
 p {
 font-size: 16px;
 }
 }
}

在嵌套层级中,外层层级的优先级低于内层层级。

多嵌套语法下的优先级:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@layer A {
 p { color: red; }
 @layer B {
 p { color: green; }
 }
}
@layer C {
 p { color: orange; }
 @layer D {
 p { color: blue; }
 }
}

其中的优先级大小是这样的:C > C.D > A > A.B

另外,嵌套语法还支持使用级联写法简化。

例如,普通内外嵌套写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@layer outer {
 button {
 width: 100px;
 height: 30px; 
 }
 @layer inner {
 button {
 height: 40px;
 width: 160px;
 } 
 }
}

上面的内外嵌套语法还可以写成下面这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@layer outer {
 button {
 width: 100px;
 height: 30px; 
 }
}
@layer outer.inner {
 button {
 height: 40px;
 width: 160px;
 } 
}

4 @layer 的兼容性

5 参考

Vue.js History 模式下的 NGINX 配置与 API 代理

2025-06-11 10:05:36

在使用 Vue.js 开发前端应用时,开启 history 模式可以让你的路由更加友好。然而,在部署应用时,需要正确配置 NGINX,以支持前端路由和 API 请求。本文将详细介绍如何配置 NGINX,使其能够处理 Vue 应用的 history 模式,并设置 API 代理。

1 前提条件

在开始之前,请确保你已经完成以下步骤:

  • 安装并配置好 NGINX。
  • 完成 Vue.js 应用的开发,并使用 npm run build 命令打包应用。

2 NGINX 配置示例

以下是一个典型的 NGINX 配置示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
server {
 listen 80; # 监听80端口
 server_name your_domain.com; # 替换为你的域名

 location / {
 root /path/to/your/dist; # 指向打包后的文件夹
 try_files $uri $uri/ /index.html; # 尝试访问指定文件,如果找不到则重定向到index.html
 }

 # API 代理设置
 location /api/ {
 proxy_pass http://your_api_server; # 替换为你的 API 服务器地址
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
 }
}

2.1 配置说明

  • listen: 指定 NGINX 监听的端口,通常是 80(HTTP)。
  • server_name: 配置你的域名。
  • root: 指向 Vue 应用的打包输出目录,通常是 dist 文件夹。
  • try_files: 尝试访问请求的文件,如果不存在,则返回 index.html,以允许 Vue Router 处理前端路由。
  • location /api/: 所有以 /api/ 开头的请求会被代理到指定的 API 服务器。
  • proxy_pass: 设置 API 请求转发到的后端服务器地址。

2.2 部署步骤

  1. 打包 Vue 应用

    1
    
    npm run build
  2. 上传内容:将 dist 文件夹的内容上传到服务器的指定路径。

  3. 修改 NGINX 配置:编辑 NGINX 配置文件,通常在 /etc/nginx/sites-available/default/etc/nginx/nginx.conf

  4. 检查配置:检查 NGINX 配置是否有语法错误:

    1
    
    sudo nginx -t
  5. 重新加载 NGINX

    1
    
    sudo systemctl reload nginx

3 总结

通过上述配置,你的 Vue.js 应用将可以在 NGINX 上正常运行,并支持 history 模式的路由。同时,所有以 /api/ 开头的请求将被有效地代理到后端服务器。这样,前端与后端的交互就更加流畅自然。

希望这篇文章能帮助你顺利部署 Vue 应用!如有任何问题,欢迎留言讨论。