样式

本项目使用的css预处理器是 stylus,其他预处理器可以自行安装相应loader。

重置样式

浏览器的对于 html 的默认样式都有各自的实现,所以我们一般开发前都需要 reset 样式,以保证在不同的浏览器上有一致的体验,本项目使用的是 normalize.css。

安装

npm install normalize.css

引入

// main.js
import 'normalize.css'

注意

请确保作为第一个样式文件引入,不然有可能会覆盖自己定义的样式。

全局样式

顾名思义,这是覆盖整个项目的样式,比如覆盖 Element 样式,或者自己定义一个 class 或 id,全局使用。

本项目全局样式定义在 @/assets/css/global.styl

TIP

全局样式没有什么约定,可自行修改,也可按 model 维度划分,最后在 main.js 引入即可。

全局变量

使用css预处理器,我们就可以定义样式变量了,例如这样:

<style lang="stylus">
$appColor = #42b983
.my-class {
    color: $appColor
}
<style>

但是当某个变量在很多.vue文件中都要用到,我们不可能在每个.vue文件都去定义这个变量。这会非常麻烦,并且根本无法管理和维护。要解决这个问题,我们只需修改对应css预处理器的 loader 规则就好了。

stylus 示例

vue-cli3.x -> vue.config.js

const path = require('path')
function resolve(dir) {
    return path.join(__dirname, dir)
}
module.exports = {
    css: {
        loaderOptions: {
            stylus: {
                // 定义样式变量的文件
                import: [resolve('./src/assets/css/index.styl')]
            }
        }
    }
}

vue-cli2.x -> /build/utils/js

function generateLoaders () {
    // ...
    return {
        css: generateLoaders(),
        postcss: generateLoaders(),
        less: generateLoaders('less'),
        sass: generateLoaders('sass', { indentedSyntax: true }),
        scss: generateLoaders('sass'),
        stylus: generateLoaders('stylus', {
            // 定义样式变量的文件
            import: path.resolve(__dirname, '../src/assets/css/index.styl')
        }),
        styl: generateLoaders('stylus')
    }
}

这样我们就可以在任意.vue文件中直接使用该index.styl文件定义好的变量。

注意

<style> 标签里使用样式变量,必须在标签上写上 lang="stylus",否则不会使用对应的loader进行解析。

小技巧

样式变量可以利用 webpack 的 :export 来导出,这样我们就可以在.vue 或者 .js文件中使用。

样式污染

在样式开发中,我们经常会碰到以下2个问题:

  • 全局污染 CSS 文件中的选择器是全局生效的,不同文件中的同名选择器,根据 build 后生成文件中的先后顺序,后面的样式会覆盖前面的。
  • 选择器复杂 为了避免上面的问题,我们普遍采用的解决方案是采用BEM风格写法,不过这只能解决我们这个大问题的很小一部分。随着应用的增长,类名变得越来越长,多人开发时还很容易导致命名风格混乱,一个元素上使用的选择器个数也可能越来越多,最终导致难以维护。

但我们非常幸运,社区已经开发出了一些解决方案,他们可以帮我们处理这些问题。说不定你已经听说过了 Styled ComponentsJSS等 —— 这些只是众多流行的工具中的其中几个。而 vue 内置了两个很好的解决方案:Scoped CSSCSS Modules

Scoped CSS

Scoped CSS 只要在 <style> 标签上加上 scoped 就会在当前组件生效,非常简单!

/* 编译前 */
.example {
  color: red;
}

/* 编译后 */
.example[data-v-b73aec10] {
  color: red;
}

使用 scoped 后,父组件的样式将不会渗透到子组件中。不过一个子组件的根节点会同时受其父组件的 Scoped CSS 和子组件的 Scoped CSS 的影响。这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式。

TIP

还有一些情况是我们需要对子组件的深层结构设置样式,我们可以使用深度作用选择器 —— 这种做法并不受推荐且应该避免,因为子组件的样式会被父组件覆盖,即使是孙节点,这也就失去了组件的封装效果,但这对于覆盖第三方插件样式非常有用。不过你还要留意一下 Scoped CSS 痛点

CSS Modules

CSS Modules 可以作为 Scoped CSS 的一个替代方案。vue-cli 已默认开启,使用方法请参照 vue-loader 文档,这里不做重复说明。

示例可以参照 @/layout/Sidebar/index

看过文档后,可以看出 CSS Modules 并没有 Scoped CSS 痛点,其次样式绑定变成了显式,我们拥有了彻底的控制权。

总结

其实两种方案都非常简单、易用,在某种程度上解决的是同样的问题。 那么该选择哪种呢?

scoped 样式的使用不需要额外的知识,给人舒适的感觉。它所存在的局限,也正是它的使用简单的原因。它可以用于支持小型到中型的应用。

在更大的应用或更复杂的场景中,这个时候,对于 CSS 的运用,我们就会希望它更加显式,拥有更多的控制权。虽然在模板中大量使用 $style 看起来并不那么优雅,但却更加安全和灵活,为此我们只需付出微小的代价。还有一个好处就是我们可以用 JS 获取到我们定义的一些变量(如色彩值),这样我们就无需手动保持其在多个文件中同步。

TIP

CSS Modules 样式变量显式 props ,对于封装插件非常有用,如插件里有多个组件公用这个样式变量。