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

这篇文章发布于 2026年07月23日,星期四,15:39,归类于 CSS相关。 阅读 50 次, 今日 47 次 没有评论

 

一、神奇效果抢先看

去年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 函数。

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

参考文章:

😉😊😇
🥰😍😘

(本篇完)

分享到:


发表评论(目前没有评论)