用 CSS random() 掷骰子

编程语言中的随机函数令人惊叹。它们能生成变化,让事物充满即兴与新鲜感。此前CSS始终无法生成随机数,而如今random()函数即将问世。你将能实现随机动画延迟、在屏幕任意位置布局内容、生成随机颜色,或完成任何创意——全程无需JavaScript。

基础用法

该函数采用三参数模式:random(min, max, step)。通过设定最小值和最大值定义随机数生成范围,三参数类型需保持一致(整数、百分比、长度、角度等均可)。step参数虽为可选项,但在需要确保结果为整数时尤为实用。

例如:random(0, 100, 2)将生成0至100之间的偶数;而random(0turn, 1turn)则生成旋转角度的分数值——即0至360度之间的任意小数。

让我们通过几个演示来了解random()的使用方法。首先用HTML和CSS创建星场。

基于CSS和HTML的星场

图0:用 CSS random() 掷骰子
<html>
<body>
    <div class="star"></div>
    <div class="star"></div>
    …
    <div class="star"></div>
  </body>
</html>

首先为每颗星创建HTML元素。接着绘制星空背景,并用任意尺寸的白色圆形作为星体样式。将星体设置为固定定位可轻松实现随机分布。

body {
  background-color: black;
}

.star {
  background-color: white;
  border-radius: 50%;
  aspect-ratio: 1/1;
  width: 3px;
  position: fixed;
}    

通过设置顶部和左侧属性为随机值来分布星星,具体而言:top(Y轴)取0-100%随机值,left(X轴)取独立随机值。默认情况下,每个random()函数会基于均匀分布生成的不同基准值,产生各异的随机数值。

.star {
  background-color: white;
  border-radius: 50%;
  aspect-ratio: 1/1; 
  width: 3px;
  position: fixed;
  top: random(0%, 100%);
  left: random(0%, 100%);
}
图1:用 CSS random() 掷骰子

为增强动态效果,同时使“星星”尺寸随机化。

元素周期表
.star {
   background-color: white;
   border-radius: 50%;
   aspect-ratio: 1/1; 
   width: random(2px, 10px, 1px);
   position: fixed;
   top: random(0%, 100%);
   left: random(0%, 100%);
 }

注意顶部和左侧随机值使用百分比。但width参数可选择基于像素的随机尺寸。需注意所有参数必须采用统一单位。上例中的第三个参数用于确保星体尺寸以1px为增量,使尺寸分布更均匀,从而呈现更多样化的效果。

图2:用 CSS random() 掷骰子

可通过添加特殊效果进一步丰富表现力,例如运用分层阴影和混合效果为星体增添微妙的光晕。

.star {
    --star-size: random(--random-star-size, 1px, 7px, 1px);
    background-color: white;
    border-radius: 50%;
    aspect-ratio: 1/1;
    width: var(--star-size);
    position: fixed;
    top: random(0%, 100%);
    left: random(0%, 100%);
    filter: drop-shadow(0px 0px calc(var(--star-size) * 0.5) oklch(0.7 0.2 random(0, 100))) 
            drop-shadow(0px 0px calc(var(--star-size) * 2) white);
    mix-blend-mode: hard-light;
}

共享随机性与自定义属性

--star-size自定义属性可复用随机生成的像素尺寸,但需注意random()函数与自定义属性的配合细节。首先,你会发现random()函数的首个参数看似另一个自定义属性——此处的--random-star-size名称实为标识符(ident)。该标识确保在其他 random() 函数调用中,只要提供相同标识,生成的随机值就会被复用。

你可能会问:为何需要这样做?关键在于理解:将 CSS 函数设置为自定义属性,不同于其他编程语言中变量存储结果的机制。自定义属性更类似于简单的文本替换机制。这意味着当你通过var()调用该自定义属性时,实际发生的过程更接近文本替换——将var()替换为声明属性的副本。在本例中,这意味着再次调用random()函数,而非使用函数的最终结果值。

回到示例,理想的光晕模糊效果需基于星体最终的随机尺寸。可通过calc()函数将星体尺寸作为模糊大小的计算因子,实现分层阴影的动态模糊效果。此方案需确保随机数值一致性,因此使用命名标识符是正确做法。

首个阴影同样采用random()选择色调,配合oklch()生成鲜艳色彩,并以“硬光”混合模式叠加在柔白光晕之上。这种组合为星星增添了微妙而动态的色彩效果。

图3:用 CSS random() 掷骰子

命名标识符可在单个元素的多个属性间共享随机值,这是众多随机共享方式之一。random()函数提供多种实现方案以满足不同需求。您也可使用element-shared值,将特定属性的随机值共享至所有匹配元素,或混合两种方式实现全局共享。与命名标识符(每次使用时共享值)不同,element-shared会将随机值共享至所有应用该属性的元素。

