使用 CSS 为 Vue 组件设置样式

终于到了让我们的应用程序看起来更漂亮的时候了。在本文中,我们将探讨使用 CSS 为 Vue 组件设置样式的各种方法。

预备知识

熟悉核心 HTMLCSSJavaScript 语言,了解 终端/命令行

Vue 组件由管理应用程序数据的 JavaScript 对象和映射到底层 DOM 结构的基于 HTML 的模板语法组合编写而成。为了安装和使用 Vue 的一些更高级功能(如单文件组件或渲染函数),您需要一个安装了 Node + npm 的终端。

目标 学习如何为 Vue 组件设置样式。

使用 CSS 为 Vue 组件设置样式

在我们将更多高级功能添加到应用程序之前,我们应该添加一些基本的 CSS 来使其看起来更好。Vue 有三种常见的应用程序样式设置方法:

  • 外部 CSS 文件。
  • 单文件组件(.vue 文件)中的全局样式。
  • 单文件组件中的组件作用域样式。

为了帮助您熟悉其中的每一种,我们将结合使用这三种方法,让我们的应用程序拥有更美观的外观和感觉。

使用外部 CSS 文件进行样式设置

您可以包含外部 CSS 文件并将它们全局应用于您的应用程序。让我们看看如何做到这一点。

首先,在 src/assets 目录中创建一个名为 reset.css 的文件。此文件夹中的文件会经过 webpack 处理。这意味着我们可以使用 CSS 预处理器(如 SCSS)或后处理器(如 PostCSS)。

尽管本教程不会使用此类工具,但值得注意的是,当将此类代码包含在 assets 文件夹中时,它将自动进行处理。

将以下内容添加到 reset.css 文件中

css
/* reset.css */
/* RESETS */
*,
*::before,
*::after {
  box-sizing: border-box;
}
*:focus {
  outline: 3px dashed #228bec;
}
html {
  font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
  margin-bottom: 0;
}
ul {
  list-style: none;
  padding: 0;
}
button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  appearance: none;
}
button::-moz-focus-inner {
  border: 0;
}
button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  /* 1 */
  overflow: visible;
}
input[type="text"] {
  border-radius: 0;
}
body {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  font:
    1.6rem/1.25 "Helvetica Neue",
    "Helvetica",
    "Arial",
    sans-serif;
  background-color: whitesmoke;
  color: #4d4d4d;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
}
@media screen and (width >= 620px) {
  body {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}
/* END RESETS */

接下来,在您的 src/main.js 文件中,像这样导入 reset.css 文件

js
import "./assets/reset.css";

这将导致该文件在构建步骤中被拾取并自动添加到我们的站点。

现在应该已经将重置样式应用于应用程序。下图显示了应用重置样式之前和之后的应用程序外观。

之前

the todo app with partial styling added; the app is now in a card, but some of the internal features still need styling

之后

the todo app with partial styling added; the app is now in a card, but some of the internal features still need styling

明显的变化包括列表项目符号的移除、背景颜色的改变,以及基本按钮和输入框样式的改变。

在单文件组件中添加全局样式

现在我们已经将 CSS 重置为在浏览器之间保持一致,我们需要进一步自定义样式。有一些样式是我们希望应用于应用程序中所有组件的。虽然将这些样式直接添加到 reset.css 样式表中也可以,但我们将把它们添加到 App.vue<style> 标签中,以演示如何使用它。

文件中已经存在一些样式。让我们删除它们,并用下面的样式替换它们。这些样式做了几件事——为按钮和输入框添加了一些样式,并自定义了 #app 元素及其子元素。

更新您的 App.vue 文件的 <style> 元素,使其看起来像这样

vue
<style>
/* Global styles */
.btn {
  padding: 0.8rem 1rem 0.7rem;
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  text-transform: capitalize;
}
.btn__danger {
  color: white;
  background-color: #ca3c3c;
  border-color: #bd2130;
}
.btn__filter {
  border-color: lightgrey;
}
.btn__danger:focus {
  outline-color: #c82333;
}
.btn__primary {
  color: white;
  background-color: black;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > * {
  flex: 1 1 auto;
}
.btn-group > * + * {
  margin-left: 0.8rem;
}
.label-wrapper {
  margin: 0;
  flex: 0 0 100%;
  text-align: center;
}
[class*="__lg"] {
  display: inline-block;
  width: 100%;
  font-size: 1.9rem;
}
[class*="__lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (width >= 620px) {
  [class*="__lg"] {
    font-size: 2.4rem;
  }
}
.visually-hidden {
  position: absolute;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: rect(1px 1px 1px 1px);
  white-space: nowrap;
}
[class*="stack"] > * {
  margin-top: 0;
  margin-bottom: 0;
}
.stack-small > * + * {
  margin-top: 1.25rem;
}
.stack-large > * + * {
  margin-top: 2.5rem;
}
@media screen and (width >= 550px) {
  .stack-small > * + * {
    margin-top: 1.4rem;
  }
  .stack-large > * + * {
    margin-top: 2.8rem;
  }
}
/* End global styles */
#app {
  background: white;
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  padding-top: 0;
  position: relative;
  box-shadow:
    0 2px 4px 0 rgb(0 0 0 / 20%),
    0 2.5rem 5rem 0 rgb(0 0 0 / 10%);
}
@media screen and (width >= 550px) {
  #app {
    padding: 4rem;
  }
}
#app > * {
  max-width: 50rem;
  margin-left: auto;
  margin-right: auto;
}
#app > form {
  max-width: 100%;
}
#app h1 {
  display: block;
  min-width: 100%;
  width: 100%;
  text-align: center;
  margin: 0;
  margin-bottom: 1rem;
}
</style>

