CSS Module

html-css07

CSS Module,第1张

CSS Modules 不是官方规范或浏览器中的实现,而是构建步骤中的一个过程(在 Webpack 或 Browserify 的帮助下),它改变了类名和选择器的作用域(即有点像命名空间)。

目的: 解决 CSS 中全局作用域的问题

在 React 中默认开启了 CSS Module,样式表文件需要以 xxx.module.sass/less/css 命名

我们也可以通过配置 webpack 来开启 CSS Module

webpack.config.js

localIdentName 可以定义生产的哈希类名,默认是 [hash:base64]

详细配置见: css-loader

localIdentName 选项的占位符有:

默认 CSS 的规则是全局生效的,任何一个组件下的 CSS 样式,都会影响其他组件中使用相同类名的地方。

style.css

App.js

Header.css

Header.js

index.js

此时我们的页面上展示的就是绿色的 Header 组件 和 Hello World

因为定义了两个相同的 title class,虽然是在不同的组件中导入,但是他们的类名是一样的,最终都会在全局作用域下生效,因为这两个组件都渲染在了页面上。

至于为什么会显示成绿色,因为 Header 组件是后导入的,所以 Header 的 title 样式就覆盖了 App 的 title 样式,这就是 CSS 层叠样式的概念了,此处不再赘述。(如果导入顺序换一下,那么就是红色了)

答案: 产生局部作用域的唯一方法,就是使用一个独一无二的 class 的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。

这里就拿 React 项目来进行解释

在 React 中,默认是开启 CSS Module 的。但是对于样式表文件的命名一个约束。需要以 .module.less/css/sass 结尾

随意我们就可以这样改造一下 Header 组件,来使用 CSS Module 的功能。

效果: Header 组件 展示为绿色; Hello World 展示为红色。可以看到 Header 中相同类名的样式并没有影响到我们的 App 组件

此时在控制台中查看 HTML,发现我们 Header 组件中的 h2 标签上的 class 类名变成了 <h2class="_src_Header_module__title">Header 组件</h2>

同理在样式表文件中也变成了

App 组件

随机的 className 是可以配置的:通过 webpack.config.js 中配置 css-loader 的 localIdentName 选项来定义生成的随机类名

通常在我们的日常开发中有这种场景:

我们有一个自己的组件,但是这个组件使用了一些第三方的组件库,对于我们使用的第三方组件我们又想修改一下它的样式。

如果此时,我们直接在当前组件的样式文件中,通过定义一个和第三方组件相同类名的类(比如说 antd button 的 antdr-btn 类),然后写自己的样式:

Button.module.css

然后我在组件中导入

Button.js

此时我们会发现我们的修改并没有生效,为什么呢?原因就是最后我们导入的类名会被 css-loader 处理成一个随机的值,所以导致不再是 antdr-btn 。

那么我们如何实现在自定义组件中修改第三方组件的样式呢?

需要不对第三方组件的类名进行哈希,保留原始类名,才能起到样式覆盖的作用 :global

:global(.className)那么此时这个 className 即使是在组件的样式表中定义的也不会被添加 hash 值,所以就可以影响全局所有类名为 className 的样式

注意:

此时组件中对该类的样式修改会影响全局所有使用该类名的地方,所以为了将样式修改限制到本组件,一般推荐将:global 使用在组件自定义类名范围下,然后添加这个自定义类名到组件中

CSS Modules 还提供一种显式的局部作用域语法:local(.className),等同于.className(直接在样式文件中写.className)该类名在编译后会被添加 hash 值

在 CSS Modules 中,一个选择器可以继承另一个选择器的规则,这称为"组合"。

在 Header.module.css 中,让.title 继承.back 。

Header.module.css

Header.js

编译后

CSS

HTML

选择器也可以继承其他 CSS 文件里面的规则。

other.module.css

Header.module.css

注意:

导入的类名需要和被导入文件中的类名相同

编译之后的效果和 composes 同一个文件中的 class 效果相同

很多组件库自身就支持定制主题,这种情况适合于有明确组件设计规范的项目使用。下面是一些定制主题的文档:

当 React 项目使用了 css-modules ,可以在 component 级别覆盖组件库的样式。比如使用了 antd 的组件,但是组件的样式与设计稿的样式有些许出入,既可以通过 CSS modules 的 global 来进行样式覆盖。

