MoreRSS

site iconZhangXinXu | 张鑫旭修改

出版《CSS选择器世界》,喜欢钓鱼、写作。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

ZhangXinXu | 张鑫旭的 RSS 预览

background-clip升级,支持边框或文字应用背景

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.

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都已经支持边框背景图了。

border-area的兼容性示意

二、和border-shape属性配合使用

上上个月介绍了全新的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+):

Nice表情包

四、结语说明

没啥好说的,是个相当实用的特性,要是10年前出现,肯定会有很多讨论,朋友圈也可以看到相关文章的转发。

现在么,就像是路边的石头,无人问津。

最后,炫一下周末的渔获吧。

周六老鬼垂钓农家乐,特价108元8小时,钓了32斤;周日天天渔场,188元6小时,钓了七八十斤,两个鱼护。

渔获展示

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

(本篇完)

来了来了,Flex/Grid布局的间隙装饰线它来了

2026-08-03 21:20:14

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12316
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、先讲下历史

间隙装饰线其实由来已久,在大多数前端从业人员还未入行的时候就已经支持了,大家如果查看 column-rule 属性的兼容性,会发现极佳。

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 

于是,在宽屏下,垂直装饰线效果生效:

水平布局下的gap装饰线

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

水平方向装饰线效果示意

比之前使用伪元素,或者插入分隔线元素实现简单多了。

除了基本的分隔线装饰效果,还顺便带来了众多其他强大的特性,例如支持多属性值、交点处如何中断、间距边缘距离控制、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道分隔线。

  1. 2px dashed显然对应第一条线;
  2. dotted则对应第二条,然后这里的宽度规则和border属性一致,默认都是medium尺寸,也就是3px,因此,这里实际渲染等同于 3px dotter
  3. 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 不断循环渲染的效果。

分隔线平铺repeat()函数示意

三、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-insetrow-rule-inset 属性用于控制装饰在间距内的扩展距离。

如果无需区分水平和垂直距离,或者水平和垂直距离一致,也可以直接使用 rule-inset 属性。

默认情况下,rule-inset 属性设置的缩进距离是对称的,如果开发者希望变化的距离是不对称的,那么可以使用以下两个CSS属性。

  • rule-inset-cap:适用于没有交叉间距的端点的距离设置;
  • rule-inset-junction:适用于与其他间距相交的端点的距离设置。

一例胜千言,还是上面的案例,我们分别设置rule-inset-caprule-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-capcolumn-rule-inset-junction 属性。

五、分隔线的显示范围控制

还是上面的案例,区别在于Grid布局的子元素只有4项,此时,间隙装饰线的效果就会让人感到困惑。

明明没有元素项,结果却凭空出现了分隔装饰线。

多余的分隔线效果示意

此时就轮到 column-rule-visibility-itemsrow-rule-visibility-items 属性出马了。

如果无需区分方向,则可以使用合写属性 rule-visibility-items,其语法如下:

  • normal(默认):取决于容器类型。根据我的测试,大多数场景下效果和 all 一致。
  • all:规则显示在每个间距中,即使是没有元素的间距也是如此。
  • around:只要存有一个或多个相邻项,间隙线就会显示。
  • between:间隙线仅会出现在两个相邻项之间。

下图示意了 aroundbetween 这两个属性值的区别。

between和around区别

相信还是很好理解的。

六、动画、兼容性与结语

间隙分隔线的宽度、颜色和交叉缩进都是可添加动画效果的,具体不展示,这个懂的都懂,设置transition过渡,自然可以看到效果。

下面说下兼容性,Gap装饰是相当新的一个特性,目前只有Chrome 149+ 浏览器才支持,如下截图所示:

row-rule兼容性

唉,东西虽好,但现在还没命享用啊,等过几年兼容性跟上了,前端圈子还不知道变成什么样子呢!

好了,就说这么多吧。

参考文章:

竹知了

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

(本篇完)

Screen.isExtended、getScreenDetails与多显示器开发

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.

screen.isExtended运行结果

我们也可以使用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都没有支持。

窗口管理API的兼容性

二、Management API与窗口开发

下面讲下如何更加智能地控制新窗口打开这一交互。

之前我们都是使用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()方法可以返回所有屏幕的具体信息,此时,我们就可以精准控制新窗口的尺寸和位置。

我们来看几个案例。