如果您查看应用程序,您会发现我们的待办事项列表现在在一个卡片中,并且我们的待办事项项的格式也更好。现在我们可以开始编辑我们的组件以使用其中一些样式了。

the todo app with partial styling added; the app is now in a card, but some of the internal features still need styling

在 Vue 中添加 CSS 类

我们应该将按钮 CSS 类应用于 ToDoForm 组件中的 <button>。由于 Vue 模板是有效的 HTML,这与在纯 HTML 中完成的方式相同——通过向元素添加 class="" 属性。

class="btn btn__primary btn__lg" 添加到您的表单的 <button> 元素

html
<button type="submit" class="btn btn__primary btn__lg">Add</button>

既然我们在这里,还可以进行一项语义和样式上的更改。由于我们的表单表示页面的一个特定部分,因此它可以受益于一个 <h2> 元素。然而,标签已经表明了表单的用途。为了避免重复,让我们将标签包装在一个 <h2> 中。我们还可以添加其他一些全局 CSS 样式。我们还将 input__lg 类添加到我们的 <input> 元素。

更新您的 ToDoForm 模板,使其看起来像这样

html
<template>
  <form @submit.prevent="onSubmit">
    <h2 class="label-wrapper">
      <label for="new-todo-input" class="label__lg">
        What needs to be done?
      </label>
    </h2>
    <input
      type="text"
      id="new-todo-input"
      name="new-todo"
      autocomplete="off"
      v-model.lazy.trim="label"
      class="input__lg" />
    <button type="submit" class="btn btn__primary btn__lg">Add</button>
  </form>
</template>

我们还将在 App.vue 文件中的 <ul> 标签中添加 stack-large 类。这将有助于稍微改善待办事项项之间的间距。

将其更新为如下所示

html
<ul aria-labelledby="list-summary" class="stack-large">
  …
</ul>

添加作用域样式

