今天在 CSS 中使用绝对值、符号、四舍五入和模数
作者:Ana Tudor
翻译:墨言
原文链接:点击这里
很长一段时间以来,CSS 规范已经包含了许多非常有用的数学函数,例如三角函数(sin()
, cos()
, tan()
, asin()
, acos()
, atan()
, atan2()
)、指数函数(pow()
, exp()
, sqrt()
, log()
, hypot()
)、符号相关函数(abs()
, sign()
)和阶梯函数值函数 ( round()
, mod()
, rem()
)。
但是,这些还没有在任何浏览器中实现,因此本文将展示如何使用我们已经拥有的 CSS 功能,计算abs()
、sign()
、round()
和mod()
应该返回的值。然后我们就来看看今天我们能做些什么。
请注意,在互联网上漫游的年代,这些技术都不适用于浏览器。其中一些甚至依赖于支持注册自定义属性的浏览器(使用@property),这意味着它们目前仅限于 Chromium。
计算的等价物
–abs
我们可以通过使用新的 CSS max()
功能来实现这一点,该功能已在所有主要浏览器的当前版本中实现。
假设我们有一个自定义属性--a
。我们不知道这是正的还是负的,我们想要得到它的绝对值。我们通过在该值与其相加倒数之间选取最大值来实现这一点:
--abs: max(var(--a), -1*var(--a));
如果--a
是正数,这意味着它大于零,乘以它-1 会得到一个负数,它总是小于零。反过来,它总是小于正数--a
,因此返回的结果max()
等于var(--a)
。
如果--a
是负数,这意味着它小于零,乘以它-1 给我们一个正数,它总是大于零,反过来,它总是大于负数--a
。因此,由 返回的结果max()
等于-1*var(--a)
。
–sign
这是我们可以使用上一节得到的,因为数字的符号是该数字除以其绝对值:
--abs: max(var(--a), -1*var(--a)); --sign: calc(var(--a)/var(--abs));
这里需要注意的一件非常重要的事情是,这只在--a
是无单位的情况下起作用,因为我们不能除以calc()
内有单位的数字。
此外,如果--a
为 0,则此解决方案仅在我们将--sign
(目前仅在 Chromium 浏览器中支持)注册为初始值 0 时有效:
@property --sign { syntax: '<integer>'; initial-value: 0; inherits: false /* true 取决于上下文 */ }
这是因为--a
为 0,也会使--abs
计算为 0,而除以 0 在 CSS calc()
中是无效的,所以我们需要确保在这种情况下--sign
被重置为 0。请记住,如果我们在将其设置为calc()
值之前在 CSS 中将其设置为 0,并且不注册它,则不会发生这种情况:
--abs: max(var(--a), -1*var(--a)); --sign: 0; /* doesn't help */ --sign: calc(var(--a)/var(--abs));
在实践中,我还经常对整数使用以下版本:
--sign: clamp(-1, var(--a), 1);
这里,我们使用一个climp()
函数。这需要三个参数:最小允许值-1、首选值var(--a)
和最大允许值 1。返回的值是首选值,只要它介于上下限和超出的限制之间。
如果--a
是一个负整数,这意味着它小于或等于-1,即clamp()
函数的下限(或允许的最小值),因此返回的值是-1。如果它是一个正整数,这意味着它大于或等于 1,即clamp()
函数的上限(或允许的最大值),所以返回的值是 1。最后,如果--a
是 0,它在下限和上限之间,那么函数返回它的值(在本例中是 0)。
这种方法的优点是更简单,不需要 Houdini 支持。也就是说,请注意,它只适用于无单位值(将长度或角度值与±1 之类的整数进行比较就像比较苹果和桔子一样——它不起作用!)绝对值为 0 或至少等于 1。对于子单元值,如-.05
,我们上面的方法失败,因为返回的值是-.05
,而不是-1
!
我的第一个想法是,我们可以通过引入一个小于我们所知道的最小非零值的极限值,将这项技术扩展到亚单位值--a
可以取。例如,假设我们的限制是.000001
,这将允许我们正确地将-1
作为-.05
的符号,将 1 作为.0001
的符号!
--lim: .000001; --sign: clamp(-1*var(--lim), var(--a), var(--lim));
Temani Afif 提出了一个更简单的版本,将--a
乘以一个非常大的数字,以产生一个超单位值。
--sign: clamp(-1, var(--a)*10000, 1);
我最终决定除以--a
极限值,因为查看它不会低于哪个最小非零值感觉更直观。
--lim: .000001; --sign: clamp(-1, var(--a)/var(--lim), 1);
–round(以及–ceil 和–floor)
就像符号的情况一样,这仅适用于无单位值,并且需要将–round 变量注册为<integer>以便我们强制对我们设置的任何值进行舍入:
@property --round { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } .my-elem { --round: var(--a); }
通过扩展,如果我们减去或加上.5,我们就可以得到--floor
和--ceil
:
@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } @property --ceil { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } .my-elem { --floor: calc(var(--a) - .5); --ceil: calc(var(--a) + .5) }
–mod
它建立在--floor
技术的基础上,以获得整数商,然后允许我们获得模值。这意味着我们的值必须是统一的。
@property --floor { syntax: '<integer>'; initial-value: 0; inherits: false /* or true depending on context */ } .my-elem { --floor: calc(var(--a)/var(--b) - .5); --mod: calc(var(--a) - var(--b)*var(--floor)) }
用例
我们可以用这种技术做什么?让我们仔细看看三个用例。
交错动画中的轻松对称(不仅如此!)
虽然绝对值可以帮助我们获得许多属性的对称结果,但动画延迟和过渡延迟是我使用最多的,所以让我们来看一些例子!
我们将–n 个项目放在一个容器中,每个项目都有一个索引–i。–n 和–i 都是通过样式属性传递给 CSS 的变量。
- let n = 16; .wrap(style=`--n: ${n}`) - for(let i = 0; i < n; i++) .item(style=`--i: ${i}`)
这为我们提供了以下编译后的 HTML:
<div class='wrap' style='--n: 16'> <div class='item' style='--i: 0'></div> <div class='item' style='--i: 1'></div> <!-- more such items --> </div>
我们设置了一些样式,以便项目以行方式排列,且为正方形,边缘长度为非零:
$r: 2.5vw; .wrap { display: flex; justify-content: space-evenly; } .item { padding: $r; }
现在,我们添加两组关键帧来设置缩放变换和长方体阴影的动画。第一组关键帧 grow 使我们的项目从 0%的零扩展到 50%的全尺寸,之后它们将保持全尺寸直到结束。第二组关键帧 melt 向我们显示了具有插入框阴影的项目,这些阴影完全覆盖到动画中的中间点(50%)。这也是物品从无到有发展到完全尺寸的时候。然后,这些嵌入阴影的扩散半径会缩小,直到在 100%时变为零。
$r: 2.5vw; .item { padding: $r; animation: a $t infinite; animation-name: grow, melt; } @keyframes grow { 0% { transform: scale(0); } 50%, 100% { transform: none; } } @keyframes melt { 0%, 50% { box-shadow: inset 0 0 0 $r; } 100% { box-shadow: inset 0 0; } }
基本动画(现场演示)
现在是有趣的部分!我们计算第一项索引和最后一项索引之间的中间值。这是两个指标的算术平均值(因为我们的指数是基于零的,第一个和最后一个分别是 0 和 n-1):
--m: calc(.5*(var(--n) - 1));
我们得到中间--m
和项目索引--i
之间差值的绝对值--abs
,然后用它来计算动画延迟:
--abs: max(var(--m) - var(--i), var(--i) - var(--m)); animation: a $t calc(var(--abs)/var(--m)*#{$t}) infinite backwards; animation-name: grow, melt;
中间--m
和项目索引--i
之间差值的绝对值--abs
可以小到 0(对于中间项目,如果--n
为奇数),而大到--m
(对于最终项目)。这意味着将其除以--m
总是会在[0,1]间隔内给我们一个值,然后我们将该值乘以动画持续时间$t
,以确保每个项目都有一个介于 0S 和animation-duration
之间的延迟。
请注意,我们还设置animation-fill-mode
为backwards
. 由于大多数项目将在稍后开始动画,这告诉浏览器在 0%关键帧中保持它们的样式直到那时。
在这种特殊情况下,如果没有它,我们也不会看到任何差异,因为虽然项目将是全尺寸(不像在 0%增长动画的关键帧中那样缩放),但在box-shadow
开始动画之前它们也将没有。然而,在许多其他情况下,它确实有所作为,我们不应该忘记它。
另一种可能性(不涉及设置animation-fill-mode
)animation-delay
是 0 通过减去一个完整的来确保总是小于或至多等于animation-duration
。
--abs: max(var(--m) - var(--i), var(--i) - var(--m)); animation: a $t calc((var(--abs)/var(--m) - 1)*#{$t}) infinite; animation-name: grow, melt;
这两个选项都是有效的,您使用哪一个取决于您希望在一开始时发生什么。我通常倾向于采用负延迟,因为它们在录制循环动画以制作如下图所示的 gif 时更有意义,这说明了animation-delay
值如何相对于中间对称。
为了直观比较这两个选项,您可以重新运行以下演示以查看最开始发生的情况。
在线查看例子:点击这里
一个更高级的例子如下:
在这里,--n
导航链接和相应的配方文章中的每一个都有一个索引--idx
。每当导航链接悬停或聚焦时,都会读取其--idx
值并将其设置为主体上的当前索引--k
。如果这些项目都没有悬停或聚焦,-k
设置为[0,n)间隔之外的值(例如,-1)。
所选项目的绝对值和当前悬停的项目的绝对值之间的差值。如果该绝对值为 0,则我们的项目是当前选定的项目(即--not sel
为 0,--sel
为 1)。如果此绝对值大于 0,则我们的项目不是当前选择的项目(即–not sel 为 1,–sel 为 0)。
如果--idx
和--k
都是整数,则它们的差也是整数。这意味着此差值的绝对值--abs
为 0(当选择项目时),或大于或等于 1(当未选择项目时)。
当我们把所有这些都写进代码时,我们得到的是:
--abs: Max(var(--k) - var(--idx), var(--idx) - var(--k)); --not-sel: Min(1, var(--abs)); --sel: calc(1 - var(--not-sel));
--sel
和--not sel
属性(总是整数,加起来总是 1)确定导航链接的大小(宽屏幕场景中的宽度和窄屏幕场景中的高度),它们是否为灰色,以及它们的文本内容是否隐藏。这是我们在这里将不讨论的内容,因为它超出了本文的范围。
与此相关的是,当单击导航链接时,它会滑出视线(在宽屏幕情况下向上,在窄屏幕情况下向左),然后是它周围的所有其他链接,每个链接都有一个过渡延迟,这取决于它们与单击链接的距离(即绝对值,--abs
,它们的索引--idx
和当前所选项目的索引--k
)之间的差异,显示相应的配方文章。这些转换延迟值相对于当前选定的项目是对称的。
transition: transform 1s calc(var(--abs)*.05s);
实际的转换和延迟实际上有点复杂,因为不仅仅是转换,还有更多的属性被设置为动画,特别是对于转换,当从配方文章返回到导航链接时,会有额外的延迟,因为我们等待<article>元素消失,然后再让链接滑下。但我们感兴趣的是,使链接更接近所选链接的延迟部分开始在那些更远的链接之前滑出视线。如上所述,使用–abs 变量进行计算。
可以玩下面的交互式演示:点击这里
在 2D 中,事情变得更加有趣,所以现在让我们将我们的行设为网格!
我们从稍微改变结构开始,这样我们就有 8 列 8 行(这意味着我们在网格上总共有 8·8=64 项)。
- let n = 8; - let m = n*n; style - for(let i = 0; i < n; i++) | .item:nth-child(#{n}n + #{i + 1}) { --i: #{i} } | .item:nth-child(n + #{n*i + 1}) { --j: #{i} } .wrap(style=`--n: ${n}`) - for(let i = 0; i < m; i++) .item
上面的 Pug 代码编译为以下 HTML:
<style> .item:nth-child(8n + 1) { --i: 0 } /* items on 1st column */ .item:nth-child(n + 1) { --j: 0 } /* items starting from 1st row */ .item:nth-child(8n + 2) { --i: 1 } /* items on 2nd column */ .item:nth-child(n + 9) { --j: 1 } /* items starting from 2nd row */ /* 6 more such pairs */ </style> <div class='wrap' style='--n: 8'> <div class='item'></div> <div class='item'></div> <!-- 62 more such items --> </div>
就像以前的情况下,我们计算一个中间指标,--m
但因为我们已经从一维转移到二维,我们现在在绝对值计算两个差异,每一个两个维度(一个用于列,--abs-i
和一个用于行,--abs-j
)。
--m: calc(.5*(var(--n) - 1)); --abs-i: max(var(--m) - var(--i), var(--i) - var(--m)); --abs-j: max(var(--m) - var(--j), var(--j) - var(--m));
我们使用完全相同的两组@keyframe
,但是animation-delay
会发生一些变化,因此它取决于--abs-i
和--abs-j
。这些绝对值可以小到 0(对于列和行中间的平铺),大到--m
(对于列和行末端的平铺),这意味着它们和--m
之间的比率始终在[0,1]间隔内。这两个比率之和在这个区间内总是 0。如果我们想把它减少到[0,1]区间,我们需要将它除以 2(或者乘以.5,同样的事情)。
animation-delay: calc(.5*(var(--abs-i)/var(--m) + var(--abs-j)/var(--m))*#{$t});
这给我们提供了[0s,$t]
间隔内的延迟。我们可以将分母var(--m)
从括号中去掉,以简化上述公式:
animation-delay: calc(.5*(var(--abs-i) + var(--abs-j))/var(--m)*#{$t});
与前一种情况一样,这使得网格项从网格的中间越远,就越晚开始设置动画。我们应该使用 animation-fill-mode: backwards,以确保它们保持在 0%关键帧指定的状态,直到延迟时间过去并开始制作动画。
或者,我们可以从所有延迟中减去一个动画持续时间$t,以确保页面加载时所有网格项都已开始其动画。
animation-delay: calc((.5*(var(--abs-i) + var(--abs-j))/var(--m) - 1)*#{$t});
效果如下:
实例代码:点击这里
现在让我们看几个更有趣的例子。我们将不详细介绍它们背后的“如何”,因为对称值技术的工作原理与前面的技术完全相同,其余内容不在本文讨论范围之内。然而,在下面每个例子的标题中都有一个指向 CodePen 演示的链接。
在第一个示例中,每个网格项都由两个三角形组成,这些三角形在它们相交的对角线的两端收缩为零,然后又恢复为完整大小。由于这是一个交替的动画,我们让延迟延伸到两个迭代(正常的和反向的),这意味着我们不再将比率之和除以一半,我们减去 2 以确保每个项目都有负延迟。
animation: s $t ease-in-out infinite alternate; animation-delay: calc(((var(--abs-i) + var(--abs-j))/var(--m) - 2)*#{$t});
实例代码:点击这里
在第二个示例中,每个网格项都有一个渐变角度,动画从 0deg 到 1turn。
实例代码:点击这里
第三个示例非常类似,除了动画角度由 conic-gradient(而不是线性渐变)以及第一个停止的色调使用之外。
实例代码:点击这里
在第四个示例中,每个网格单元包含七个上下振荡的彩虹点。振荡延迟有一个组件,它以与之前的网格完全相同的方式依赖于单元索引(这里唯一不同的是列数与行数不同,因此我们需要计算两个中间索引,一个沿着两个维度中的每一个)和一个依赖于点索引的分量–idx,相对于每个单元格的点数,–n-dots。
--k: calc(var(--idx)/var(--n-dots)); --mi: calc(.5*(var(--n-cols) - 1)); --abs-i: max(var(--mi) - var(--i), var(--i) - var(--mi)); --mj: calc(.5*(var(--n-rows) - 1)); --abs-j: max(var(--mj) - var(--j), var(--j) - var(--mj)); animation-delay: calc((var(--abs-i)/var(--mi) + var(--abs-j)/var(--mj) + var(--k) - 3)*#{$t});
实例代码:点击这里
在第五个示例中,构成立方体面的瓷砖收缩并向内移动。该animation-delay
用于顶面,准确计算作为我们的第一个 2D 的演示。
在第六个示例中,我们有一个上下摆动的列网格。
实例代码:点击这里
这 animation-delay 不是我们可以设置为具有对称值的唯一属性。我们也可以用物品的尺寸来做到这一点。在下面的第七个示例中,图块分布在从垂直 ( y ) 轴开始的六个环周围,并使用取决于它们离环顶点多远的因子进行缩放。这基本上是轴在圆上弯曲的一维情况。
实例代码:点击这里
第八个示例显示十个小玩意臂环绕一个大球体。这些小玩意的大小取决于它们离两极的距离,最近的最小。这是通过计算–m 手臂上的点的中间指数 ,以及–abs 它与当前摆设指数–j 之间差异的绝对值 ,然后使用此绝对值与中间指数之间的比率来获得尺寸来完成的因子, –f,然后我们在设置 padding.
--m: calc(.5*(var(--n-dots) - 1)); --abs: max(var(--m) - var(--j), var(--j) - var(--m)); --f: calc(1.05 - var(--abs)/var(--m)); padding: calc(var(--f)*#{$r});
实例代码:点击这里
特定(选定或中间)项目前后的项目的不同样式
假设我们有一堆单选按钮和标签,标签有一个索引集作为自定义属性,–i。我们希望所选项目之前的标签具有绿色背景,所选项目的标签具有蓝色背景,其余标签为灰色。在主体上,我们将当前所选选项的索引设置为另一个自定义属性,–k。
- let n = 8; - let k = Math.round((n - 1)*Math.random()); body(style=`--k: ${k}`) - for(let i = 0; i < n; i++) - let id = `r${i}`; input(type='radio' name='r' id=id checked=i===k) label(for=id style=`--i: ${i}`) Option ##{i}
这将编译为以下 HTML:
<body style='--k: 1'> <input type='radio' name='r' id='r0'/> <label for='r0' style='--i: 0'>Option #0</label> <input type='radio' name='r' id='r1' checked='checked'/> <label for='r1' style='--i: 1'>Option #1</label> <input type='radio' name='r' id='r2'/> <label for='r2' style='--i: 2'>Option #2</label> <!-- more options --> </body>
我们设置了一些布局和美化样式,包括 background 在标签上创建三个垂直条纹的渐变,每个条纹占据三分之一 background-size(目前,这只是默认的 100%,完整元素宽度):
$c: #6daa7e, #335f7c, #6a6d6b; body { display: grid; grid-gap: .25em 0; grid-template-columns: repeat(2, max-content); align-items: center; font: 1.25em/ 1.5 ubuntu, trebuchet ms, sans-serif; } label { padding: 0 .25em; background: linear-gradient(90deg, nth($c, 1) 33.333%, nth($c, 2) 0 66.667%, nth($c, 3) 0); color: #fff; cursor: pointer; }
在 JavaScript 中,–k 每当我们选择不同的选项时,我们都会更新 的值:
addEventListener('change', e => { let _t = e.target; document.body.style.setProperty('--k', +_t.id.replace('r', '')) })
现在是有趣的部分!对于我们的 label 元素,我们计算--sgn
标签索引--i
与当前所选选项的索引之间差异的符号--k
。然后我们使用这个--sgn
值来计算background-position
何时background-size
设置为 300%- 即标签的三倍,width 因为我们可能有三种可能的背景:一个用于标签用于所选背景之前的选项的情况,第二个用于标签用于所选选项的情况,第三种情况是标签用于所选选项之后的选项。
--sgn: clamp(-1, var(--i) - var(--k), 1); background: linear-gradient(90deg, nth($c, 1) 33.333%, nth($c, 2) 0 66.667%, nth($c, 3) 0) calc(50%*(1 + var(--sgn)))/ 300%
如果--i
小于--k
(label 对于所选选项之前的选项的情况),则--sgn
是-1 并且background-position
计算为50%*(1 + -1) = 50%*0 = 0%
,这意味着我们只能看到第一个垂直条纹(绿色)。
如果--i
等于--k
(label 对于所选选项的情况),则--sgn
是 0 并且background-position
计算为50%*(1 + 0) = 50%*1 = 50%
,因此我们只能看到中间的垂直条纹(蓝色)。
如果--i
大于--k
(label 对于所选选项之后的选项的情况),则--sgn
是 1 并且background-position
计算为50%*(1 + 1) = 50%*2 = 100%
,这意味着我们只能看到最后一个垂直条纹(灰色)。
实例代码:点击这里
一个更美观的示例是以下导航,其中垂直条位于最靠近所选选项的一侧,对于所选选项,它分布在整个元素上。
这使用了与之前的演示类似的结构,带有用于导航项的无线电输入和标签。移动的“背景”实际上是一个::after
伪元素,其平移值取决于符号–sgn。文本是一个::before
伪元素,它的位置应该在白色区域的中间,所以它的翻译值也取决于–sgn。
/* relevant styles */ label { --sgn: clamp(-1, var(--k) - var(--i), 1); &::before { transform: translate(calc(var(--sgn)*-.5*#{$pad})) } &::after { transform: translate(calc(var(--sgn)*(100% - #{$pad}))) } }
实例代码:点击这里
现在让我们快速查看一些演示,其中计算符号(可能还有绝对值)会派上用场。
首先,我们有一个方格的单元格,radial-gradient
其半径从覆盖整个单元格缩小到没有。这animation
有一个延迟计算,如上一节所述。这里的新内容是radial-gradient
圆的坐标取决于单元格相对于网格中间的位置——也就是说,取决于列--i
和行--j
索引与中间索引之间差异的符号--m
。
/* relevant CSS */ $t: 2s; @property --p { syntax: ''; initial-value: -1px; inherits: false; } .cell { --m: calc(.5*(var(--n) - 1)); --dif-i: calc(var(--m) - var(--i)); --abs-i: max(var(--dif-i), -1*var(--dif-i)); --sgn-i: clamp(-1, var(--dif-i)/.5, 1); --dif-j: calc(var(--m) - var(--j)); --abs-j: max(var(--dif-j), -1*var(--dif-j)); --sgn-j: clamp(-1, var(--dif-j)/.5, 1); background: radial-gradient(circle at calc(50% + 50%*var(--sgn-i)) calc(50% + 50%*var(--sgn-j)), currentcolor var(--p), transparent calc(var(--p) + 1px)) nth($c, 2); animation-delay: calc((.5*(var(--abs-i) + var(--abs-j))/var(--m) - 1)*#{$t}); } @keyframes p { 0% { --p: 100%; } }
实例代码:点击这里
然后我们有一个双螺旋小球体,其中球体直径--d
和径向距离--x
都取决于--abs
每个人的指数--i
和中间指数之间的差值的绝对值,这有助于确定球体位置--m
。该--sgn
差值的符号用于确定螺旋旋转方向。这取决于每个球体相对于中间的位置——也就是说,它的索引--i
是小于还是大于中间索引--m
。
/* relevant styles */ --m: calc(.5*(var(--p) - 1)); --abs: max(calc(var(--m) - var(--i)), calc(var(--i) - var(--m))); --sgn: clamp(-1, var(--i) - var(--m), 1); --d: calc(3px + var(--abs)/var(--p)*#{$d}); /* sphere diameter */ --a: calc(var(--k)*1turn/var(--n-dot)); /* angle used to determine sphere position */ --x: calc(var(--abs)*2*#{$d}/var(--n-dot)); /* how far from spiral axis */ --z: calc((var(--i) - var(--m))*2*#{$d}/var(--n-dot)); /* position with respect to screen plane */ width: var(--d); height: var(--d); transform: /* change rotation direction by changing x axis direction */ scalex(var(--sgn)) rotate(var(--a)) translate3d(var(--x), 0, var(--z)) /* reverse rotation so the sphere is always seen from the front */ rotate(calc(-1*var(--a))); /* reverse scaling so lighting on sphere looks consistent */ scalex(var(--sgn))
实例代码:点击这里
最后,我们有一个带有border
。这些框有一个mask
使用conic-gradient
动画开始角度创建的,--ang
。这些盒子是水平翻转还是垂直翻转取决于它们相对于中间的位置——也就是说,取决于列–i 和行–j 索引与中间索引之间差异的符号,--m
。的animation-delay
依赖于这些差的绝对值和作为前一节中解释的计算。我们也有一个filter
更好的“蠕虫”外观的粘性,但我们不会在这里讨论。
/* relevant CSS */ $t: 1s; @property --ang { syntax: ''; initial-value: 0deg; inherits: false; } .box { --m: calc(.5*(var(--n) - 1)); --dif-i: calc(var(--i) - var(--m)); --dif-j: calc(var(--j) - var(--m)); --abs-i: max(var(--dif-i), -1*var(--dif-i)); --abs-j: max(var(--dif-j), -1*var(--dif-j)); --sgn-i: clamp(-1, 2*var(--dif-i), 1); --sgn-j: clamp(-1, 2*var(--dif-j), 1); transform: scale(var(--sgn-i), var(--sgn-j)); mask: repeating-conic-gradient(from var(--ang, 0deg), red 0% 12.5%, transparent 0% 50%); animation: ang $t ease-in-out infinite; animation-delay: calc(((var(--abs-i) + var(--abs-j))/var(--n) - 1)*#{$t}); } @keyframes ang { to { --ang: .5turn; } }
实例代码:点击这里
时间(不仅仅是)格式
假设我们有一个元素,我们在自定义属性中为其存储了秒数,例如–val,我们希望以某种 mm:ss 格式显示它。
我们使用–val 和 60(一分钟内的秒数)之间的比率的下限来获得分钟数和超过该分钟数的秒数的模数。然后我们使用一个巧妙的小 counter 技巧在伪元素中显示格式化的时间。
@property --min { syntax: ''; initial-value: 0; inherits: false; } code { --min: calc(var(--val)/60 - .5); --sec: calc(var(--val) - var(--min)*60); counter-reset: min var(--min) sec var(--sec); &::after { /* so we get the time formatted as 02:09 */ content: counter(min, decimal-leading-zero) ':' counter(sec, decimal-leading-zero); } }
这在大多数情况下都有效,但当–val 正好为 0 时,我们会遇到一个问题。在这种情况下,0/60 为 0,然后减去.5,我们得到-.5,这四舍五入到绝对值中相邻的较大整数。也就是说,-1,不是 0!这意味着我们的结果将是-01:60,而不是 00:00!
幸运的是,我们有一个简单的修复方法,那就是稍微改变获取分钟数的公式–min:
--min: max(0, var(--val)/60 - .5);
还有其他格式选项,如下图所示:
/* shows time formatted as 2:09 */ content: counter(min) ':' counter(sec, decimal-leading-zero); /* shows time formatted as 2m9s */ content: counter(min) 'm' counter(sec) 's';
我们也可以应用相同的技术来格式化时间 hh:mm:ss。
@property --hrs { syntax: ''; initial-value: 0; inherits: false; } @property --min { syntax: ''; initial-value: 0; inherits: false; } code { --hrs: max(0, var(--val)/3600 - .5); --mod: calc(var(--val) - var(--hrs)*3600); --min: max(0, var(--mod)/60 - .5); --sec: calc(var(--mod) - var(--min)*60); counter-reset: hrs var(--hrs) var(--min) sec var(--sec); &::after { /* so we get the time formatted as 00:02:09 */ content: counter(hrs, decimal-leading-zero) ':' counter(min, decimal-leading-zero) ':' counter(sec, decimal-leading-zero); } }
这是我用来设计本 output 机范围滑块样式的一种技术,如下所示。
实例代码:点击这里
时间不是我们唯一可以利用的东西。计数器值必须是整数值,这意味着模运算技巧对于显示小数也很方便,如下面的第二个滑块所示。
实例代码:点击这里
码云笔记 » 今天在 CSS 中使用绝对值、符号、四舍五入和模数