前阵子有个叫Cohost的小众社交站悄咪咪黄了,存活时间还没一碗泡面久。但里面藏着一堆CSS骚操作,什么宽度黑客、SVG动画,能把网页整出花活儿。今儿就来扒一扒这些歪门邪道,看看咋用inline样式搞出互动游戏和隐藏彩蛋,哪怕站点没了,这些招儿照样能在别处发光。
宽度黑客
原理扒皮
宽度黑客(width-hacking)这词儿最早是Cohost用户@corncycle起的,核心就俩HTML元素:<details>和<summary>。这俩天生带开关功能——点一下<summary>,里面的隐藏内容就露出来,再点又缩回去。不像老掉牙的checkbox hack需要依赖CSS选择器(Cohost只允许内联样式,写不了选择器),这哥俩直接原生支持,省心多了。
<details>
<summary>点我开关</summary>
<div>藏起来的小秘密</div>
</details>实操整活:造个会亮的按钮
先搞个按钮,点一下就变红亮起来,再点就灭。关键点在于用绝对定位把<summary>固定住,然后塞一个遮罩层<div>,给它加上pointer-events: none。这样点遮罩的时候,点击事件直接穿透到下面的<summary>,开关逻辑照常跑。
<style>
.btn-container {
position: relative;
display: inline-block;
}
.btn-summary {
position: fixed; /* 脱离文档流 */
width: 100px;
height: 40px;
background: gray;
border: outset 2px;
cursor: pointer;
}
.overlay {
position: fixed;
width: 100px;
height: 40px;
background: red;
border: inset 2px;
pointer-events: none; /* 让点击穿透 */
display: none;
}
details[open] .overlay {
display: block; /* 打开时就显示红色遮罩 */
}
</style>
<details class="btn-container">
<summary class="btn-summary">按我</summary>
<div class="overlay"></div>
</details>搞这段代码时,记得给父容器设置position: relative或fixed配合,否则绝对定位的元素会飘到页面左上角。还有pointer-events: none一定要加,不然遮罩层会拦截所有点击,按钮就永远关不掉了。
进阶玩法:用宽度编码状态
上面的按钮只有开和关两种状态,太单调。想做个组合锁或者小游戏,需要几十甚至上百种状态。宽度黑客就是干这个的:把多个<details>塞进一个inline-flex容器,每个<details>里面藏一个特定宽度的小<div>。当某个<details>打开时,它的宽度就变成内部<div>的宽度,整个容器的总宽度就是所有打开项宽度的和。
比如三个<details>,里面分别藏宽度1px、2px、4px的<div>。打开第一个和第三个,容器宽度就是1+4=5px。反过来,看见容器宽度是2px,就能反推出只有第二个是打开的。这样三个开关就能编码8种状态(2³=8)。
<style>
.state-machine {
display: inline-flex;
border: 1px solid #ccc;
}
.bit {
position: relative;
display: inline-block;
width: 0; /* 默认宽度为0,只有打开时才撑开 */
}
.bit div {
width: 100%; /* 继承父级宽度,实际由内部div决定 */
}
.bit1 div { width: 1px; }
.bit2 div { width: 2px; }
.bit4 div { width: 4px; }
.secret {
width: 0;
overflow: hidden;
transition: width 0.2s;
}
.secret .message {
width: 350px;
background: gold;
padding: 10px;
}
</style>
<div class="state-machine">
<details class="bit bit1">
<summary>按钮1</summary>
<div></div>
</details>
<details class="bit bit2">
<summary>按钮2</summary>
<div></div>
</details>
<details class="bit bit4">
<summary>按钮3</summary>
<div></div>
</details>
</div>
<div class="secret">
<div class="message">隐藏彩蛋出现了!</div>
</div>这时候秘密消息还藏不住,得用calc()搞个条件判断:当容器宽度等于某个目标值(比如只打开按钮2时的2px),就把秘密消息的宽度设为350px,否则设为0px。公式可以这样写:
.secret {
width: calc(
min(
max( (父容器宽度 - 1px) * 9999, 0px ),
min(
max( (3px - 父容器宽度) * 9999, 0px ),
350px
)
)
);
}原理就是利用min()和max()构造分段线性函数,让输出只在目标宽度时蹦到350px。实际写的时候要调参数,确保其他宽度下结果趋近0。玩这个的时候注意overflow: hidden要配上,否则宽度为0时内容还会溢出来。另外calc()里别用%直接跟px加减,浏览器可能会懵。
SVG动画
骚操作核心
光有静态彩蛋不够过瘾,还得让画面动起来。常规CSS动画在Cohost的内联样式里没法写@keyframes,但SVG背景+媒体查询曲线救国。SVG文件内部可以塞<style>标签,甚至能用@media查询背景容器的大小。把状态编码成宽度,然后用媒体查询触发SVG里面的元素变化,再加上transition就能丝滑过渡。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
circle {
fill: red;
transition: fill 0.3s;
}
@media (min-width: 101px) {
circle { fill: blue; }
}
</style>
<circle cx="50" cy="50" r="40" />
</svg>上面这段SVG作为div的背景图,当div宽度超过100px时,圆圈的填充色就会从红色渐变成蓝色。但有个坑:某些浏览器(尤其是老版本Chrome)对SVG的CSS动画检测有bug,transition可能不触发。解决办法是加一个1×1像素、完全透明的假动画元素,让它永远在那轻轻抖一下,唤醒浏览器的动画引擎。
<svg>
<style>
/* 假动画 */
#hack {
animation: dummy 0.001s infinite;
}
@keyframes dummy {
from { opacity: 0.999; }
to { opacity: 1; }
}
/* 正常动画 */
text {
transition: transform 0.2s;
}
@media (min-width: 351px) {
text { transform: scale(1); }
}
@media (max-width: 350px) {
text { transform: scale(0); }
}
</style>
<rect id="hack" x="0" y="0" width="1" height="1" opacity="0.999" />
<text x="10" y="50">秘密消息飞出来了</text>
</svg>这个假动画的opacity从0.999变到1,肉眼根本看不出区别,但足以让浏览器认真对待SVG里的过渡效果。把这段SVG设成背景图,再结合宽度黑客的calc()控制容器宽度在350px和351px之间跳变,秘密消息就能带着缩放动画优雅现身。
实战:做个会动的土壤性格测试
曾经有个Cohost用户搞了个测试——“假如是条蚯蚓,会喜欢哪种土壤”。用宽度黑客记录答题状态(比如每个问题对应一个<details>,打开代表选A,关闭代表选B),最后根据总宽度触发SVG背景里不同的<text>元素显示结果。再加上transition,切换结果时文字会滑入滑出,观感直接拉满。
实现的时候记得给每个<details>内部的小<div>宽度设成2的幂次(1,2,4,8…),这样每种答案组合的宽度唯一。然后在SVG里写一堆媒体查询,每个<text>对应一个宽度区间。为了防止媒体查询太多导致代码爆炸,可以用min-width和max-width组合划定范围。
| 状态值 | 容器宽度 | 显示结果 |
|---|---|---|
| 0001 | 1px | 沙壤土 |
| 0010 | 2px | 黏壤土 |
| 0011 | 3px | 粉壤土 |
| 0100 | 4px | 泥炭土 |
整个流程跑通后,效果就跟那些点一点就变脸的互动海报一样,溜得飞起。唯一要注意的是移动端上拖拽缩放可能影响媒体查询的触发条件,最好在桌面浏览器测试。
好了,先唠到这儿。剩下的歪门邪道比如resize属性搞拖拽游戏、伪元素做小动画,下次有机会再拆。Cohost虽然凉透了,但这些骚操作换个地儿照样能整活。