我们要设置样式的最后一个组件是我们的 ToDoItem 组件。为了使样式定义靠近组件,我们可以在其中添加一个 <style> 元素。但是,如果这些样式会影响该组件之外的内容,那么追踪负责设置样式的代码并修复问题可能会很困难。这时 scoped 属性就很有用了——它会向您的所有样式附加一个唯一的 HTML data 属性选择器,从而防止它们与全局样式冲突。

要使用 scoped 修饰符,请在 ToDoItem.vue 文件底部创建一个 <style> 元素,并为其指定一个 scoped 属性

vue
<style scoped>
/* … */
</style>

接下来,将以下 CSS 复制到新创建的 <style> 元素中

css
.custom-checkbox > .checkbox-label {
  font-family: "Arial", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-weight: normal;
  font-size: 1rem;
  line-height: 1.25;
  color: #0b0c0c;
  display: block;
  margin-bottom: 5px;
}
.custom-checkbox > .checkbox {
  font-family: "Arial", sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-weight: normal;
  font-size: 1rem;
  line-height: 1.25;
  box-sizing: border-box;
  width: 100%;
  height: 2.5rem;
  margin-top: 0;
  padding: 5px;
  border: 2px solid #0b0c0c;
  border-radius: 0;
  appearance: none;
}
.custom-checkbox > input:focus {
  outline: 3px dashed #ffdd00;
  outline-offset: 0;
  box-shadow: inset 0 0 0 2px;
}
.custom-checkbox {
  font-family: "Arial", sans-serif;
  -webkit-font-smoothing: antialiased;
  font-weight: normal;
  font-size: 1.6rem;
  line-height: 1.25;
  display: block;
  position: relative;
  min-height: 40px;
  margin-bottom: 10px;
  padding-left: 40px;
  clear: left;
}
.custom-checkbox > input[type="checkbox"] {
  -webkit-font-smoothing: antialiased;
  cursor: pointer;
  position: absolute;
  z-index: 1;
  top: -2px;
  left: -2px;
  width: 44px;
  height: 44px;
  margin: 0;
  opacity: 0;
}
.custom-checkbox > .checkbox-label {
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
  display: inline-block;
  margin-bottom: 0;
  padding: 8px 15px 5px;
  cursor: pointer;
  touch-action: manipulation;
}
.custom-checkbox > label::before {
  content: "";
  box-sizing: border-box;
  position: absolute;
  top: 0;
  left: 0;
  width: 40px;
  height: 40px;
  border: 2px solid currentColor;
  background: transparent;
}
.custom-checkbox > input[type="checkbox"]:focus + label::before {
  border-width: 4px;
  outline: 3px dashed #228bec;
}
.custom-checkbox > label::after {
  box-sizing: content-box;
  content: "";
  position: absolute;
  top: 11px;
  left: 9px;
  width: 18px;
  height: 7px;
  transform: rotate(-45deg);
  border: solid;
  border-width: 0 0 5px 5px;
  border-top-color: transparent;
  opacity: 0;
  background: transparent;
}
.custom-checkbox > input[type="checkbox"]:checked + label::after {
  opacity: 1;
}
@media only screen and (width >= 40rem) {
  label,
  input,
  .custom-checkbox {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}

现在我们需要将一些 CSS 类添加到我们的模板中,以连接这些样式。

向根 <div> 添加 custom-checkbox 类。向 <input> 添加 checkbox 类。最后,向 <label> 添加 checkbox-label 类。更新后的模板如下

html
<template>
  <div class="custom-checkbox">
    <input type="checkbox" :id="id" :checked="isDone" class="checkbox" />
    <label :for="id" class="checkbox-label">{{label}}</label>
  </div>
</template>

应用程序现在应该有自定义复选框了。您的应用程序外观应类似于下面的截图。

the todo app with complete styling. The input form is now styled properly, and the todo items now have spacing and custom checkboxes

总结

我们的示例应用程序的样式工作已完成。在下一篇文章中,我们将继续添加更多功能,即使用计算属性在应用程序中添加已完成待办事项项的计数。