Slidev 单页作用域样式完全指南:用 `<style>` 让每一页 CSS 彼此隔离,并善用 UnoCSS 嵌套与全局样式
Slidev 单页作用域样式完全指南用style让每一页 CSS 彼此隔离并善用 UnoCSS 嵌套与全局样式【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev在本仓库SlidevPresentation Slides for Developers中每张幻灯片本质上都是一个独立的 Vue 组件因此在 Markdown 中直接书写style块即可定义只作用于当前这一页的 CSS而不会泄漏或污染其他幻灯片。本文以 skills/slidev/references/style-scoped.md 及其官方文档 docs/features/slide-scope-style.md 为骨架结合仓库源码讲解scoped属性是如何被自动注入的、作用域化后选择器会受到哪些限制、如何借助 UnoCSS 指令与嵌套 CSS 简化写法以及何时应当改用全局样式文件。读完你可以在自己的演示项目中熟练、安全地为单页定制样式。基础用法仅作用于当前幻灯片的style在任意一页幻灯片的 Markdown 内容中直接写入style标签即可为该页定义样式。Slidev 的 Markdown 渲染管线和 Vue 组件化机制会确保它只影响当前这一页# This is Red style h1 { color: red; } /style --- # Other slides are not affected效果说明上例中第一页包含style的页面的h1标题会变成红色用---分隔的第二页同样含有h1但不会继承红色样式因为它位于不同的幻灯片组件中。这与 Vue 单文件组件SFC中style scoped的语义一致每页幻灯片会被编译成一个独立的 Vue 组件样式只挂载在属于该组件的内容上。你可以在 packages/slidev/node/vite/vue.ts 看到 Vite 的 Vue 插件将.md与.md?vue一并纳入编译范围这正是每页内容 组件 scoped 样式能成立的基础。默认全作用域自动注入scoped的源码原理文档强调style标签在幻灯片中默认就是作用域化的Scoped by Default你不需要也不应该手动写style scoped虽然写了也会被保留。这一行为由 Slidev 内置的 markdown-it 插件实现源码位于 packages/slidev/node/syntax/scoped.tsconst RE_STYLE_TAG_OPEN /style\b([^]*)/gi const RE_SCOPED_ATTR /\bscoped\b/i export default function MarkdownItStyleScoped(md: MarkdownExit) { const addScoped (html: string) { return html.replace(RE_STYLE_TAG_OPEN, (match, attrs) { if (RE_SCOPED_ATTR.test(attrs)) return match return style scoped${attrs} }) } // 同时改写 html_block 与 html_inline 两条渲染规则 }从源码可以看到三点实现细节插件用正则style\b([^]*)匹配渲染产物的每一个style标签若标签属性中没有scoped就把它改写为style scoped ...如果你显式写了style scoped正则检测到已有scoped属性后原样保留不会重复添加该插件在 packages/slidev/node/syntax/index.ts 的useMarkdownItPlugins()中被无条件注册md.use(MarkdownItStyleScoped)也就是说这是 Slidev 对所有演示的默认行为不需要任何配置开关。配套的单测 packages/slidev/node/syntax/scoped.test.ts 验证了三种关键场景// 1. 自动添加 scoped // style → style scoped it(add scoped to style, ...) // 2. 已有 scoped 的原样保留 // style scoped → style scoped it(preserve existing scoped, ...) // 3. 代码块内的 style 不会被改写被当作源码展示 // html\nstyle... → precodelt;stylegt;... it(not transform in code block, ...)第三个测试用例非常实用如果你用代码围栏fenced code block展示怎么写style围栏内容会被转义成普通文本而不会被执行幻灯片样式也不会受影响。因此你完全可以在某一页演示源码、在另一页真实书写style而互不干扰。注意自动注入的scoped属性负责标记这段样式属于哪个组件真正让选择器带上作用域标记例如 Vue 编译后形如[data-v-xxxxx]的是 Vue SFC 编译器对style scoped的处理。Slidev 的插件与 Vue 编译两级配合才实现默认全作用域。作用域带来的限制为何子组合器.a .b不可用既然样式是全作用域的就必然要遵守 Vue Scoped CSS 的规则。文档明确指出子组合器Child Combinator例如.a .b因作用域机制而无法按预期工作。原理简述与 Vue SFC 的 scoped CSS 一致作用域化编译时选择器会被改写并在末位元素上附加data-v-xxx这类标记例如直接子元素选择器可能被改写成.a .b[data-v-xxx]的形式而中间/父级选择器并不保证带标记导致直接子元素的匹配语义被破坏。所以这类必须命中 DOM 父子结构的选择器无法像在普通全局 CSS 中那样直接套用。如果你确实需要这类匹配实践中通常有两条路重构选择策略改为对当前组件内元素作用更大的类名例如把要修饰的元素包在带 class 的容器里再写后代选择器.wrap .b或者干脆直接给目标元素加一个类将样式移到全局真正依赖 DOM 结构全局生效的规则例如命中布局组件内部结构应当放入全局样式文件见下文何时使用全局样式一节。UnoCSS 加持嵌套 CSS 与--uno指令Slidev 以 UnoCSS 作为内置的原子化 CSS 引擎。因此在style块里除了书写普通 CSS你还能直接使用嵌套 CSS与UnoCSS 指令--uno:/apply等官方文档给出的示例# Slidev Hello **world** style blockquote { strong { --uno: text-teal-500 dark:text-teal-400; } } /style这段代码做的事情可以拆解为三层blockquote { strong { ... } }是嵌套 CSS写法等价于blockquote strong { ... }它只对当前页引用块里的strong生效内层的--uno: text-teal-500 dark:text-teal-400是 UnoCSS 的指令语法相当于内联声明一组工具类text-teal-500以及带dark:变体的dark:text-teal-400深色主题下换用text-teal-400因此无需记住具体 CSS 值整个规则仍处于作用域内不会污染其他页面的引用块。能在style中使用这些语法是因为 Slidev 客户端的 UnoCSS 配置显式开启了指令转换器。参见 packages/client/uno.config.tstransformers: [ transformerDirectives({ enforce: pre }), transformerVariantGroup(), ],其中transformerDirectives负责处理apply、--uno:即--at-apply的别名等指令。基于同一机制你也可以用更常见的apply语法书写style blockquote { strong { apply text-teal-500 dark:text-teal-400; } } /style在apply与--uno:中选择其一即可两者表达的意图相同。正是因为有这条enforce: pre的转换链Slidev 内置在 packages/client/uno.config.ts 中还注册了forward:/backward:之类的方向变体配合深色模式变体你可以在单页作用域内写出非常精细的条件样式。全局样式何时使用styles/index.css如何被加载作用域样式适合本页专属但有些需求天然是全局的例如统一定义配色变量、字体基线或作用于所有页面的基础排版命中由布局组件Layout渲染的结构而非当前页 Markdown 直接生成的内容。官方文档的指引是需要全局样式时请使用项目根目录下的styles/index.css详见 docs/custom/directory-structure.md 的 Style 一节。Slidev 遵循如下目录约定并自动注入到应用根部Pattern: ./style.css | ./styles/index.{css,js,ts}也就是说项目根目录下放置style.css或styles/index.css也支持styles/index.ts/styles/index.js便于用import ./a.css手动管理多个样式文件的引入顺序即可被自动加载。背后的实现位于 packages/slidev/node/virtual/conditional-styles.ts该虚拟模块会遍历项目的每个入口根目录用 import glob 匹配以下候选路径并注入 importjoin(root, styles/index.{ts,js,css}), join(root, styles.{ts,js,css}), join(root, style.{ts,js,css}),如果你需要引入多个 CSS 入口推荐styles/目录 一个入口文件统一管理的结构例如/* styles/index.css */ import ./base.css; import ./code.css;同时注意一个关键区别放在styles/index.css全局里的规则会命中任何页面适合上述全局性需求写在单页 Markdownstyle里的规则只命中当前页适合做局部微调且不用担心与其他页面冲突。实践要点小结围绕单页作用域样式这一主题把最容易被忽略的规则汇总如下方便你在写演示时对照使用场景做法依据 / 参考只想美化当前页在该页 Markdown 中直接写style无需手写scopedscoped.ts 自动注入scoped已有style scoped无需改动编译会原样保留scoped.test.ts在文档里展示style示例代码放进代码围栏如 html不会被当作真实样式执行同上测试用例 3需要.a .b这类子组合器作用域下不可靠请重构选择器或改用全局样式Vue Scoped CSS 语义想用原子化工具类写局部样式在style中使用嵌套 CSS --uno:或applypackages/client/uno.config.ts 的transformerDirectives需要全局性样式/配色变量使用根目录styles/index.css或styles/index.{js,ts}/style.cssconditional-styles.ts 自动注入深入阅读官方专题文档slide-scope-style.md、config-unocss.md最后再强调一次核心结论在 Slidev 中Markdown 内的style永远是页面级作用域的——这是由 packages/slidev/node/syntax/scoped.ts 在渲染阶段统一改写、再由 Vue 编译层施加作用域标记共同保证的默认行为。理解这一点后你就可以放心大胆地用每页一个style的方式做局部精修并把主题级、跨页级的样式交给styles/index.css让整套演示的样式边界清晰、易于维护。【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考