2026-08-06 20:24:54
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12341
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
在过去,渐变边框的主要实现方法,一是嵌套或伪元素模拟嵌套,二是使用border-image属性,相关实现在“被低估的border-image属性”一文中有介绍。
但是border-image属性实现渐变边框有个知名的弱点,那就是不支持圆角!

如果支持圆角,哪怕border-image属性的语法再难以理解,也还是与应用场景的。
本以为这件事情就到此为止了,没想到峰回路转,background-clip属性升级了,居然可以用来实现边框背景效果了,那自然将边框背景设置成渐变不再话下,需要用到全新的属性值 border-area.
例如:
<style>
canvas {
border: 1em solid #0000;
background: linear-gradient(deepskyblue, deeppink);
background-origin: border-box;
background-clip: border-area;
border-radius: 20px;
}
</style>
<canvas></canvas>
就可以得到如下图所示的圆角渐变边框效果了:

效果很赞!
这里有一些细节需要说明。
当值border-area值合写的时候,background-origin属性是可以省略的,也就是:
canvas {
border: 1em solid #0000;
background: linear-gradient(deepskyblue, deeppink) border-area;
border-radius: 20px;
}
但是如果分开书写,就必须设置background-origin:border-box,否则按照默认的padding-box渲染,渐变边框的效果会比较奇怪,会像下面这样。

目前Chrome和Safari都已经支持边框背景图了。

上上个月介绍了全新的CSS border-shape属性,可以轻松实现不规则边框效果,于是可以强强联合,实现不规则图形的特殊边框效果。
例如:
<style>
.star {
width: 150px;
aspect-ratio: 1;
border: 10px dotted #0000;
border-shape: polygon(
50% 5%,
61.8% 34.5%,
95% 34.5%,
68.9% 57.3%,
79.4% 90.45%,
50% 69.1%,
20.6% 90.45%,
31.1% 57.3%,
5% 34.5%,
38.2% 34.5%
);
box-shadow: 2px 2px 4px #0008;
background: conic-gradient(from -36deg,
deepskyblue 72deg,
deeppink 0 144deg,
rebeccapurple 0 216deg,
orange 0 288deg,
green 0
) border-area;
}
</style>
<canvas class="star"></canvas>
此时,就可以得到如下图所示的五角色值都不一样的图像效果了:

除了边框支持背景,文本也支持背景了。
实际上,在Chrome浏览器诞生之初,其就支持background-clip:text了,不过那时候还是私有特性,非Web标准。
现在迎来了好消息,自Chrome 120之后,background-clip:text无需私有前缀了,已经晋升为CSS标准属性了。