1. 指定在第一屏第二屏显示

您可以狠狠地点击这里: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();

2. 屏幕信息获取、打开指定窗口,全屏处理等

您可以狠狠地点击这里:屏幕切换显示、全屏、信息获取等综合演示demo

例如,下面截图示意的就是我当前两个显示器的基础信息显示:

显示器屏幕信息示意

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

demo页面交互按钮截图示意

相关源码大家可以右键页面查看,或者让AI帮你分析。

三、ScreenDetails属性、事件以及屏幕信息获取

getScreenDetails()方法会返回ScreenDetails对象。

ScreenDetails对象包含属性和事件各两个,如下示意。

属性

currentscreen

返回一个单独的ScreenDetailed对象,表示当前浏览器窗口所显示屏幕的详细信息。

screens

ScreenDetailed对象数组,包含所有屏幕信息(注意,镜像屏幕不在其中)。

方法

currentscreenchange

当窗口的当前屏幕以某种方式发生变化时触发,例如可用宽度或高度,或方向发生了变化。

screenschange

当屏幕与系统连接或断开时触发。

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

(本篇完)

纯CSS实现repeat(–n, anything)循环展示或相加功能

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, " 👽")的字符串拼接也生效了。

如下图所示:

repeat效果示意

眼见为实,您可以狠狠地点击这里:CSS自定义任意可重复可相加函数demo

所以,@function --repeat(--n, --x)这个自定义函数里面究竟是怎么设置的,居然可以有这样的效果呢?

二、解密–repate()函数代码

此自定义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 次”。下面拆解原理。

1. 目标

--repeat(--n, --x) 要返回:把 --x 这个 token(可以是字符串片段、也可以是某个属性值单元)平铺重复 --n,中间用空格连接。

比如 --repeat(5, a)a a a a a(5 个 a)。

难点在于:CSS 没有循环,无法写 for (i=0; i<n; i++)。于是作者用了二进制展开这个数学技巧。

2. 第一部分:把 N 拆成二进制位

--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 是 2552⁸ - 1)。

3. 第二部分:倍增构造 2ⁿ 个副本(快速幂)

--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″。

4. 第三部分:按二进制位选择性拼接

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() 查询):

  • 如果第 K 位是 1,就取出对应的积木块 --powK(含 2^K 个 x);
  • 否则输出空(else: 后面是空的)。

把 8 个条件的结果首尾相接,就得到总数为 Σ (bitK × 2^K) 个 x —— 这正好等于 --n

--n = 5(bit2=1, bit0=1)为例:

  • bit0=1 → 输出 --pow0 = 1 个 x
  • bit1=0 → 空
  • bit2=1 → 输出 --pow2 = 4 个 x
  • 其余 → 空
  • 拼接 = 4 + 1 = 5 个 x ✅

5. 整体原理一句话总结

把”重复 N 次”这个无法用循环表达的操作,转化为”N 的二进制分解”问题:先用倍增法预生成 1、2、4、…、128 份副本,再根据 N 的每一个二进制位决定是否把对应份数拼进结果,最终拼出的总份数恰好等于 N。

这本质上就是快速幂 / 二进制拆分思想在 CSS token 拼接上的应用,把原本需要 O(N) 次循环的重复,压缩成 O(log N) = 最多 8 个固定的条件判断。

几个值得注意的细节

  1. 为什么 --x 可以是”字符串或属性值”:CSS 自定义属性存的是未解析的 token 序列,--x 只是被当作一段 token 反复拼接,函数本身不关心它语义上是关键字、长度值还是字符串片段,所以既能拼 grid-template-columns1fr,也能拼平铺文本片段。最终由使用方的属性去解释这串 token 的含义。
  2. 分隔符是空格var(--pow0) var(--pow0) 之间天然有空格,if() 各段之间也有空格。所以它做的是”空格分隔的平铺”,非常适合 grid-template-columns: --repeat(10, 1fr) 这类场景(等效于 repeat(10, 1fr) 展开)。
  3. 上限 255:只展开到 bit7 / pow7。想支持更大,再往上加 bit8 / pow8(256)即可,按同样规律扩展。
  4. 依赖的新特性@functionif() + style() 查询、round() 数学函数——都属于较新的 CSS 规范(CSS Values 4 / CSS Functions),目前浏览器支持仍在铺开阶段,需注意兼容性。

