CSS“进化史”:从CSS、SASS、BEM、CSS模块到样式化组件
自从互联网诞生以来,就一直跟网站样式打交道,CSS一直存在并且已经按照自己的节奏发展,本文就带大家了解一下CSS的发展史。
首先,需要在CSS是什么这一问题上做一个了解,通常将CSS用于描述以标记语言编写的文档的表示形式。
如今CSS的发展变得越来越强大,众所周知,需要使用其他工具才能使CSS更好的应用于项目开发,提高效率。
CSS的发展
在90年代,人们专注于创造“花哨”的界面,一个重要的因素是给人带来眼前一亮的惊叹,内联样式是当时最流行的,只是不在乎看起来是否不同,最后网页就像可爱的玩具,可以扔一些动图,将大框和其他可怕的(当时令人印象深刻的)元素放在一起,希望吸引访问者的注意力。
之后,开始创建动态网站,但是CSS始终是一团糟,每个开发人员都有自己的CSS实现方式,我们中的一些人在细节上苦苦挣扎,在引入新代码时导致视觉上的退化,依靠!important
强制使UI元素以某种方式呈现。
随着项目规模、复杂性和团队成员的增长,所有这些实践变得越来越明显,问题也越来越大。因此,对于有经验和没有经验的开发人员来说,没有一个一致的样式设计模式成为了最大的障碍之一,他们正在努力寻找一种正确的方法来使用CSS。到最后,我们并不知道怎么做是对的,但只是想让事情看起来不错。
SASS的问世
SASS以预处理引擎的形式将CSS转变为一种体面的编程语言,该引擎为样式表带来嵌套、变量、mixin、扩展和计算逻辑的函数等特征,更多特征可以看文章《读scss/sass实例项目带你入门》,因此,可以更好地组织css文件,并至少具有一些在较小文件中解构css块的方法,这是一个很大的进步。
本质上,它采用scss代码,对其进行预处理,并在全局CSS捆绑包中输出文件的编译版本。
BEM和基于组件的思考
当BEM来临时,它是一股清流,使我们更多考虑可重用性和组件化,基本上将语义带到一个新的层次,它让我们确保className
的唯一性,从而通过使用一个简单的块元素修饰符约定来减少发生特异性冲突的风险。 看下面的例子:
如果稍作分析一下标记,可以看到“块元素修改器”的约定。
可以看到在代码中有两个块元素:.scenery
和.sky
,每个块都有自己的块。.sky
是唯一有修饰符,因为它可能是有cloud
、sun
,那些都是可以应用于同一元素的不同特性。
下面我们看一下带有一些伪代码的配套CSS,以便更好地对其进行分析:
.scenery {
&__sky {
fill: screen;
}
&__ground {
float: bottom;
}
&__people {
float: center;
}
}
.sky {
background: dusk;
&__clouds {
type: distant;
}
&__sun {
strength: 0.025;
}
// Modifiers
&--dusk {
background: dusk;
.sky__clouds {
type: distant;
}
.sky__sun {
strength: 0.025;
}
}
&--daytime {
background: daylight;
.sky__clouds {
type: fluffy;
float: center;
}
.sky__sun {
strength: 0.7;
align: center;
float: top;
}
}
}
在确保组件是唯一的#reusabilityFtw
的意义上,BEM很好。通过这种思考,随着我们开始将旧样式表迁移到新的约定中,一些明显的模式变得更加明显。
但是,出现了新的问题
-
类名选择变成了一项繁琐的事情
-
标记变得很长导致类型变得臃肿
-
每当需要重用时,都需要显式扩展每个ui组件
-
标记就失去了意义
CSS模块和本地范围
SASS或BEM的问题存在的,在语言逻辑中没有真正封装的概念,因此依赖开发人员选择唯一的类名。 感觉这是通过工具而不是规范来解决的过程。
这正是CSS模块所做的,它依靠为每个本地定义的样式创建一个动态的类名,确保注入新的CSS属性不会引起视觉上的退化,所有样式均已正确封装。
CSS-Modules在React生态系统中迅速流行起来,现在常见的是看到许多React项目都在使用它,它具有一定的优缺点,但总的来说,它被证明是一个很好的范例。
但是CSS模块本身并不能解决CSS的核心问题,它只是向你展示了一种本地化样式定义的方法:一种自动化BEM的灵活方法,这样就不需要再考虑选择类名(或者至少不需要再考虑它)。
但这并不能减轻对良好且可预测的样式体系结构的需求,该体系结构易于以最少的工作量扩展重用和控制。
下面是本地css的样子:
@import '~tools/theme';
:local(.root) {
border: 1px solid;
font-family: inherit;
font-size: 12px;
color: inherit;
background: none;
cursor: pointer;
display: inline-block;
text-transform: uppercase;
letter-spacing: 0;
font-weight: 700;
outline: none;
position: relative;
transition: all 0.3s;
text-transform: uppercase;
padding: 10px 20px;
margin: 0;
border-radius: 3px;
text-align: center;
}
@mixin button($bg-color, $font-color) {
background: $bg-color;
color: $font-color;
border-color: $font-color;
&:focus {
border-color: $font-color;
background: $bg-color;
color: $font-color;
}
&:hover {
color: $font-color;
background: lighten($bg-color, 20%);
}
&:active {
background: lighten($bg-color, 30%);
top: 2px;
}
}
:local(.primary) {
@include button($color-primary, $color-white)
}
:local(.secondary) {
@include button($color-white, $color-primary)
}
会看到它只是CSS,主要区别是所有以:local
开头的classNames
都会生成一个唯一的类名,看起来像这样:
.app-components-button-__root — 3vvFf {}
可以使用localIdentName
查询参数配置生成的标识。示例:css-loader?localIdentName=[path][name]---[local]---[hash:base64:5]
这就是Local CSS Modules
背后的简单原理。本地模块通过生成唯一的className
来确保BEM
表示法自动化,即使该类名使用相同的名称也不会与其他模块冲突。
样式化的组件
样式化的组件,可在JS中完全混合CSS,样式化组件是作为包装组件的纯视觉基础,它们可以映射到实际的html标签,它们所做的就是用样式化组件包装子组件。
下面的代码将对其进行更好的解释:
import React from "react"
import styled from "styled-components"
const Input = styled.input`
background: green
`
const FormWrapper = () =>
Send
如果看到这个样式组件非常容易理解,它使用模板文字表法来定义css属性,那么这次似乎是核心样式组件团队将它融合到了ES6
和css
的全部功能。
样式化组件提供了一种非常简单的模式来重用UI,并将UI与功能和状态组件完全分开。创建一个可以在浏览器中以HTML或使用React Native本地访问本机标签的api。
下面是将自定义属性(或修饰符)传递给样式化组件的方式:
import styled from "styled-components"
const Sky = styled.section`
${props => props.dusk && 'background-color: dusk' }
${props => props.day && 'background-color: white' }
${props => props.night && 'background-color: black' }
`;
可以看到,props
变成了每个组件接收的修饰符,它们可以被处理以输出不同的css
行,明白吗?
这使我们可以更快地移动并利用JS
的全部功能来处理样式,同时确保样式保持一致和可重用。