于是乎,我们就可以将边框背景和文字背景结合起来使用,实现更加炫酷的按钮效果。
例如:
<style>
.button {
font-size: 14px;
padding: 6px 12px;
border: 1px solid #0000;
border-radius: 4px;
color: transparent;
background: linear-gradient(deepskyblue, deeppink) border-area text,
linear-gradient(#fff, #fe000005);
&:active {
filter: brightness(0.95);
}
}
</style>
<button class="button">我是按钮</button>
实时渲染效果如下(需要Chrome 150+):

没啥好说的,是个相当实用的特性,要是10年前出现,肯定会有很多讨论,朋友圈也可以看到相关文章的转发。
现在么,就像是路边的石头,无人问津。
最后,炫一下周末的渔获吧。
周六老鬼垂钓农家乐,特价108元8小时,钓了32斤;周日天天渔场,188元6小时,钓了七八十斤,两个鱼护。

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12341
(本篇完)
2026-08-03 21:20:14
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12316
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
间隙装饰线其实由来已久,在大多数前端从业人员还未入行的时候就已经支持了,大家如果查看 column-rule 属性的兼容性,会发现极佳。

但是,请注意,column-rule 属性生效是有前提的,那就是只能在Columns布局中有效。
而Columns布局我已经快10年没有在实际项目中使用了。
同时,之前的column-rule 属性所设置的装饰线某种程度上和border属性类似,不能设置缩进,不支持多属性,算不得强大。
所以,column-rule 属性可谓名存实亡。
只有抱紧Flex布局和Grid布局的大腿,column-rule 属性才可以真正迎来春天。
等啊等,等啊等,在2026年的某天,终于,全新的装饰线效果可以在Flex/Grid布局中生效了。
比方说这个演示页面:使用 Gap 装饰实现分屏布局demo
核心CSS代码:
.flex-split {
gap: 1px;
column-rule-width: 2px;
column-rule-style: dashed, solid;
column-rule-color: #d4d0c8;
row-rule-width: 2px;
row-rule-style: dashed, solid;
row-rule-color: #d4d0c8;
}
@media (width
于是,在宽屏下,垂直装饰线效果生效:

手机设备下,则是水平方向的装饰线效果:

比之前使用伪元素,或者插入分隔线元素实现简单多了。
除了基本的分隔线装饰效果,还顺便带来了众多其他强大的特性,例如支持多属性值、交点处如何中断、间距边缘距离控制、repeat()函数语法、动画支持等。
我们一个一个来看下。
二、多属性值和 repeat() 语法
如今,无论是column-rule属性,row-rule属性,亦或是他们的子属性,都支持同时设置多个值,分别表示从开始到结束各自的分隔线样式效果。
且看一个极为简单的案例,HTML和CSS代码如下:
<ul>
<li>《CSS世界》</li>
<li>《CSS选择器世界》</li>
<li>《CSS新世界》</li>
<li>《HTML并不简单》</li>
</ul>
ul {
display: flex;
flex-direction: column;
gap: .5em;
row-rule: 2px dashed, dotted;
row-rule-color: red, blue, green;
}
此时,可见如下图所示的分隔线效果:

我们来看下属性值是如何生效的。
先看row-rule属性。
列表共有四项,所有会有3道分隔线。
-
2px dashed显然对应第一条线;
-
dotted则对应第二条,然后这里的宽度规则和border属性一致,默认都是medium尺寸,也就是3px,因此,这里实际渲染等同于 3px dotter;
-
row-rule/column-rule的多值属性走的是平铺规则。也就是:
- 如果仅一个值,那所有的gap线都使用该值;
- 如果是2个值,则两两循环匹配;
- 更多数量的值一样不断循环平铺渲染。
所以,这里,第三条线的样式也是2px dashed。
再看row-rule-color属性,这个很好理解,三条线分别三个颜色,一次是红蓝绿。
那么问题来了,如果我设置4个颜色值,会怎么样呢?
答案是:不会怎么样,超出数量的属性值会被忽略。
repeat()函数
如果分隔线的数量很多,我们一个一个值去设置,会累死人的,代码也啰嗦,此时可以使用repeat()函数简化我们的属性设置。
比方说下面这个案例:row-rule应用repeat()函数demo
核心CSS代码参见下方:
.flex-settings {
row-gap: 1px;
row-rule: 1px solid #243352;
row-rule-width: repeat(2, 1px), 4px;
rule-inset-start: 16px;
}
可得到如下图所示的效果,可以看到,风格线的宽度有着 1px 1px 4px 不断循环渲染的效果。

三、rule-break与交叉中断控制
在一个二维布局中,例如Grid布局,或者设置了flex-wrap:wrap出现换行的Flex布局,同时出现纵向分隔线和横向分隔线是再正常不过的事情。
于是就会出现纵线和横线互相交叉相遇的场景。
默认情况下,两线是相交的,但很多时候,纵横相交的效果并不好快,最好希望互相保留安全距离。
则可以使用CSS rule-break属性,设置rule-break:intersection声明就好了。
请看案例:
<section>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
</section>
section {
display: grid;
width: 300px;
grid: 1fr 1fr / 1fr 1fr 1fr;
gap: 1rem;
canvas {
aspect-ratio: 1;
background: deepskyblue;
width: 100%;
}
column-rule: solid red;
row-rule: solid green;
/* rule-break: intersection */
}
此时可以看到水平方向和垂直方向的分隔线相交了。

此时,我们给section元素设置rule-break:intersection,原本交叉的地方互相留白了,如下截图所示:

四、分隔线的扩展与收缩
column-rule-inset 和 row-rule-inset 属性用于控制装饰在间距内的扩展距离。
如果无需区分水平和垂直距离,或者水平和垂直距离一致,也可以直接使用 rule-inset 属性。
默认情况下,rule-inset 属性设置的缩进距离是对称的,如果开发者希望变化的距离是不对称的,那么可以使用以下两个CSS属性。
一例胜千言,还是上面的案例,我们分别设置rule-inset-cap和rule-inset-junction的值,来看看最终的渲染效果。
关键CSS代码如下:
section {
column-rule: solid red;
row-rule: solid green;
rule-break: intersection;
rule-inset-cap: 5px;
rule-inset-junction: 20px;
}
可以看到,交叉点位置的缩进极大,而四边的端点缩进为5px,明显小一圈,即所谓的不对称缩进。

无论是rule-inset-cap还是rule-inset-junction属性都支持负值,此时装饰线就不是缩进,而是扩张效果。
另外,如果只是希望调整某一个方向的装饰线的缩放效果,可以加上 column- 或者 row- 前缀,例如 column-rule-inset-cap 和 column-rule-inset-junction 属性。
还是上面的案例,区别在于Grid布局的子元素只有4项,此时,间隙装饰线的效果就会让人感到困惑。
明明没有元素项,结果却凭空出现了分隔装饰线。

此时就轮到 column-rule-visibility-items 和 row-rule-visibility-items 属性出马了。
如果无需区分方向,则可以使用合写属性 rule-visibility-items,其语法如下:
normal(默认):取决于容器类型。根据我的测试,大多数场景下效果和 all 一致。all:规则显示在每个间距中,即使是没有元素的间距也是如此。around:只要存有一个或多个相邻项,间隙线就会显示。between:间隙线仅会出现在两个相邻项之间。下图示意了 around 和 between 这两个属性值的区别。

相信还是很好理解的。
间隙分隔线的宽度、颜色和交叉缩进都是可添加动画效果的,具体不展示,这个懂的都懂,设置transition过渡,自然可以看到效果。
下面说下兼容性,Gap装饰是相当新的一个特性,目前只有Chrome 149+ 浏览器才支持,如下截图所示:

唉,东西虽好,但现在还没命享用啊,等过几年兼容性跟上了,前端圈子还不知道变成什么样子呢!
好了,就说这么多吧。
参考文章:

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12316
(本篇完)
2026-07-29 11:27:05
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12253
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
screen.isExtended属性和window.getScreenDetails()都可以判断当前用户是否是多显示器。
代码示意:
if (screen.isExtended) {
console.log("用户拥有多显示器");
}
例如,阿拉的电脑现在就插着外接显示器,运行的结果就是 true.

我们也可以使用window.getScreenDetails()判断用户是否拥有多个显示器。
代码示意:
async function checkMultipleScreens() {
if ("getScreenDetails" in window) {
try {
// 注意:必须在用户交互(如点击事件)中触发,否则会抛出异常
const screenDetails = await window.getScreenDetails();
// 3. 通过 screens 数组长度精准判断
const isMultiMonitor = screenDetails.screens.length > 1;
console.log(`屏幕总数: ${screenDetails.screens.length}`);
return isMultiMonitor;
} catch (err) {
console.error("用户拒绝授权或调用失败", err);
return false;
}
} else {
console.log("当前浏览器不支持 getScreenDetails");
return false;
}
}
getScreenDetails()方法返回的是个Promise,第一次执行的时候,浏览器会出现权限申请提示,如下截图所示:

如果用户点击“屏蔽”,则会返回NotAllowedError错误:
Uncaught (in promise) NotAllowedError: Permission denied.
此时,我们可以点击地址栏前面的小图标,将权限开启,如下截图所示:

可见,如果只是单纯判断显示器是否包含多个,一定是使用screen.isExtended属性,只有需要精确知道显示器个数的时候,才需要考虑getScreenDetails()方法。
这两个特性的兼容性都是一样的,都是Chrome 100支持的,目前Firefox和Safari都没有支持。

下面讲下如何更加智能地控制新窗口打开这一交互。
之前我们都是使用window.open()来打开新窗口、调整现有窗口的大小以及关闭现有窗口等。
例如,要在屏幕的左上方50px处打开一个800×600的窗口:
const myWindow = window.open( "https://www.zhangxinxu.com/", "myWindow", "left=50,top=50,width=800,height=600", );
然而,上述功能存在局限性。
window.screen只能返回主屏幕的数据,而不包括设备可用的辅助显示器。
如果我们想要要将窗口移动到辅助显示器,虽然可以使用window.moveTo()方法,但你具体的坐标需要通过猜测才能得到,这显然不太靠谱。
而window.getScreenDetails()方法可以返回所有屏幕的具体信息,此时,我们就可以精准控制新窗口的尺寸和位置。
我们来看几个案例。
您可以狠狠地点击这里:Window Management API 多屏显示演示
如果你有多个显示屏,那么点击下图所示的按钮:

会出现窗口管理的权限提示,这里点击运行。
然后,还会出现“请禁用弹窗拦截”的自定义提示,此时,点击地址栏右侧的图标,开启允许弹出式窗口,如下截图所示。

然后再次点击按钮,就会看到第一屏平铺了三个窗口,第二屏则是一个全屏窗口,截图示意如下:

然后关闭任意窗口,其他窗口都会关闭。
相关的运行代码参考:
const screenDetails = await window.getScreenDetails();
if (screenDetails.screens.length > 1) {
const screen1 = screenDetails.screens[0];
const screen2 = screenDetails.screens[1];
// 1/3窗口宽度,100%窗口高度
const windowWidth = Math.floor(screen1.availWidth / 3);
const windowHeight = screen1.availHeight;
//主屏打开三个参考窗口
openWindow(screen1.availLeft,
screen1.availTop,
windowWidth,
windowHeight,
sites[1].url);
openWindow(screen1.availLeft + windowWidth,
screen1.availTop,
windowWidth,
windowHeight,
sites[2].url);
openWindow(screen1.availLeft + windowWidth * 2),
screen1.availTop,
windowWidth,
windowHeight,
sites[3].url);
// 副屏打开一个全屏窗口
openWindow(screen2.availLeft,
screen2.availTop,
screen2.availWidth,
screen2.availHeight,
sites[0].url);
}
窗口关闭的原理示意如下:
const windowRef = window.open( url, "_blank", windowFeatures, ); // 如果需要关闭 windowRef.close();
您可以狠狠地点击这里:屏幕切换显示、全屏、信息获取等综合演示demo
例如,下面截图示意的就是我当前两个显示器的基础信息显示:

其他一些功能,大家可以点击演示页面对应按钮体验,这里就不赘述了。

相关源码大家可以右键页面查看,或者让AI帮你分析。
getScreenDetails()方法会返回ScreenDetails对象。
ScreenDetails对象包含属性和事件各两个,如下示意。
属性
返回一个单独的ScreenDetailed对象,表示当前浏览器窗口所显示屏幕的详细信息。
ScreenDetailed对象数组,包含所有屏幕信息(注意,镜像屏幕不在其中)。
方法
当窗口的当前屏幕以某种方式发生变化时触发,例如可用宽度或高度,或方向发生了变化。
当屏幕与系统连接或断开时触发。
ScreenDetailed对象包含屏幕的位置尺寸等信息。
| 参数名称 (Property) | 读写权限 (Access) | 数据类型 (Type) | 功能描述 (Description) |
|---|---|---|---|
availLeft |
只读 (Read only) | 数字 (Number) | 表示可用屏幕区域的 X 轴坐标(左侧边缘位置)。 |
availTop |
只读 (Read only) | 数字 (Number) | 表示可用屏幕区域的 Y 轴坐标(顶部边缘位置)。 |
devicePixelRatio |
只读 (Read only) | 数字 (Number) | 表示该屏幕的设备像素比(DPR)。 |
isInternal |
只读 (Read only) | 布尔值 (Boolean) | 指示该屏幕是设备的内置(内部)显示器还是外接(外部)显示器。 |
isPrimary |
只读 (Read only) | 布值 (Boolean) | 指示该屏幕是否被操作系统(OS)设置为主显示器。 |
label |
只读 (Read only) | 字符串 (String) | 提供该屏幕的描述性名称标签,例如 “Built-in Retina Display”(内置视网膜显示屏)。 |
left |
只读 (Read only) | 数字 (Number) | 表示整个屏幕总区域的 X 轴坐标(左侧边缘位置)。 |
top |
只读 (Read only) | 数字 (Number) | 表示整个屏幕总区域的 Y 轴坐标(顶部边缘位置)。 |
基本上我们日常开发,就是围绕上述这些属性值进行的。
点评下吧。
本文介绍的Window Management API算是对过往新窗口打开这套实现的增强与补充。
不过由于在实操过程中,有过多的权限申请,还需要用户允许新窗口打开,对齐应用前景我不太看好。
只能用在某些特殊的交互场合。
例如,新窗口模式的支付弹框,成功后自动关闭这种。
倒是screen.isExtended这个判断屏幕数量的属性使用简单,说不定以后会经常被使用。
不过眼下受制于兼容性,暂时还看不到在生产环境实践的可能。
唉,注定又是一个冷门特性。
行了,就说这么多吧,最后秀下周末两场的渔获。
周六鱼泡钓场,168六小时,草鱼爆箱,11条草鱼还有翘嘴鳊鱼罗非。
周日阿坤钓场,99六小时特价,同样爆箱,大昂刺鲫鱼鳊鱼,好搞好搞。

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12253
(本篇完)
2026-07-23 15:39:57
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12312
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
去年9月有介绍过CSS @function自定义函数,那么有没有可能自定义一个名为repeat(--n, anything)的函数,实现字符循环拼接或者数值相加的功能呢?
还真可以,例如:
<div class="target"></div>
@function --repeat(--n, --x) {
/* 略 */
}
.target:before {
--repeat-demo: calc(0 --repeat(16, + 10));
width: calc(var(--repeat-demo) * 1px);
counter-reset: result var(--repeat-demo);
content: counter(result) "px" --repeat(3, " 👽");
display: block;
line-height: 30px;
background: deepskyblue;
}
此时,可以看到,尺寸的160px生效了,result计数器的数值也生效了,以及--repeat(3, " 👽")的字符串拼接也生效了。
如下图所示:

眼见为实,您可以狠狠地点击这里:CSS自定义任意可重复可相加函数demo
所以,@function --repeat(--n, --x)这个自定义函数里面究竟是怎么设置的,居然可以有这样的效果呢?
此自定义CSS函数代码是固定的,粘贴即可使用:
@function --repeat(--n, --x) {
--bit7: calc(round(down, var(--n) / 128));
--val6: calc(var(--n) - var(--bit7) * 128);
--bit6: calc(round(down, var(--val6) / 64));
--val5: calc(var(--val6) - var(--bit6) * 64);
--bit5: calc(round(down, var(--val5) / 32));
--val4: calc(var(--val5) - var(--bit5) * 32);
--bit4: calc(round(down, var(--val4) / 16));
--val3: calc(var(--val4) - var(--bit4) * 16);
--bit3: calc(round(down, var(--val3) / 8));
--val2: calc(var(--val3) - var(--bit3) * 8);
--bit2: calc(round(down, var(--val2) / 4));
--val1: calc(var(--val2) - var(--bit2) * 4);
--bit1: calc(round(down, var(--val1) / 2));
--bit0: calc(var(--val1) - var(--bit1) * 2);
--pow0: var(--x);
--pow1: var(--pow0) var(--pow0);
--pow2: var(--pow1) var(--pow1);
--pow3: var(--pow2) var(--pow2);
--pow4: var(--pow3) var(--pow3);
--pow5: var(--pow4) var(--pow4);
--pow6: var(--pow5) var(--pow5);
--pow7: var(--pow6) var(--pow6);
result: if(style(--bit0 = 1): var(--pow0); else: ;)
if(style(--bit1 = 1): var(--pow1); else: ;)
if(style(--bit2 = 1): var(--pow2); else: ;)
if(style(--bit3 = 1): var(--pow3); else: ;)
if(style(--bit4 = 1): var(--pow4); else: ;)
if(style(--bit5 = 1): var(--pow5); else: ;)
if(style(--bit6 = 1): var(--pow6); else: ;)
if(style(--bit7 = 1): var(--pow7); else: ;);
}
或者直接@import在线的CSS地址。
@import url("https://unpkg.com/@propjockey/[email protected]/functions/repeat.css");
1. 字符串循环拼接
@import url("./repeat.css");
body::before {
content: --repeat(7, "😂 ");
}
实时渲染效果如下:
2. text-shadow文字阴影
@import url("https://unpkg.com/@propjockey/[email protected]/functions/repeat.css");
@import url("https://unpkg.com/@propjockey/[email protected]/functions/repeat-join.css");
body::before {
content: --repeat(7, "CSS新世界 📚 ");
text-shadow: --repeat-join(4, {, }, 0px 0px 2px deeppink);
}
实时渲染效果如下:
3. 使用drop-shadow()滤镜代替
@import url("./repeat.css");
body::before {
content: --repeat(3, "CSS新世界 📚 ");
filter: --repeat(4,
drop-shadow(1px 1px deeppink)
);
font-weight: bold;
color: white;
font-family: system-ui;
}
实时渲染效果如下:
4. 在shape()函数中使用repeat
@import url("repeat.css");
@import url("repeat-join.css");
body::before {
content: --repeat(3, "HTML并不简单 📚 ");
display: inline-block;
border-shape: shape(
from 0 30px,
--repeat-join(255, {,}, {line by 5px -6px, line by 5px 6px})
);
height: 36px;
border: 1px solid deeppink;
clip-path: inset(0px);
}
实时渲染效果如下所示:
这是一个非常巧妙的 CSS @function,核心思想是用二进制分解 + 倍增(快速幂思想)来实现”重复 N 次”。下面拆解原理。
--repeat(--n, --x) 要返回:把 --x 这个 token(可以是字符串片段、也可以是某个属性值单元)平铺重复 --n 次,中间用空格连接。
比如 --repeat(5, a) → a a a a a(5 个 a)。
难点在于:CSS 没有循环,无法写 for (i=0; i<n; i++)。于是作者用了二进制展开这个数学技巧。
--bit7: calc(round(down, var(--n) / 128)); --val6: calc(var(--n) - var(--bit7) * 128); --bit6: calc(round(down, var(--val6) / 64)); ... --bit0: calc(var(--val1) - var(--bit1) * 2);
这段就是十进制转二进制的标准”逐位提取”算法。round(down, …) 是向下取整(等价于 floor)。
以 --n = 5 为例(二进制 00000101):
| 步骤 | 计算 | 结果 |
|---|---|---|
| bit7 | floor(5/128) | 0,余 5 |
| bit6 | floor(5/64) | 0,余 5 |
| bit5 | floor(5/32) | 0,余 5 |
| bit4 | floor(5/16) | 0,余 5 |
| bit3 | floor(5/8) | 0,余 5 |
| bit2 | floor(5/4) | 1,余 1 |
| bit1 | floor(1/2) | 0,余 1 |
| bit0 | 1 – 0 | 1 |
所以 --bit2 = 1、--bit0 = 1,其余为 0 → 对应 5 = 4 + 1 = 2² + 2⁰。
由于只到 bit7(128),这个函数支持的最大 N 是 255(2⁸ - 1)。
--pow0: var(--x); /* 1 个 x (2^0) */ --pow1: var(--pow0) var(--pow0); /* 2 个 x (2^1) */ --pow2: var(--pow1) var(--pow1); /* 4 个 x (2^2) */ --pow3: var(--pow2) var(--pow2); /* 8 个 x (2^3) */ ... --pow7: var(--pow6) var(--pow6); /* 128 个 x (2^7) */
这是关键的倍增(doubling)技巧:每一级都把上一级的内容拼两遍,于是 --powK 恰好包含 2^K 个 --x,用空格分隔。
注意这里不需要循环也不需要条件——纯粹靠变量自引用的层层展开,--pow7 展开后就是实实在在的 128 个 token 并排。
这一步类似”生成 8 个预制的积木块,长度分别是 1、2、4、8、16、32、64、128″。
result: if(style(--bit0 = 1): var(--pow0); else: ;)
if(style(--bit1 = 1): var(--pow1); else: ;)
...
if(style(--bit7 = 1): var(--pow7); else: ;);
if(style(--bitK = 1): …; else: ;) 是新的 CSS 内联条件(if() 函数 + style() 查询):
--powK(含 2^K 个 x);else: 后面是空的)。把 8 个条件的结果首尾相接,就得到总数为 Σ (bitK × 2^K) 个 x —— 这正好等于 --n。
以 --n = 5(bit2=1, bit0=1)为例:
--pow0 = 1 个 x--pow2 = 4 个 x4 + 1 = 5 个 x ✅把”重复 N 次”这个无法用循环表达的操作,转化为”N 的二进制分解”问题:先用倍增法预生成 1、2、4、…、128 份副本,再根据 N 的每一个二进制位决定是否把对应份数拼进结果,最终拼出的总份数恰好等于 N。
这本质上就是快速幂 / 二进制拆分思想在 CSS token 拼接上的应用,把原本需要 O(N) 次循环的重复,压缩成 O(log N) = 最多 8 个固定的条件判断。
几个值得注意的细节
--x 可以是”字符串或属性值”:CSS 自定义属性存的是未解析的 token 序列,--x 只是被当作一段 token 反复拼接,函数本身不关心它语义上是关键字、长度值还是字符串片段,所以既能拼 grid-template-columns 的 1fr,也能拼平铺文本片段。最终由使用方的属性去解释这串 token 的含义。
var(--pow0) var(--pow0) 之间天然有空格,if() 各段之间也有空格。所以它做的是”空格分隔的平铺”,非常适合 grid-template-columns: --repeat(10, 1fr) 这类场景(等效于 repeat(10, 1fr) 展开)。
bit7 / pow7。想支持更大,再往上加 bit8 / pow8(256)即可,按同样规律扩展。
@function、if() + style() 查询、round() 数学函数——都属于较新的 CSS 规范(CSS Values 4 / CSS Functions),目前浏览器支持仍在铺开阶段,需注意兼容性。
本文内容让我不由得想起4月份介绍的蜂巢布局效果,也就是金字塔布局:CSS六边形头像的实现与蜂巢布局。
其中,也用到了CSS if()函数和大量的数学计算。
不过,这种案例炫技的成分大一些,还不太适合用在生产环境。
实际开发,多半还是老老实实字符平铺写死,而不会使用自定义的 --repeat 函数。
好了,就说这么多吧,算是增长眼界的一篇文章了。
参考文章:
😉😊😇
🥰😍😘
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12312
(本篇完)
2026-07-17 17:01:24
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12289
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。
一不留神,CSS又多了几十个我不认识的单位。
来来来,排排站,给大家露个脸。
dvb, dvh, dvi, dvmax, dvmin, dvw, svb, svh, svi, svmax, svmin, svw, lvb, lvh, lvi, lvmax, lvmin, lvw, vi, vb cqw, cqh, cqi, cqb, cqmin, cqmax
赶快花时间学习一番。
dvh这个单位应该是最为常用的,全称 Dynamic Viewport Height,即动态视区高度。
我们平时用的比较多的是vh这个单位,表示视区高度。
但是在iOS Safari浏览器下,页面浏览的时候,如果你滚动,会发现菜单啊,导航啊这些会被滚走,此时,视区的高度变化了。比方说下面录屏视频底部的地址栏,滚动的时候高度会变小。
如果还是使用传统的vh单位,布局就会有问题。
此时,就可以使用dvh这个CSS单位,1dvh就是1%的动态视区高度。
比方说,我们构建一个全屏覆盖的黑色半透明遮罩层,则可以使用下面的CSS代码:
.fullscreen {
height: 100dvh;
background: #0009;
}
至于对应的宽度单位dvw就没什么应用价值了,因为网页视窗的宽度一般是不存在所谓的动态变化的。
svh是Small Viewport Height的缩写,表示假定浏览器所有工具栏全部展开(可见区域最小)的高度。
lvh则是Large Viewport Height的缩写,是假定浏览器所有工具栏全部收起(可见区域最大)的高度。
个中区别参见下表:
| 单位 | 全称 | 行为模式 | 最佳适用场景 |
|---|---|---|---|
svh |
Small Viewport Height | 假定浏览器所有工具栏全部展开(可见区域最小)的高度。 | 固定的底部常驻菜单,确保绝对不会被遮挡。 |
lvh |
Large Viewport Height | 假定浏览器所有工具栏全部收起(可见区域最大)的高度。 | 大气的背景大图,追求极致的视觉延伸感。 |
dvh |
Dynamic Viewport Height | 随工具栏的显示或隐藏动态实时变化的高度。 | 全屏 App 界面、侧边栏菜单、全屏表单。 |
本文开头出现的几十个单位中,有1/3的单位要么以i结尾,要么以b结尾。
这些单位都是CSS逻辑单位。
i是inline的首字母,默认表示水平方向,例如单位vi在绝大多数场景下,其实和vw的表现一样。b是block的首字母,默认表示垂直方向。其实类似的CSS逻辑属性还挺多,比方说用得比较多的:
margin-inline: 20px; padding-block: 1rem;
从实用角度来看,这些CSS逻辑单位大家了解下即可,因为平时很少会用到需要改变水平或垂直排版流的场景,没有这样的场景,自然也就没有使用的必要。
所以,类似与vi这样的单位,除了装逼以外,没有任何作用,还是老老实实使用vw吧。
说起cq家族,不知道为何脑中突然出现葬爱家族,哈哈哈。