如果样式覆盖顺序没有生效,可以加上 !important

一般组件都会留有 API 来自定义一些东西。比如容器样式、关闭按钮、内部组件样式、图标替换等。如果这个组件被应用到一个地方,直接在组件 API 上定义改动即可。如果这个组件会被反复使用。就可以进行组件的二次封装。

下面我基于 antd 的 Modal 组件的二次封装,整个项目都改用封装后的 Modal 组件。

之前遇到一个需要需要将表格多选功能改为 鼠标 hover 的时候显示 checkbox 而非 hover 情况下显示为数字序号 。这个需求在我们用到的 ali-react-table 中并没有相关 API 实现。

既然无法实现需求,就放弃用这个库吗?非也!可以通过将项目 fork 下来,修改源码来实现。且如果这个库有后续的更新,也可以将主仓库的改动合并到 fork 的仓库中。

而在 package.json 的引用中,改为引用 fork 下来的项目即可。这里的引用可以直接引用 git 路径,也可以发到 npm 上去引用。

或许还有别的重写组件的方法,但目前来看以上四种方法都是比较优雅的。

如果我们的页面上存在非常多的样式,譬如有我们开发页面的时候的自定义样式,也有引入的组件库样式。这时候样式将会非常混乱难以管理。

当我们想覆盖一些本身非我们书写的样式时候,往往不得不通过使用优先级权重更高的样式名,去覆盖那些样式。

同时,当样式优先级感到难以控制时,开发者习惯滥用 !important 去解决,这又循环导致了后续更混乱的样式结构。

基于让 CSS 得到更好的控制和管理的背景,CSS @layer 应运而生。

CSS Cascade Layers,也叫做CSS级联层,是Cascading and Inheritance Level5 规范中新增了一个新的 CSS 特性。

这样,我们就创建一个名为 utilities 的 @layer 级联层。

@layer规则可以通过三种方式其一来创建级联层。第一种方法如上方代码所示,它创建了一个块级的@规则,其中包含作用于该层内部的CSS规则。

一个级联层同样可以通过 @import 来创建,规则存在于被引入的样式表内:

你也可以创建带命名的级联层,但不指定任何样式。例如,单一的命名层:

或者,多个命名层也可以被同时定义。例如:

因此,在上面的例子中,如果 theme 层和 utilities 层中存在冲突的规则,那么 utilities 层中的将优先被应用。

即使 utilities 层中规则的 优先级低于 theme 层中的,该规则仍会被应用。一旦级联层顺序建立之后,优先级和出现顺序都会被忽略。

这将使创建CSS选择器变得更加简单,因为你不需要确保每一个选择器都有足够高的优先级来覆盖其他冲突的规则,你只需要确保它们出现在一个顺序更靠后的级联层中。

级联层允许嵌套,例如:

向 layout 层内部的 framework 层附加规则,只需用 . 连接这两层。

如果创建了一个级联层但并未指定名字,例如:

那么则称为创建了一个匿名层。除创建后无法向其添加规则外,该层和其他命名层功能一致。

1、使用@layer 块规则,并立即为其分配样式:

2、使用规则@layer 语句,没有指定任何样式:

3、将@import 与layer关键字或layer()函数一起使用

以上每一个都创建了一个名为 的级联层reset。

在下面的例子中,我们建立四个级联层:reset,base,theme,和utilities。

重复使用级联层名称时,样式将附加到现有级联层。级联层的顺序保持不变,因为只有第一次的出现已经确定顺序:

重新使用级联层名称时层顺序保持不变的使@layer 语法变得更加方便和严谨。使用它,可以预先建立图层顺序,然后将所有 CSS 附加到它:

按以往CSS级联来进行分析的话,form input(多层级)的优先级会大于input,但是由于级联层所起的作用,@layer theme的input会取胜。

级联层支持嵌套使用,如下:

在这个例子中有两个级联外层:

base

framework

该framework层本身也包含两层:

base

theme

如果要将样式附加到嵌套级联层,需要使用以下全名来引用它,

如果第一个@media (min-width: 30em)匹配(基于视口尺寸),则layout级联层层将在图层顺序中排在第一位。如果只有@media (prefers-color-scheme: dark)匹配,theme则将是第一层。

如果两者匹配,则图层顺序将为layout, theme。如果没有匹配,则不定义层。