IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

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

张鑫旭-鑫空间-鑫生活 2026-07-23 16:40:36 累计浏览 10 次
本机暂存

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/doubledash.css@0.0.2/functions/repeat.css");

案例

1. 字符串循环拼接

@import url("./repeat.css");

body::before {
  content: --repeat(7, " ");
}

实时渲染效果如下:

2. text-shadow文字阴影

@import url("https://unpkg.com/@propjockey/doubledash.css@0.0.2/functions/repeat.css");
@import url("https://unpkg.com/@propjockey/doubledash.css@0.0.2/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

(本篇完)

同分类推荐文章

  1. Weaponizing And Defending The React Flight Protocol: Deserialization Sinks In RSCs (2026-07-21 18:00:00)
  2. 把 Electron 的 Sparkle 更新器抽成库:bug 都住在边界上 (2026-07-18 21:59:08)
  3. CSS新单位dvh、lvmin、vi、cqb等初解 (2026-07-17 17:01:24)

查看更多 前端 文章 →

建议继续学习

  1. 50个活力和动感的网页设计-颜色的灵感 (累计阅读 34,490)
  2. 视觉设计前瞻实用性研究(PNVD) 第二期 (累计阅读 13,033)
  3. 各公司对前端开发的职位描述 (累计阅读 10,459)
  4. iframe大小自适应 (累计阅读 10,131)
  5. 解决IE6从Nginx服务器下载图片不Cache的Bug (累计阅读 8,449)
  6. 浏览器的渲染原理简介 (累计阅读 8,436)
  7. 程序员眼里IE浏览器是什么样的 (累计阅读 8,050)
  8. 2010网页设计趋势 (累计阅读 7,854)
  9. Web前端工程师编程能力飞升之路 (累计阅读 7,749)
  10. 颜色的代码表达式 (累计阅读 7,712)