样式
了解如何为你的 Nuxt 应用添加样式。
Nuxt 在样式方面非常灵活。你可以编写自己的样式,或引用本地及外部样式表。 你可以使用 CSS 预处理器、CSS 框架、UI 库以及 Nuxt 模块来为应用添加样式。
本地样式表(Local Stylesheets)
如果你在编写本地样式表,最自然的位置是 app/assets/ 目录。
在组件内导入(Importing Within Components)
你可以在页面、布局和组件中直接导入样式表。
你可以使用 JavaScript import,或 CSS @import 语句。
<script>
// Use a static import for server-side compatibility
import '~/assets/css/first.css'
// Caution: Dynamic imports are not server-side compatible
import('~/assets/css/first.css')
</script>
<style>
@import url("~/assets/css/second.css");
</style>
CSS 属性(The CSS Property)
你也可以使用 Nuxt 配置中的 css 属性。
样式表最自然的位置是 app/assets/ 目录。然后你可以引用它的路径,Nuxt 会将其包含到应用的所有页面中。
export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
})
使用字体(Working With Fonts)
将本地字体文件放到 public/ 目录中,例如 public/fonts。然后就可以在样式表中使用 url() 引用它们。
@font-face {
font-family: 'FarAwayGalaxy';
src: url('/fonts/FarAwayGalaxy.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
然后在样式表、页面或组件中按名称引用你的字体:
<style>
h1 {
font-family: 'FarAwayGalaxy', sans-serif;
}
</style>
通过 NPM 分发的样式表(Stylesheets Distributed Through NPM)
你也可以引用通过 npm 分发的样式表。我们以流行的 animate.css 库为例。
npm install animate.css
yarn add animate.css
pnpm install animate.css
bun install animate.css
deno install npm:animate.css
然后你就可以在页面、布局和组件中直接引用它:
<script>
import 'animate.css'
</script>
<style>
@import url("animate.css");
</style>
该包也可以作为字符串在 Nuxt 配置的 css 属性中引用。
export default defineNuxtConfig({
css: ['animate.css'],
})
外部样式表(External Stylesheets)
你可以通过在 nuxt.config 文件的 head 部分添加 link 元素,将外部样式表引入应用。你可以使用不同的方法实现这一点。注意本地样式表也可以通过这种方式引入。
你可以使用 Nuxt 配置的 app.head 属性来操作 head:
export default defineNuxtConfig({
app: {
head: {
link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }],
},
},
})
动态添加样式表(Dynamically Adding Stylesheets)
你可以使用 useHead composable 在代码中动态设置 head 里的值。
阅读更多useHead({
link: [{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }],
})
Nuxt 底层使用 unhead,你可以参考它的完整文档。
使用 Nitro 插件修改渲染后的 Head(Modifying The Rendered Head With A Nitro Plugin)
如果你需要更高级的控制,可以通过 hook 拦截渲染后的 HTML 并以编程方式修改 head。
在 ~~/server/plugins/my-plugin.ts 中创建一个插件,如下所示:
import { definePlugin } from 'nitro'
export default definePlugin((nitro) => {
nitro.hooks.hook('render:html', (html) => {
html.head.push('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">')
})
})
外部样式表是阻塞渲染的资源(render-blocking):在浏览器渲染页面之前,它们必须被加载和处理。包含不必要的大样式表的网页渲染时间会更长。你可以在 web.dev 上阅读更多内容。
使用预处理器(Using Preprocessors)
要使用 SCSS、Sass、Less 或 Stylus 等预处理器,请先安装它。
npm install -D sass
npm install -D less
npm install -D stylus
样式表最自然的位置是 app/assets 目录。
然后你可以使用预处理器的语法在 app.vue(或布局文件)中导入源文件。
<style lang="scss">
@use "~/assets/scss/main.scss";
</style>
或者,你也可以使用 Nuxt 配置的 css 属性。
export default defineNuxtConfig({
css: ['~/assets/scss/main.scss'],
})
如果你需要在预处理文件中注入代码,例如包含颜色变量的 Sass partial,可以使用 Vite 的 preprocessors options。
在 app/assets 目录中创建一些 partial:
$primary: #49240F;
$secondary: #E4A79D;
$primary: #49240F
$secondary: #E4A79D
然后在你的 nuxt.config 中:
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "~/assets/_colors.scss" as *;',
},
},
},
},
})
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
sass: {
additionalData: '@use "~/assets/_colors.sass" as *\n',
},
},
},
},
})
Nuxt 默认使用 Vite。如果你想改用 webpack,请参考各个预处理器 loader 的文档。
预处理器 Workers(实验性)(Preprocessor Workers (Experimental))
Vite 提供了一个实验性选项,可以加速预处理器的使用。
你可以在 nuxt.config 中启用它:
export default defineNuxtConfig({
vite: {
css: {
preprocessorMaxWorkers: true, // number of CPUs minus 1
},
},
})
单文件组件(SFC)样式(Single File Components (SFC) Styling)
Vue 和 SFC 最棒的地方之一,就是它在处理样式方面非常自然。你可以直接在组件文件的 style 块中编写 CSS 或预处理器代码,因此你无需使用 CSS-in-JS 之类的方案就能获得极佳的开发者体验。不过,如果你希望使用 CSS-in-JS,可以找到支持它的第三方库和模块,例如 pinceau。
你可以参考 Vue 文档 获取关于在 SFC 中为组件添加样式的完整参考。
类与样式绑定(Class And Style Bindings)
你可以利用 Vue SFC 特性,通过 class 和 style 属性为组件添加样式。
<script setup lang="ts">
const isActive = ref(true)
const hasError = ref(false)
const classObject = reactive({
'active': true,
'text-danger': false,
})
</script>
<template>
<div
class="static"
:class="{ 'active': isActive, 'text-danger': hasError }"
/>
<div :class="classObject" />
</template>
<script setup lang="ts">
const isActive = ref(true)
const error = ref(null)
const classObject = computed(() => ({
'active': isActive.value && !error.value,
'text-danger': error.value && error.value.type === 'fatal',
}))
</script>
<template>
<div :class="classObject" />
</template>
<script setup lang="ts">
const isActive = ref(true)
const errorClass = ref('text-danger')
</script>
<template>
<div :class="[{ active: isActive }, errorClass]" />
</template>
<script setup lang="ts">
const activeColor = ref('red')
const fontSize = ref(30)
const styleObject = reactive({ color: 'red', fontSize: '13px' })
</script>
<template>
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }" />
<div :style="[baseStyles, overridingStyles]" />
<div :style="styleObject" />
</template>
更多信息请参阅 Vue 文档。
使用 v-bind 的动态样式(Dynamic Styles With v-bind)
你可以在 style 块中使用 v-bind 函数引用 JavaScript 变量和表达式。 该绑定是动态的,也就是说,如果变量的值发生变化,样式也会随之更新。
<script setup lang="ts">
const color = ref('red')
</script>
<template>
<div class="text">
hello
</div>
</template>
<style>
.text {
color: v-bind(color);
}
</style>
作用域样式(Scoped Styles)
scoped 属性允许你以隔离的方式为组件添加样式。使用该属性声明的样式只会应用于当前组件。
<template>
<div class="example">
hi
</div>
</template>
<style scoped>
.example {
color: red;
}
</style>
CSS Modules
你可以使用 CSS Modules 及其 module 属性。通过注入的 $style 变量访问它。
<template>
<p :class="$style.red">
This should be red
</p>
</template>
<style module>
.red {
color: red;
}
</style>
预处理器支持(Preprocessors Support)
SFC 的 style 块支持预处理器语法。Vite 内置支持 .scss、.sass、.less、.styl 和 .stylus 文件,无需配置。你只需先安装它们,它们就能在 SFC 中通过 lang 属性直接使用。
<style lang="scss">
/* Write scss here */
</style>
<style lang="sass">
/* Write sass here */
</style>
<style lang="less">
/* Write less here */
</style>
<style lang="stylus">
/* Write stylus here */
</style>
你可以参考 Vite CSS 文档 和 @vitejs/plugin-vue 文档。 对于 webpack 用户,请参阅 vue loader 文档。
使用 PostCSS(Using PostCSS)
Nuxt 内置了 postcss。你可以在 nuxt.config 文件中进行配置。
export default defineNuxtConfig({
postcss: {
plugins: {
'postcss-nested': {},
'postcss-custom-media': {},
},
},
})
为了在 SFC 中获得正确的语法高亮,你可以使用 postcss lang 属性。
<style lang="postcss">
/* Write postcss here */
</style>
默认情况下,Nuxt 已经预配置了以下插件:
- postcss-import:改进
@import规则 - postcss-url:转换
url()语句 - autoprefixer:自动添加浏览器厂商前缀
- cssnano:压缩与清除
利用布局实现多套样式(Leveraging Layouts For Multiple Styles)
如果你需要为应用的不同部分使用完全不同的样式,可以使用布局。 为不同的布局使用不同的样式。
<template>
<div class="default-layout">
<h1>Default Layout</h1>
<slot />
</div>
</template>
<style>
.default-layout {
color: red;
}
</style>
第三方库与模块(Third Party Libraries And Modules)
Nuxt 在样式方面不做限定,为你提供了丰富多样的选择。你可以使用任何你喜欢的样式工具,例如流行的库 UnoCSS 或 Tailwind CSS。
社区和 Nuxt 团队开发了大量 Nuxt 模块,让集成更轻松。 你可以在网站的 模块章节 中发现它们。 以下是一些帮助你入门的模块:
- UnoCSS:即时按需的原子化 CSS 引擎
- Tailwind CSS:实用优先(utility-first)的 CSS 框架
- Fontaine:字体度量回退(font metric fallback)
- Pinceau:可适配的样式框架
- Nuxt UI:面向现代 Web 应用的 UI 库
- Panda CSS:在构建时生成原子化 CSS 的 CSS-in-JS 引擎
Nuxt 模块开箱即用地为你提供良好的开发者体验,但请记住:如果你喜欢的工具没有对应的模块,并不意味着你不能在 Nuxt 中使用它!你可以为自己的项目自行配置。根据工具的不同,你可能需要使用 Nuxt 插件 和/或编写自己的模块。如果愿意,欢迎与社区分享它们!
轻松加载 Web 字体(Easily Load Webfonts)
你可以使用 Nuxt Google Fonts 模块 来加载 Google Fonts。
如果你在使用 UnoCSS,请注意它自带 web fonts presets,可方便地从常见提供商(包括 Google Fonts 等)加载字体。
进阶(Advanced)
过渡(Transitions)
Nuxt 自带与 Vue 相同的 <Transition> 元素,并且支持实验性的 View Transitions API。
字体高级优化(Font Advanced Optimization)
我们建议使用 Fontaine 来减少你的 CLS。如果你需要更高级的方案,可以考虑创建一个 Nuxt 模块来扩展构建过程或 Nuxt 运行时。
LCP 高级优化(LCP Advanced Optimizations)
你可以采取以下措施来加速全局 CSS 文件的下载:
- 使用 CDN,让文件在物理上更靠近你的用户
- 压缩你的资源,最好使用 Brotli
- 使用 HTTP2/HTTP3 进行传输
- 将资源托管在同一个域名下(不要使用不同的子域名)
如果你使用的是 Cloudflare、Netlify 或 Vercel 等现代平台,大多数事情都会自动为你完成。 你可以在 web.dev 上找到 LCP 优化指南。
如果 Nuxt 内联了你的所有 CSS,你可以(实验性地)完全阻止外部 CSS 文件被引用到渲染后的 HTML 中。 你可以通过一个 hook 来实现这一点,它可以放在模块中,也可以放在 Nuxt 配置文件中。
export default defineNuxtConfig({
hooks: {
'build:manifest': (manifest) => {
// find the app entry, css list
const css = Object.values(manifest).find(options => options.isEntry)?.css
if (css) {
// start from the end of the array and go to the beginning
for (let i = css.length - 1; i >= 0; i--) {
// if it starts with 'entry', remove it from the list
if (css[i].startsWith('entry')) {
css.splice(i, 1)
}
}
}
},
},
})