在星场示例中,可通过添加带简单图形的四角星来实现此效果。

.star.fourpointed {
    clip-path: shape(from 50% 0%,line to 50.27% 3.25%, …);
    --star-size: random(--random-four-point-size, 20px, 60px, 1px);
    rotate: random(element-shared, -45deg, 45deg);
}
图4:用 CSS random() 掷骰子

使用CSS shape() 函数定义四角图形,以此裁剪圆形白色背景。星体尺寸仍可随机生成,但需放大一个数量级才能达到视觉平衡。更逼真的星体效果需模拟星体图像中的衍射光线——这些光线因重要物理特性而保持相同角度。虽然可设定固定旋转角度,但随机分配角度更能增添趣味与动态感。但要实现这一效果,需找到让所有四角星共享相同随机角度的方法。

此时element-shared便派上用场。该值会为所有元素生成共享的随机属性值,从而使所有四角星以相同随机角度旋转。

完整星场示例可在此处体验:

随机矩形布局

random() 函数还有诸多其他应用场景。基于星场示例的部分概念,你可以尝试将random()grid等布局工具结合使用。

图5:用 CSS random() 掷骰子

在此变体示例中,网页区域被均分为100行100列的网格。随后随机着色的矩形被置于网格中的任意位置:

.grid {
  display: grid;
  --rows: 100;
  --columns: 100;
  grid-template-rows: repeat(var(--rows), 1fr);
  grid-template-columns: repeat(var(--columns), 1fr);
  width: 100vw;
  height: 100vh;
}

.rectangle {
  background-color: lch(100% 90% random(0deg, 360deg));
  grid-area: random(1, var(--rows), 1) / random(1, var(--columns), 1);
}

支持random()的浏览器均可查看最终效果:

照片堆叠

random()的另一应用是生成随机堆叠的照片组,通过随机排列和方向调整,营造出照片被随意抛掷堆叠的效果。您既可耗费大量时间手动调整每张照片,也可让计算机在每次页面加载时程序化生成。

图6:用 CSS random() 掷骰子
.stack img {
    width: 100%;
    grid-column: 1;
    grid-row: 1;
    border: 10px solid hsl(0, 100%, 100%);
    box-shadow: 10px 10px 40px hsl(0, 0%, 0%, 20%);

    --random-rotate: rotate(random(-1 * var(--rotate-offset), var(--rotate-offset)));

    transition: .3s ease-out;
    transform: var(--random-rotate);
    transform-origin: random(0%, 100%) random(0%, 100%);
}

更进一步,随机性也能轻松融入交互设计。为图片悬停状态添加随机平移效果,更能增添趣味性:

.stack:hover img {
    transform: var(--random-rotate) translateX(random(-1 * var(--translate-offset), var(--translate-offset))) translateY(random(-1 * var(--translate-offset), var(--translate-offset)));
}

幸运转盘

图7:用 CSS random() 掷骰子

random()函数甚至能用于创建需要不可预测结果的交互元素。幸运转盘演示完美展现了这一特性。

@keyframes spin {
    from {
        rotate: 0deg;
    }
    to {
        rotate: 10turn; /* Fallback for browsers that don't support `random()` */
        rotate: random(2turn, 10turn, by 20deg);
    }
}

点击“旋转”按钮时,@keyframe动画通过random()生成旋转值,从而决定转盘停驻的时机与位置。该示例凸显了现代CSS日益强大的能力——所有交互性、随机性及动画效果均可在样式表中定义。

演示效果请见:

实用随机性参考指南

根据需求不同,random() 函数存在多种应用方式,元素间共享随机性的方法也各异。

最大随机性

两个属性均获取不同值,且每个元素的值也各不相同,从而生成大量随机矩形。

.random-rect {
  width: random(100px, 200px);
  height: random(100px, 200px);
}

元素内部按名称共享

使用标识符时,两个属性取相同值,但每个元素仍保持差异,从而生成大量随机正方形。

.random-square {
    width: random(--foo, 100px, 200px);
    height: random(--foo, 100px, 200px);
}

元素间共享属性

使用 element-shared 时,两个属性取不同值,但所有元素共享该值,从而生成大量尺寸相同的随机矩形。

.shared-random-rect {
    width: random(element-shared, 100px, 200px);
    height: random(element-shared, 100px, 200px);
}

全局按名称共享

同时使用命名标识符和element-shared时,两个属性将获得相同值,且所有元素共享该随机值,因此会生成大量尺寸相同的随机大小正方形。

.shared-random-squares {
    width: random(--foo element-shared, 100px, 200px);
    height: random(--foo element-shared, 100px, 200px);
}

立即尝试并反馈体验

您可立即在Safari技术预览版中体验random()函数。但需注意:CSS工作组仍在就规范展开讨论,关于此方案是否最契合开发者需求仍存在若干待决议题。