cq是container query的简介,中文译为容器查询。
本博已经多次介绍过:
是非常实用的特性。
其中用的最多的单位就是cqw,1cqw就是1%的对应容器的宽度大小,这个在弹性布局中很有用。
至于其他单位,也就好理解了,参加下表:
| 单位 | 全称 | 计算规则 | 适用场景 |
|---|---|---|---|
cqw |
Container Query Width | 查询容器宽度的 1% | 水平排版下,基于容器宽度自适应尺寸、字体 |
cqh |
Container Query Height | 查询容器高度的 1% | 垂直排版、卡片高度驱动布局、竖向自适应组件 |
cqi |
Container Query Inline | 查询容器内联轴尺寸的 1%,随书写模式自动切换轴向;横排等价 cqw,竖排等价 cqh | 多语言横竖排版兼容组件,无需修改样式自动适配流向 |
cqb |
Container Query Block | 查询容器块轴尺寸的 1%,随书写模式自动切换轴向;横排等价 cqh,竖排等价 cqw | 文字块垂直高度自适应、图文流布局,兼容横竖书写 |
cqmin |
Container Query Min | 取 cqw 与 cqh 中更小值的 1% | 正方形卡片、头像、图标,保证元素不会拉伸变形 |
cqmax |
Container Query Max | 取 cqw 与 cqh 中更大值的 1% | 全屏填充容器、横幅大图,铺满容器最长边 |
噢啦,正文到此结束,最后,附上兼容性截图。

容器查询家族单位的兼容性与d家族单位相比,大差不差,Chrome早一些,Safari慢一些,见:

OK,碎碎念时间。
最近团队发生了一件不大不小的事情,那就是运转了十多年的前端双周分享和每周小测停止了。
还能为啥,没人参与了呗!
那为什么没人参与呢?
最直接的原因就是AI太强了,大家对这种低效又痛苦的学习方式已经没了兴趣。
这种心理很好理解,我哼次哼次辛辛苦苦学了这些技术点,回过头一看,很AI相比,连个脚趾头都比不上,道心直接破碎,还学个毛线!
总而言之,此事对我还颇有触动,想了不少东西。
有兴趣的可以访问这里,看看我的一些思考。

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12289
(本篇完)