样式

了解如何为 Nuxt 应用添加样式。

Nuxt 在样式方面非常灵活。你可以写自己的样式,或者引用本地和外部样式表。 你可以使用 CSS 预处理器、CSS 框架、UI 库和 Nuxt 模块来为应用添加样式。

本地样式表

如果你写本地样式表,自然的位置是 app/assets/ 目录。

在组件中导入

你可以在页面、布局和组件中直接导入样式表。 可以用 JavaScript import,也可以用 CSS @import 语句。

<script>
// 使用静态 import 以兼容服务端
import '~/assets/css/first.css'

// 注意:动态 import 不兼容服务端
import('~/assets/css/first.css')
</script>

<style>
@import url("~/assets/css/second.css");
</style>

样式表会被内联到 Nuxt 渲染的 HTML 中。

CSS 属性

你也可以在 Nuxt 配置中使用 css 属性。 样式表自然的位置是 app/assets/ 目录。然后引用路径,Nuxt 会把它包含到应用的所有页面中。

export default defineNuxtConfig({
  css: ['~/assets/css/main.css'],
})

样式表会被内联到 Nuxt 渲染的 HTML 中,全局注入,存在于所有页面。

处理字体

把本地字体文件放在 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 分发的样式表

你也可以引用通过 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'],
})

外部样式表

你可以在 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' }],
    },
  },
})

动态添加样式表

你可以使用 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

如果你需要更高级的控制,可以用 hook 拦截渲染后的 html,编程式地修改 head。

在 ~~/server/plugins/my-plugin.ts 中创建插件:

export default defineNitroPlugin((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">')
  })
})

外部样式表是渲染阻塞资源:必须在浏览器渲染页面前加载和处理。包含不必要大样式的网页渲染更慢。可以在 web.dev 了解更多。

使用预处理器

要使用 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'],
})

两种方式下,编译后的样式表都会被内联到 Nuxt 渲染的 HTML 中。

如果你需要在预处理文件中注入代码,比如带颜色变量的 Sass partial,可以用 Vite 的预处理器选项。

在 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 的文档。

预处理器 Worker(实验性)

Vite 提供了一个实验性选项,可以加速预处理器的使用。

在 nuxt.config 中启用:

export default defineNuxtConfig({
  vite: {
    css: {
      preprocessorMaxWorkers: true, // CPU 数减 1
    },
  },
})

这是实验性选项,请参考 Vite 文档并提供反馈。

单文件组件(SFC)样式

Vue 和 SFC 最棒的一点就是对样式的原生支持。你可以直接在组件文件的 style 块中写 CSS 或预处理器代码,获得极佳的开发体验,无需使用 CSS-in-JS。不过如果你想用 CSS-in-JS,也有第三方库和模块支持,比如 pinceau。

SFC 中样式组件的完整参考,参考 Vue 文档。

Class 和 Style 绑定

你可以利用 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 实现动态样式

你可以在 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 样式

scoped 属性让你隔离组件样式。用这个属性声明的样式只作用于当前组件。

<template>
  <div class="example">
    hi
  </div>
</template>

<style scoped>
.example {
  color: red;
}
</style>

CSS Modules

你可以用 module 属性使用 CSS Modules。通过注入的 $style 变量访问。

<template>
  <p :class="$style.red">
    这应该是红色的
  </p>
</template>

<style module>
.red {
  color: red;
}
</style>

预处理器支持

SFC style 块支持预处理器语法。Vite 内置支持 .scss、.sass、.less、.styl 和 .stylus 文件,无需配置。只需先安装,然后在 SFC 中用 lang 属性直接使用。

<style lang="scss">
  /* 在这里写 scss */
</style>
<style lang="sass">
  /* 在这里写 sass */
</style>
<style lang="less">
  /* 在这里写 less */
</style>
<style lang="stylus">
  /* 在这里写 stylus */
</style>

参考 Vite CSS 文档 和 @vitejs/plugin-vue 文档。 webpack 用户参考 vue loader 文档。

使用 PostCSS

Nuxt 内置 postcss。可以在 nuxt.config 文件中配置。

export default defineNuxtConfig({
  postcss: {
    plugins: {
      'postcss-nested': {},
      'postcss-custom-media': {},
    },
  },
})

为了在 SFC 中正确高亮语法,可以使用 postcss lang 属性。

<style lang="postcss">
  /* 在这里写 postcss */
</style>

默认情况下,Nuxt 已预配置以下插件:

利用布局实现多种样式

如果你需要让应用不同部分有完全不同的样式,可以使用布局。 不同布局用不同样式。

<template>
  <div class="default-layout">
    <h1>默认布局</h1>
    <slot />
  </div>
</template>

<style>
.default-layout {
  color: red;
}
</style>

第三方库和模块

Nuxt 在样式方面不做强制要求,提供了丰富的选择。你可以用任何想要的样式工具,比如流行的 UnoCSS 或 Tailwind CSS。

社区和 Nuxt 团队开发了大量 Nuxt 模块来简化集成。 你可以在网站的模块部分发现它们。 以下是几个入门模块:

Nuxt 模块开箱即用地提供良好的开发体验,但记住,如果你喜欢的工具没有模块,不代表不能在 Nuxt 中使用!你可以自己为项目配置。根据工具不同,你可能需要使用 Nuxt 插件和/或自己写模块。如果做了,欢迎分享给社区!

轻松加载 Web 字体

你可以使用 Nuxt Google Fonts 模块加载 Google Fonts。

如果你在使用 UnoCSS,注意它自带 web fonts presets,方便地从常见提供商加载字体,包括 Google Fonts 等。

进阶

过渡

Nuxt 内置了和 Vue 一样的 <Transition> 元素,也支持实验性的 View Transitions API。

字体进阶优化

推荐使用 Fontaine 来降低 CLS。如果需要更高级的功能,可以考虑创建 Nuxt 模块来扩展构建流程或 Nuxt 运行时。

记住利用整个 Web 生态中可用的各种工具和技术,让应用样式化更简单高效。无论你用原生 CSS、预处理器、postcss、UI 库还是模块,Nuxt 都能支持你。样式化愉快!

LCP 进阶优化

你可以做以下事情来加速全局 CSS 文件的下载:

  • 使用 CDN,让文件物理上更靠近用户
  • 压缩资源,最好用 Brotli
  • 使用 HTTP2/HTTP3 传输
  • 资源托管在同一域名(不要用不同子域名)

如果你在用 Cloudflare、Netlify 或 Vercel 等现代平台,大部分事情应该会自动完成。 可以在 web.dev 找到 LCP 优化指南。

如果所有 CSS 都被 Nuxt 内联了,你可以(实验性地)完全停止在渲染的 HTML 中引用外部 CSS 文件。 可以通过 hook 实现,放在模块中或 nuxt.config 文件中。

export default defineNuxtConfig({
  hooks: {
    'build:manifest': (manifest) => {
      // 找到 app 入口的 css 列表
      const css = Object.values(manifest).find(options => options.isEntry)?.css
      if (css) {
        // 从数组末尾往前遍历
        for (let i = css.length - 1; i >= 0; i--) {
          // 如果以 'entry' 开头,从列表中移除
          if (css[i].startsWith('entry')) {
            css.splice(i, 1)
          }
        }
      }
    },
  },
})