用 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,最大宽度限制为其自身大小的一小部分。这看起来可能比解释起来更容易:
最后,还有循环选择器。它们接受一个参数,该参数可以是一个公式,用于非常精确地选择。
section:nth-child(2n) {
/* selects every even element */
}
section:nth-child(4n + 2) {
/* selects every fourth, starting from the 2nd */
}
对于真正特殊的边缘情况,您可以结合:nth-child()
使用:not()
,例如:
section:nth-child(3n):not(:nth-child(6)) {
/* selects every 3rd element, but not the 6th */
}
您可以:nth-child()
用:nth-of-type()
和:nth-last-of-type()
来替换以改变最后几个示例的范围。
逻辑门
Ana Tudor 写了一篇关于CSS 逻辑门的文章。这篇文章探讨了如何将变量与 组合calc
。之后,她又运用 实现了 3D 建模和动画效果。这篇文章读起来就像神秘的魔法,随着文章的深入,越来越令人着迷,总的来说,它很好地解释了为什么 CSS 是一种编程语言。
技术
猫头鹰选择器
* + * {
margin-top: 1rem;
}
Owl 选择器会选中一个项目之后的所有项目。应用margin-top
可以有效地在项目之间添加间隙,就像grid-gap
一样,但没有网格系统。这也意味着它更加可定制。您可以覆盖您的设置margin-top
并适应任何类型的内容。想要1rem
在每个项目之间,但在标题之前留出 的间距3rem
?使用 Owl 选择器比使用网格更容易实现。
Kevin Pennekamp 有一篇关于它的深入文章,甚至用伪代码解释了它的算法。
条件样式
我们可以在 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 值和黑色或白色字体颜色计算出背景颜色。仅此一项就可能导致颜色对比度过低(40% 灰色的字体在 60% 灰色的背景上几乎难以辨认),因此我将减去一个阈值(颜色从白色变为黑色的点),将其乘以一个非常高的值(例如 1000),并将其限制在 10% 到 95% 之间,最终获得有效的亮度百分比。所有这些都可以通过编辑代码片段开头的四个变量来控制。
该方法还可用于仅基于 HSL 值编写复杂的颜色逻辑和自动主题。
清理样式表
让我们整合一下目前为止的内容来清理样式表。按视口排序看起来有点杂乱,但按组件排序也同样感觉不错。使用变量,我们可以兼顾两者的优点:
/* define variales */
: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;
}
/* manipulate variables by viewport */
@media (min-width: 48rem) {
:root {
--layout: var(--layout-l);
--template: var(--template-l);
}
}
/* bind to 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 非常擅长定义智能且响应式的布局系统。与其他语言相比,它的控制结构和算法可能有些奇怪,但它们确实存在,并且能够胜任这项任务。让我们停止仅仅描述一些样式,开始让它们发挥作用吧。
文章来源:https://dev.to/iamschulz/writing-logic-in-css-3ig0