四、结语说明

本文内容让我不由得想起4月份介绍的蜂巢布局效果,也就是金字塔布局:CSS六边形头像的实现与蜂巢布局

其中,也用到了CSS if()函数和大量的数学计算。

不过,这种案例炫技的成分大一些,还不太适合用在生产环境。

实际开发,多半还是老老实实字符平铺写死,而不会使用自定义的 --repeat 函数。

好了,就说这么多吧,算是增长眼界的一篇文章了。

参考文章:

😉😊😇
🥰😍😘

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

(本篇完)

CSS新单位dvh、lvmin、vi、cqb等初解

2026-07-17 17:01:24

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12289
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、CSS又出新单位了

一不留神,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说起

dvh这个单位应该是最为常用的,全称 Dynamic Viewport Height,即动态视区高度。

我们平时用的比较多的是vh这个单位,表示视区高度。

但是在iOS Safari浏览器下,页面浏览的时候,如果你滚动,会发现菜单啊,导航啊这些会被滚走,此时,视区的高度变化了。比方说下面录屏视频底部的地址栏,滚动的时候高度会变小。

如果还是使用传统的vh单位,布局就会有问题。

此时,就可以使用dvh这个CSS单位,1dvh就是1%的动态视区高度。

比方说,我们构建一个全屏覆盖的黑色半透明遮罩层,则可以使用下面的CSS代码:

.fullscreen {
  height: 100dvh;
  background: #0009;
}

至于对应的宽度单位dvw就没什么应用价值了,因为网页视窗的宽度一般是不存在所谓的动态变化的。

svh和lvh又是什么?

svh是Small Viewport Height的缩写,表示假定浏览器所有工具栏全部展开(可见区域最小)的高度。
lvh则是Large Viewport Height的缩写,是假定浏览器所有工具栏全部收起(可见区域最大)的高度。

个中区别参见下表:

单位 全称 行为模式 最佳适用场景

svh
Small Viewport Height 假定浏览器所有工具栏全部展开(可见区域最小)的高度。 固定的底部常驻菜单,确保绝对不会被遮挡。

lvh
Large Viewport Height 假定浏览器所有工具栏全部收起(可见区域最大)的高度。 大气的背景大图,追求极致的视觉延伸感。

dvh
Dynamic Viewport Height 随工具栏的显示或隐藏动态实时变化的高度。 全屏 App 界面、侧边栏菜单、全屏表单。

二、那些以i, b结尾的单位

本文开头出现的几十个单位中,有1/3的单位要么以i结尾,要么以b结尾。

这些单位都是CSS逻辑单位。

  • iinline的首字母,默认表示水平方向,例如单位vi在绝大多数场景下,其实和vw的表现一样。
  • bblock的首字母,默认表示垂直方向。

其实类似的CSS逻辑属性还挺多,比方说用得比较多的:

margin-inline: 20px;
padding-block: 1rem;

从实用角度来看,这些CSS逻辑单位大家了解下即可,因为平时很少会用到需要改变水平或垂直排版流的场景,没有这样的场景,自然也就没有使用的必要。

所以,类似与vi这样的单位,除了装逼以外,没有任何作用,还是老老实实使用vw吧。

三、cq家族单位

说起cq家族,不知道为何脑中突然出现葬爱家族,哈哈哈。

葬爱家族 精神小妹

cq是container query的简介,中文译为容器查询。

本博已经多次介绍过:

是非常实用的特性。

其中用的最多的单位就是cqw1cqw就是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% 全屏填充容器、横幅大图,铺满容器最长边

四、打完收工,打道回府

噢啦,正文到此结束,最后,附上兼容性截图。

dvh的兼容性

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

cqw的兼容性

OK,碎碎念时间。

最近团队发生了一件不大不小的事情,那就是运转了十多年的前端双周分享和每周小测停止了。

还能为啥,没人参与了呗!

那为什么没人参与呢?

最直接的原因就是AI太强了,大家对这种低效又痛苦的学习方式已经没了兴趣。

这种心理很好理解,我哼次哼次辛辛苦苦学了这些技术点,回过头一看,很AI相比,连个脚趾头都比不上,道心直接破碎,还学个毛线!

总而言之,此事对我还颇有触动,想了不少东西。

有兴趣的可以访问这里,看看我的一些思考。

配图1

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

(本篇完)