写CSS时总感觉某些效果实现起来特别拧巴?其实很多让人眼前一亮的CSS技巧并不是凭空造出来的,更像是从规范文档的角落里“挖”出来的。就像米开朗基罗说雕塑本来就藏在石头里,创作者只是把多余的部分凿掉。CSS的特性们早就互相看对眼了,就差有人牵个线让它们一起跳舞。这篇博客聊聊怎么用递归思维、拥抱限制和极端化这些野路子,把那些八竿子打不着的CSS特性撮合到一块,搞出点纯CSS的骚操作。
用递归思维给CSS特性当媒人
写代码的都知道递归这玩意儿,核心就是找个出口避免无限套娃。这种思维放到CSS里,能帮人发现看似水火不容的特性其实能配合得天衣无缝。比如一个经典问题:view-timeline能不能控制触发它自己的那个东西?这就像问“理发师能不能给自己刮胡子”,但还真有解。把view-timeline和position: fixed凑成一对,这俩货就像那种天天吵架但离了对方就活不下去的奇葩室友。下面这段代码展示了怎么让滚动触发动画纯CSS实现:
.scroll-container {
view-timeline: --scroller;
}
.pinned-element {
position: fixed;
animation: fadeIn linear both;
animation-timeline: --scroller;
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}写这段逻辑的时候有个坑:position: fixed元素会脱离文档流,如果没给父容器设明确高度,滚动容器就不知道什么时候触发动画。解决办法是给.scroll-container一个height: 100vh,同时确保overflow: auto。另一个容易翻车的地方是animation-timeline在旧版Chromium内核下不认,得加-webkit-前缀保平安。
让关键帧动画互相套娃
另一个脑洞:一个关键帧动画能不能触发另一个关键帧动画?这问题像问“俄罗斯套娃能不能无限套下去”。答案是能,但需要拉上三个家伙组队:@keyframes、样式查询(@container style())、还有animation-play-state。这三者凑一块能模拟出碰撞检测这种看起来只有JS能干的事。看这个例子,鼠标悬浮时让动画暂停,同时触发另一个动画:
.box {
animation: slide 2s infinite;
}
.box:active {
animation-play-state: paused;
container-type: inline-size;
}
@container style(--trigger: true) {
.explosion {
animation: boom 0.5s forwards;
}
}实际跑起来会发现一个问题:style()查询目前只有Chrome Canary支持,想在生产环境用还得等。另一个翻车点是animation-play-state设成paused后,动画重置到起始状态还是停在原地?不同浏览器行为不一致。稳妥的法子是配合animation-delay负值来控制起始位置。
拥抱“啥也没有”的边界
网页设计这行,艺术和科学的界限本来就糊得不行。很多人迷信“你不能证明一个东西不存在”,但真去较真就会发现,限制反而能逼出创意。好比写诗比写散文难,因为要押韵、要格律,但好诗就是在这种框框里蹦出来的。放到CSS里也一样:没有JavaScript,能不能搞出数独游戏?没有checkbox hack,能不能做CSS游戏?甚至以后if()函数普及了,很多老hack是不是可以扔了?某个前端老哥用纯CSS做了个井字棋,为了让电脑对手能正常下棋,用HAML生成了成千上万个:target状态。CodePen直接报错说HTML太多了,最后砍成4×4数独才跑起来。这种极限施压的过程反而让人更清楚CSS的边界在哪。
/* 模拟简单逻辑:如果宽度小于400px就变红色 */
.card {
background: lightblue;
}
@container (max-width: 400px) {
.card {
background: tomato;
}
}容器查询这个特性刚出来的时候,有人吐槽“这不就是媒体查询换了个壳吗?”但真上手就会发现,组件级别的响应式让卡片可以自己决定怎么缩放,不用听外面的页面布局瞎指挥。写的时候注意给容器设container-type,不然查询不生效。
把老特性推到极端
有些CSS特性老了容易被人遗忘,但往死里整反而能整出新活。比如:target选择器从2000年初就存在,大多数人都只在做单页目录时用过。但有人拿它做了个纯CSS的电脑对手井字棋:用HAML渲染出几千种可能的棋盘状态,每个状态对应一个:target锚点。点击格子就跳转到新的URL hash,:target匹配到对应状态后重新绘制棋盘。这种用法下页面URL会疯狂变化,浏览器历史记录直接爆炸,后退按钮基本废掉。但这不重要,因为实验目的就是看看:target到底能扛多大压力。
/* :target 伪类实战:切换显示不同面板 */
.panel {
display: none;
}
#panel1:target,
#panel2:target {
display: block;
}
/* 配合 :not 实现默认面板 */
.panel:target ~ .default-panel {
display: none;
}另一个老古董是radio/checkbox hack,2011年就有人玩。2021年有人用Pug生成几百个radio按钮做了个4×4数独,结果Chrome devtools直接卡出无限转圈圈。这翻车现场反而让后来的人知道:CSS生成的内容别超过浏览器承受极限,不然连调试工具都救不了。
局外人视角更容易捡到宝
很多人纠结“CSS算不算编程语言”这种破事。从全栈转前端的人一开始看CSS就头疼,脑核磁共振显示搞设计和搞逻辑激活的脑区真不一样。但恰恰是这种不适感,让那些从命令式编程带过来的思维模式成了优势。比如:
| CSS特性 | 类比JS/框架概念 |
|---|---|
| 自定义属性 | Vue响应式变量 |
| :target选择器 | SPA客户端路由 |
| min()/max()函数 | 逻辑运算模拟 |
有人被checkbox hack启发后,一夜之间用纯文本冒险游戏的形式做了个radio按钮版游戏。设计同事看完直接惊了,不是因为游戏多好玩,而是从没见过这种操作。这种局外人身份反而成了独门武器。
写代码时经常被问“有JS不用干嘛非要用CSS折腾?”这就好比问“写散文更容易,干嘛要写诗?”诗歌就是戴着镣铐跳舞,限制越多,蹦出来的词儿越狠。CSS的边界不是墙,是跳板。下次再遇到某个效果实现不了,别急着掏JS,先想想:有没有两个互相看不顺眼的CSS特性,能拉过来硬凑一对试试?