虽然上述示例展现了令人兴奋的可能性,但我们正积极征集网络开发社区的反馈以完善最终方案,您的参与至关重要。若您在 Safari Technology Preview 中测试了 random(),我们期待聆听您的体验:哪些功能运行良好?哪些操作令人不适?共享值的表达方式是否合理?是否有未涵盖的使用场景?或者对element-shared的命名有更佳建议?您的反馈将直接影响该功能从当前形态的演进方向。这是您参与塑造CSS的契机——请尝试使用并分享您的见解。

您可以通过Bluesky / Mastodon,或联系我们的其他布道师——珍·西蒙斯,通过Bluesky / Mastodon,或萨隆·伊特巴雷克,BlueSky。您还可通过领英关注WebKit。若发现漏洞或问题,请提交WebKit漏洞报告

共有 31 条评论

  1. 这似乎是个好机会,可以重新提起那个古老的、更粗糙但性能更高且更可预测的CSS随机效果——通过使用质数大小的背景来实现“随机”分布。所谓的“蝉原理”

    https://www.sitepoint.com/the-cicada-principle-and-why-it-ma

    https://lea.verou.me/blog/2020/07/the-cicada-principle-revis

    在此情况下,需使用多种透明星形图案的瓷砖(图片或渐变/剪切路径技巧),每种图案的尺寸对应不同的质数。不过只要能在CSS中实现平铺叠加的效果,任何方案都可行。

    • 虽然不确定是否会用到这个技巧,但内容确实很有启发性。

    • (哦,需要说明的是第二个链接使用nth选择器实现伪随机规则应用,不仅限于贴图)…另外,如果页面具有唯一属性选择器,或许可以调整质数和偏移量来“初始化”伪随机nth选择器。比如在Drupal中,可根据body标签内nid的数字位数生成不同选择器。

  2. 星场示例很酷,但这恰恰可能是random()无法达到预期效果的场景:当用于生成图形时,真正的随机性往往效果不佳——它会形成密集区与空白区,许多观察者认为这种分布反而不如间距更均匀的伪随机序列显得随机。

    此现象称为“低偏差序列”,多年来HN上已有若干相关讨论。我明白在API尚未真正存在时就讨论细节可能有些吹毛求疵,但对于图像呈现而言,我认为许多应用程序可能更需要这种特性。

  3. 相关:纯CSS实现的动态星场

    https://codepen.io/ArneSava/pen/BaWxOaR

  4. 不错。目前我得用JS设置CSS自定义属性才能实现相同效果。

    看到CSS比JS更早获得可用随机函数真棒。

    • 也许“可用”是你的评判标准,但Math.random()有什么问题?

      • 要生成特定范围内的随机数,总得做些我老是忘记还得谷歌搜索的操作。

            Math.floor(Math.random() * (max - min + 1)) + min;
        

        (谷歌AI摘要说这就是方法)

        CSS函数应为random(min, max)

        此外CSS函数似乎需要多步计算,我目前还不清楚如何用Math.random()实现

      • JS还有Crypto.getRandomValues()

    • 这样就能给隐藏的DOM元素添加随机数据属性,再从JS查询。这样JS的随机函数就更简洁啦。;)

  5. 不错但…没戏!

  6. 或许能在页面加载时将种子设为固定值?我倒挺喜欢刷新页面后星云效果保持不变的设计。或者说,刷新后效果改变反而会让我困扰——毕竟刷新本该只是重新呈现原网页。

    • 谁说的?我刷新页面就是为了看相同内容啊?通常刷新就是为了看不同内容。

  7. 我渴望看到无需任何JavaScript就能实现React式页面的那一天。WebKit继续努力吧,我相信你。

  8. 这些示例感觉有些牵强,还有其他场景需要随机CSS值吗?开发商业应用时我很少用到随机性。

    • 真想看看运用这项新技术能创造出多么突破性的MySpace主题。

  9. 规范文档在哪?MDN上找不到相关条目。

    能否实现结果可复现?同浏览器或跨浏览器环境下?即使非默认模式也行。

  10. 鉴于过去见过太多“这个随机函数本不该用于安全,但人们偏要拿它做安全”的漏洞:

    求求你们求求你们求求你们,能不能从一开始就默认启用安全机制?

    • 若在生产环境应用中用CSS实现安全协议,那被黑客攻击后因疏忽被起诉也是咎由自取。

    • 在样式表里搞安全?得了吧,你得给预期用途设个边界。

      • “瞧,我纯CSS实现了骰子密码法!”这种场景恐怕不难想象。

        我敢打赌此刻就有人在搞这个。

        • 这点我并不反对。

          在样式表标准中引入密码学,只会徒增不必要的复杂性。归根结底,当企业兜售不安全的加密方案时,浏览器厂商无需为此负责。

          给CSS添加加密功能,无异于让CSS引擎沦为比特币挖矿工具。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


京ICP备12002735号