.title {
color: silver;
}
然后又在后边写了一条类似的(选手2):
.title {
color: gold;
}
因为名字相同,选手2就会和选手1打起来(让你丫冒充我!)。结果是选手2获胜,class名为title的元素,最终的color值为gold。
css里就像这样,随时可能一言不和就发生战争,结果输掉的一方就会被胜利的一方所覆盖。“层叠”一词可以说形象地描述了这个过程。
那么,为什么会有这样的层叠(zhàn zhēng )呢?
css的作用域问题
在Javascript里,可以做到这样的搭配:
var title = "silver";
(function(){
var title = "gold";
console.log(title); // gold
}());
console.log(title); // silver
利用Javascript的函数作用域,两位同样名为title的选手可以友好相处。
但回到css里的样式规则,情况就完全不是这么回事了。
css不是程序语言,但如果说要给它加一个作用域的概念的话,那就是:只有全局作用域。
无论分拆为多少个css文件,无论用怎样的方式引入,所有的样式规则都位于同一作用域,只要选择符近似,就有发生覆盖的可能。
减少相互影响的策略
为减少相互影响,避免预料之外的样式覆盖,我们一直以来想过很多办法。
比如你接手一个别人留下来的旧项目,接下来要新增一个标题元素的时候,你会有意识地不去使用.title这样模糊的class名,因为它太容易重名了。最终,你用的名称可能是:
.module-sp-title {
color: deepskyblue;
}
即使你决定要用.title这个名字,你也会加上包含选择符作为限定:
.module-1 .title {
font-size: 18px;
}
/* ... */
.module-2 .title {
font-size: 14px;
}
其中.module-1和.module-2的名字应该是唯一的,这样的代码在组件化(模块化)的开发风格里很常见。
此外,一些有名的css理论,如SMACSS,会建议你为所有布局样式使用l-或layout-的前缀,以示区分。
类似的做法还有很多,但归结起来,都是在尝试提供一种合理的命名约定。而合理的命名约定,的确是组织css代码的有效策略。
现在,我们有了新的可用策略,CSS Modules就是其中之一。
技术流的模块化
CSS Modules是一种技术流的组织css代码的策略,它将为css提供默认的局部作用域。
CSS Modules是如何做到的呢?来看一个CSS Modules的简单例子吧。
有这样的一个html元素:
a title for CSS Modules
按照普通css的写法,我们可以这样为它添加样式:
.title {
background-color: snow;
}
现在我们改用CSS Modules。首先,css保持不变。然后,修改html的写法。不再这样直接写html,而是改为在Javascript文件里动态添加,这样做(css文件名为main.css):
var styles = require("./main.css");
var el = document.getElementById("example_title");
el.outerHTML = 'a title for CSS Modules
';
咦,require了一个css文件?对的,所以要用到webpack。编译后,html和css会变成这样:
module: {
loaders: [{
test: /\.css$/,
loader: 'style!css?modules'
}]
}
才发现一直用着的css-loader原来有这功能?其实,CSS Modules确实是一个后来才并入css-loader的新功能。
自定义生成的class名
“
名字都这样了,还怎么调试?”
为css-loader增加localIdentName参数,是可以指定生成的名字。localIdentName的默认值是[hash:base64],一般开发环境建议用类似这样的配置:
{
test: /\.css$/,
loader: 'style!css?modules&localIdentName=[name]__[local]___[hash:base64:5]'
}
同样应用到前面的例子里,这时候就会变成这样的结果:
import styles from './ScopedSelectors.css';
import React, { Component } from 'react';
export default class ScopedSelectors extends Component {
render() {
return (
Scoped Selectors
);
}
};
如果不使用React,还是那句话,只要有办法把变量风格的class名注入到html中,就可以用CSS Modules。原始的字符串拼接的写法显然很糟糕,但我们可以借助各种模板引擎和编译工具做一些改进。下面请看一个用Jade的参考示例。
想象一下你有一个用普通css的页面,但你想在一小块区域使用CSS Modules。这一块区域在一个容器元素里:
后用jade来写html(关联的css文件为module_sp.css):
- styles = require("./module_sp.css");
h2(class=styles.title) a title for CSS Modules
接下来,仍然是在Javascript里添加这段jade生成的html:
var el = document.getElementById("module_sp_container");
var template = require("./main.jade");
el.innerHTML = template();
最后,记得在css-loader启用CSS Modules的同时,增加jade-loader:
{
test: /\.jade$/,
loader: 'jade'
}
编译运行,就可以得到想要的结果。除Jade以外,还有些其他CSS Modules的html应用方案,推荐参考github上的这篇issue。
目前CSS Modules还在发展中,而且也在考虑改进CSS Modules下的html写作体验。CSS Modules团队成员有提到一个叫CSS Modules Injector的未来规划项目,目的是让开发者不用Javascript也可以使用CSS Modules(这就很接近原生html + css的组合了)。
CSS Modules下的样式复用
“样式都是唯一的了,怎么复用?”
我们已经说了挺多普通css单个全局作用域的坏处。但对应的,这也有一个很大的好处,就是便于实现样式的复用。css理论OOCSS也是在追求这一点。
CSS Modules提供一个composes方法用于样式复用。例如,你有一个btn.css里有一条:
.btn{
display: inline-block;
}
然后,你在另一个CSS Module的module_sp.css里可以这样引入它:
.btn-sp{
composes: btn from "./btn.css";
font-size: 16px;
}
那么,这个p.btn-sp的DOM元素将会是:
var styles = require("./main.css");
console.log("styles = ", styles);
结果类似这样:
{
"btn-sp": "_2SCQ7Kuv31NIIiVU-Q2ubA _2r6eZFEKnJgc7GLy11yRmV",
title: "_1m-KkPQynpIso3ofWhMVuK"
}
这可以帮助理解CSS Modules是怎样工作的。
预编译器
sass等预编译器也可以用CSS Modules,对应的loader可能是这样:
{
test: /\.scss$/,
loader: 'style!css?modules!resolve-url!sass?sourceMap'
}
注意不要因为是sass就习惯性地用嵌套写法,CSS Modules并不适合使用包含选择符。
建议的命名方式
CSS Modules会把.title转换为styles.title,由于后者是用在Javascript中,因此驼峰命名会更适合。
如果像我之前那样写.btn-sp,需要注意在Javascript中写为styles["btn-sp"]。
此外,你还可以为css-loader增加camelCase参数来实现自动转换:
{
test: /\.css$/,
loader: 'style!css?modules&camelCase',
}
这样即便你写.btn-sp,你也可以直接在Javascript里用styles.btnSp。
结语
无论是一直以来我们认真遵循的命名约定,还是这个新的CSS Modules,目的都是一样的:可维护的css代码。我觉得就CSS Modules基本还是在写css这一点来说,它还是很友好的。
虽然本文为了严谨,结果写了相当长的篇幅,但希望你读过之后,还能觉得CSS Modules是简单易懂的。因为这样,我就达成我的目的:扣题,了。
推荐教程:《PHP》
以上就是CSS Modules 详解的详细内容,更多请关注 第一PHP社区 其它相关文章!