CSS是编程语言吗?
CSS是一门专业性很强的编程语言,它专注于网页样式系统的实现。由于其独特的用例以及声明性本质,有时很难理解。有些人甚至完全否认它是编程语言。今天,我们就通过编写一个智能、灵活的样式系统来证明这样的观点是错误的。
控制结构
较为传统的通用编程语言(如JavaScript)为我们提供了条件(if/then
)、循环(for
、while
)、逻辑运算(===
、&&
等)和变量等工具。这些结构在CSS中不但命名不同,而且为了更好地适应样式化文档的特定用例,语法也是大相径庭,甚至其中一些直到几年前才在CSS中可用。
变量
变量就是其中之一。它们在CSS中被称为自定义属性(尽管我们都称为变量)。
:root {
--color: red;
}
span {
color: var(--color, blue);
}
双破折号声明变量并赋值。这必须发生在作用域内,因为在选择器之外这样做会破坏CSS语法。注意:root
选择器作用于全局范围。
条件
条件可以用多种方式编写,具体取决于你打算在何处使用。选择器的作用域在于其元素,而媒体查询是全局范围的,且需要各自的选择器。
属性选择器:
[data-attr='true'] {
/* if */
}
[data-attr='false'] {
/* elseif */
}
:not([data-attr]) {
/* else */
}
伪类:
:checked {
/* if */
}
:not(:checked) {
/* else */
}
媒体查询:
:root {
color: red; /* else */
}
@media (min-width > 600px) {
:root {
color: blue; /* if */
}
}
循环
计数器既是CSS中最直接的循环形式,也是用例范围最窄的一种。你只能在content
属性中使用计数器,并将其显示为文本。你可以在任何给定点调整其步长增量、起始点以及它的值,但输出始终限制为文本。
main {
counter-reset: section;
}
section {
counter-increment: section;
counter-reset: section;
}
section > h2::before {
content: 'Headline ' counter(section) ': ';
}
那么,如果我们想使用循环来定义重复的布局模式,应该怎么做?这种循环有点晦涩:通过Grid
的auto-fill
属性。
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
这将会使尽可能多的元素填充网格,同时缩放以填充可用空间,但在需要时又可以分成多行。只要找到元素并将其限制为最小宽度300px
和最大宽度为其自身大小的一小部分,它就会重复。直接看代码可能更清楚:
@for $i from 1 through 20 {
.random-#{$i} {
background: rgb(random(255), random(255), random(255));
}
}
@keyframes size {
0%, 2% {
max-width: 100vw;
}
80%, 100% {
max-width: 300px;
}
}
body {
animation: size 5s infinite alternate;
animation-delay: 3s;
background: black;
font-family: sans-serif;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.box {
display: grid;
place-items: center;
color: white;
font-size: 3rem;
text-shadow: 1px 1px 2px black;
padding: 2rem;
}
最后,还有循环选择器。它可以接受参数,并且参数可以是公式。
section:nth-child(2n) {
/* 选中第偶数个元素 */
}
section:nth-child(4n + 2) {
/* 选中从第二个开始,第4n个元素 */
}
对于非常特殊的情况,你可以将:nth-child()
与:not()
结合使用,例如:
section:nth-child(3n):not(:nth-child(6)) {
/* 选中第3的倍数的元素,但不是第6的倍数的元素 */
}
也可以将:nth-child()
替换为:nth-of-type()
和:nth-last-of-type()
以更改最后几个示例的作用域。
逻辑运算
CSS逻辑运算是说明CSS实际上是编程语言的最佳解释之一。
猫头鹰选择器
* + * {
margin-top: 1rem;
}
猫头鹰选择器选择项目后面的每个项目。应用margin-top
有效地增加了项目之间的间隙,就像是grid-gap
,但没有网格系统。这也意味着更具可定制性。你可以覆盖margin-top
并适应任何类型的内容。想要每个项目之间有1rem
的空间,而在标题之前的空间是3rem
?使用猫头鹰选择器比在网格中更容易实现。
使用猫头鹰选择之后只会调整第二三行间距,而不会设置第一行的margin-top
值。
条件样式
我们可以在css代码中创建开关,使用变量和calc
来打开和关闭某些规则。这为我们提供了非常灵活的条件。
.box {
padding: 1rem 1rem 1rem calc(1rem + var(--s) * 4rem);
color: hsl(0, calc(var(--s, 0) * 100%), 80%);
background-color: hsl(0, calc(var(--s, 0) * 100%), 15%);
border: calc(var(--s, 0) * 1px) solid hsl(0, calc(var(--s, 0) * 100%), 80%);
}
.icon {
opacity: calc(var(--s) * 100%);
transform: scale(calc(var(--s) * 100%));
}
根据--s
、.box
的值启用或禁用警告样式。
自动对比色
让我们采用相同的逻辑创建颜色变量,该变量取决于与背景颜色的对比度:
:root {
--theme-hue: 210deg;
--theme-sat: 30%;
--theme-lit: 20%;
--theme-font-threshold: 51%;
--background-color: hsl(var(--theme-hue), var(--theme-sat), var(--theme-lit));
--font-color: hsl(
var(--theme-hue),
var(--theme-sat),
clamp(10%, calc(100% - (var(--theme-lit) - var(theme-font-threshold)) * 1000), 95%)
);
}
此代码段通过反转背景的亮度值,根据HSL值和黑色或白色字体颜色计算背景颜色。仅此一项就可能导致颜色对比度低(例如60%灰色背景上的40%灰色字体几乎难以辨认),所以我将减去一个阈值(颜色从白色切换到黑色的点),乘以像1000这样非常高的值并将其限制在10%到95%之间,以最终获得有效的亮度百分比。这一切都可以通过编辑代码片段开头的四个变量来控制。
此方法还可用于编写复杂的颜色逻辑和自动主题,仅基于HSL值。
清理样式表
让我们结合当前内容来清理样式表。按视口对所有内容进行排序似乎有点乱糟糟,但按组件排序也没有变得更好。不过有了变量,则可以两全其美:
/* 定义变量 */
:root {
--paragraph-width: 90ch;
--sidebar-width: 30ch;
--layout-s: "header header" "sidebar sidebar" "main main" "footer footer";
--layout-l: "header header" "main sidebar" "footer footer";
--template-s: auto auto minmax(100%, 1fr) auto /
minmax(70%, var(--paragraph-width)) minmax(30%, var(--sidebar-width));
--template-l: auto minmax(100%, 1fr) auto /
minmax(70%, var(--paragraph-width)) minmax(30%, var(--sidebar-width));
--layout: var(--layout-s);
--template: var(--template-s);
--gap-width: 1rem;
}
/* 根据视图窗口操作变量 */
@media (min-width: 48rem) {
:root {
--layout: var(--layout-l);
--template: var(--template-l);
}
}
/* 绑定到DOM中 */
body {
display: grid;
grid-template: var(--template);
grid-template-areas: var(--layout);
grid-gap: var(--gap-width);
justify-content: center;
min-height: 100vh;
max-width: calc(
var(--paragraph-width) + var(--sidebar-width) + var(--gap-width)
);
padding: 0 var(--gap-width);
}
所有全局变量都在最顶部定义并按视图窗口排序。该部分有效地解决以下问题:
-
样式表有哪些全局范围?考虑 font-size
、颜色、重复测量等。 -
有哪些经常变化的方面?考虑容器宽度、网格布局等。 -
视口之间的值如何变化?什么样的全局样式适用于什么样的视口?
以下是按组件排序的规则定义。这里不再需要媒体查询,因为它们已经在顶部定义并放入变量中。此时,我们可以不间断地编写样式表。
读取哈希参数
伪类的一个特例是:target
选择器,它可以读取URL的哈希片段。下面是使用此机制来模拟类似SPA体验的演示:
注意,上面有一些严重的可访问性影响,并且需要一些JavaScript机制才能真正无障碍。所以不要在现场环境中这样做。
在JavaScript中设置变量
操作CSS变量现在已经成为一个非常强大的工具。也可以用于JavaScript中:
// set --s on :root
document.documentElement.style.setProperty('--s', e.target.value);
// set --s scoped to #myID
const el = document.querySelector('#myID');
el.style.setProperty('--s', e.target.value);
// read variables from an alement
const switch = getComputedStyle(el).getPropertyValue('--s');
总结
CSS非常擅于定义智能和响应式布局系统。与其他语言相比,它的控制结构和算法可能有点奇怪,但就是这样的CSS完成任务全不在话下。
感谢大家的阅读。
每日分享前端插件干货,欢迎关注!
点赞和在看就是最大的支持❤️