有时候写样式真的挺想偷懒的,比如说想把某个属性的值直接拿来给另一个属性用,而且压根不关心它具体是多少,哪怕它后面自己变了也懒得管。可惜CSS里目前还没有一个叫compute()的神器能干这事儿。但咱也不能就这么干等着,总得找点法子把活儿干漂亮了。今儿个就来盘一盘,在没法直接“复制粘贴”属性值的情况下,有哪些野路子能让样式写起来更顺手。
搞明白为啥直接引用这么难
这玩意儿其实在圈子里被提过好多回了,但一直没落地,主要是因为CSS的解析机制跟咱想的有点不一样。浏览器渲染页面的时候,属性值是按照特定顺序算出来的,要是允许随便互相引用,那计算顺序就乱套了,跟套娃似的谁也说不清谁先算。不过话说回来,虽然官方大招没憋出来,社区里早就折腾出一堆骚操作,而且好多已经能直接用起来了。
自定义属性玩出花
这招算是目前最稳当的,核心思路就是提前把值存起来。既然不能直接读别人的属性,那就把值放进自定义属性这个“保险箱”里,谁用谁取。
第一步:给元素立个标尺
想象一下要做个按钮,想让圆角大小跟着高度走,但又不知道按钮最后多高,这时候直接给按钮的高度和圆角都喂同一个变量就行:
.button {
--btn-size: 3rem;
height: var(--btn-size);
border-radius: calc(var(--btn-size) * 0.3);
}这里边--btn-size就是那个“标尺”,高度和圆角都从它那儿拿数。好处是一旦想改按钮大小,改这一个变量就完事儿,圆角自动跟着变,不用再算一遍。
第二步:让标尺能传下去
有时候组件是嵌套的,比如头部里头有个按钮,头部本身的圆角可能也要跟按钮大小有点关系。这时候把变量挂在更上层就更方便:
:root {
--base-size: 3rem;
}
.header {
--header-pad: 1rem;
padding: var(--header-pad);
--header-total: calc(var(--base-size) + (var(--header-pad) * 2));
border-radius: calc(var(--header-total) * 0.2);
.button {
height: var(--base-size);
border-radius: calc(var(--base-size) * 0.3);
padding-inline: calc(var(--base-size) * 0.5);
}
}这么写之后,头部和按钮的圆角都跟--base-size绑定了。万一设计拍板说要放大一圈,改--base-size一个地方,所有关联的样式全跟着动。不过这里有个坑,计算--header-total的时候得确认用到的变量都已经声明过了,要是变量没定义,calc就会直接摆烂。另外变量声明的位置也重要,挂:root上全局都能用,但要是只想在某个模块里生效,就挂在那个模块的容器上。
未来神器inherit()函数
这个玩法现在浏览器还不支持,但规格已经写得差不多了。可以理解成继承关键字的大升级版,以前继承只能原封不动拿父级的值,现在能在拿过来之后用calc再加工。
想象一下有个头部高度固定,里边的按钮想用头部高度的一半当圆角:
/* 这只是个美好愿景,目前还没浏览器接招 */
header {
height: 3rem;
button {
height: 100%;
border-radius: calc(inherit(height) * 0.3);
}
}要是这玩意儿落地了,很多场景就不用折腾自定义属性了,直接引用父级属性值,代码写起来跟说话似的。不过目前只能在演示里过过眼瘾,生产环境还得靠前面的自定义属性方案。
aspect-ratio救场宽高互推
这属性简直是宽高互相引用的救星。不需要知道具体数值,只告诉浏览器宽和高是啥比例关系,它自己就能算。
比如弄个卡片,想让高度永远是宽度的一半,不管屏幕多宽:
.card {
width: 100%;
aspect-ratio: 2 / 1;
}这里边宽是2份高是1份,浏览器拿到宽度之后就自动算出高度。要是想搞个正方形更简单,aspect-ratio: 1 / 1就搞定。但这玩意儿有个局限,它只能处理宽高之间的关系,没法拿高度去算别的属性,比如让内边距也跟着高度变,它就管不着了。用的时候还得确认容器本身宽度是确定的,不然浏览器没法算。
currentColor和它的亲戚们
这是个老牌玩法了,用来复用颜色值贼顺手。currentColor拿的是当前元素的文字颜色,可以在任何需要颜色的地方直接用。
比如做了个按钮,想让边框跟文字一个色,而且文字色可能会换:
.btn {
color: #0ea5e9;
border: 2px solid currentColor;
background: hsl(from currentColor h s 90%);
}这么写之后,边框和背景的色调都跟着文字色跑。后面要是把color改成#f97316,边框和背景也跟着变,不用再去改border-color和background-color。不过currentColor只能拿颜色值,要是想拿宽度、高度啥的就使不上劲儿了。圈里也有人提过currentBackground、currentWidth之类的建议,真能落地的话那可就太香了。
CSS单位里藏着的秘密
很多单位本身就能表达相对关系,用好了完全不需要额外的引用机制。
vw、vh玩转视口
想让一个弹窗的宽度和高度跟视口有关系,直接用视口单位:
.modal {
width: 80vw;
height: 50vh;
}这么写之后,不管在啥屏幕上都占视口的80%宽和50%高。但这里有个坑,vh在移动端浏览器上可能不太准,地址栏伸缩会影响实际视口高度,得小心测试。
ch、lh这类相对单位
ch是字符0的宽度,用来控制文本框宽度特别合适,比如想让人家输手机号,给个14ch就能大致框住:
input {
width: 14ch;
}lh是行高的值,用来控制段落间距很有用。比如说想让段落底边距刚好是一行的高度:
p + p {
margin-top: 1lh;
}这样不管字体多大、行高设多少,段间距都能自动匹配。但ch和lh这类单位在字体加载前后可能会有差异,特别是用自定义字体的时候,最好等字体加载完再看效果。
容器查询单位真香
要是用了容器查询,就能拿到容器本身的大小,这在组件化开发里特别好使。
先给容器声明容器类型:
.card-container {
container-type: inline-size;
}然后在容器里的组件就能用容器单位了:
.card {
padding: 2cqw;
}这里2cqw是容器宽度的2%,不管父容器多宽,内边距都跟着成比例。更骚的是可以结合calc搞出更复杂的计算:
.card {
border-radius: calc(3cqw + 0.5rem);
}这么写圆角既有一部分固定值,又有一部分跟着容器大小走,在小屏幕上不至于太夸张,在大屏幕上又能成比例放大。但用容器查询单位的前提是父容器得设置了container-type,而且容器本身得有确定的宽度,不然cqw就是0。
打个赌:未来可期的compute和关键词们
说实话,要是能有个compute()函数直接拿别的属性值,那写样式简直能起飞。比如:
/* 纯属脑补,现实中还不能这么写 */
.special-card {
border-radius: compute(height, self);
margin-top: compute(width, parent);
}或者退一步,多来点currentColor这种关键词也行啊。像是currentWidth、currentHeight这种,拿来直接用多爽,写个正方形都不用算半天。从-font这种虽然小众,但至少说明CSS工作组已经在琢磨怎么让属性值互相引用了。
现在能用的这些法子,自定义属性稳是稳但写起来啰嗦,aspect-ratio和容器单位覆盖了很多宽高关联的场景,currentColor解决了颜色复用的大头。虽然都没法做到万能引用,但把它们组合起来用,大部分需要“偷懒”的场景都能找到解法。只要摸透每种方式的适用边界,写起样式来照样能又快又稳。
