发布于 2026-01-05 1 阅读
0

带有 CSS 的幸运轮盘

带有 CSS 的幸运轮盘

我的推送里刚刚出现了一个“幸运轮盘”组件。我每次都转,但从来没赢过!总之,这类组件通常是用 CSS 实现的<canvas>,所以我决定写一篇教程,教大家如何用 CSS 制作。至于交互功能,还是需要用到 JavaScript。

我们将要建造的东西如下:

命运之轮

标记

对于楔形块,我们将使用一个简单的列表:

<ul class="wheel-of-fortune">
  <li>$1000</li>
  <li>$2000</li>
  <li>$3000</li>
  <li>$4000</li>
  <li>$5000</li>
  <li>$6000</li>
  <li>$7000</li>
  <li>$8000</li>
  <li>$9000</li>
  <li>$10000</li>
  <li>$11000</li>
  <li>$12000</li>
</ul>
Enter fullscreen mode Exit fullscreen mode

好的,我们现在有了一系列数字。接下来,我们来设置一些初始样式:

:where(.ui-wheel-of-fortune) {
  --_items: 12;
  all: unset;
  aspect-ratio: 1 / 1;
  background: crimson;
  container-type: inline-size;
  direction: ltr;
  display: grid;
  place-content: center start;
}
Enter fullscreen mode Exit fullscreen mode

首先,我们将使用一个变量来控制项目的数量。由于列表有 12 个项目,我们将其设置为 12 --_items: 12;

我进行了设置,container-type以便我们可以使用容器查询单元(稍后会详细介绍),然后创建一个内容“左居中”的网格。这样我们就得到了:

最初的

好吧,看起来没什么特别的,我们来看看这些楔形块:

li {
  align-content: center;
  background: deepskyblue;
  display: grid;
  font-size: 5cqi;
  grid-area: 1 / -1;
  list-style: none;
  padding-left: 1ch;
  transform-origin: center right;
  width: 50cqi;
}
Enter fullscreen mode Exit fullscreen mode

position: absolute我们不使用“堆叠”的方式将所有元素堆叠<li>在网格的同一位置,而是将元素grid-area: 1 / -1设置为,这意味着我们将围绕该轴旋转楔形。transform-origincenter right

所以,现在我们有了:

包含物品

因为所有元素都堆叠在一起,所以我们只能看到最后一个。

我们来解决这个问题。首先,我们给每个楔形区域添加一个索引变量:

li {
  &:nth-of-type(1) { --_idx: 1; }
  &:nth-of-type(2) { --_idx: 2; }
  &:nth-of-type(3) { --_idx: 3; }
  &:nth-of-type(4) { --_idx: 4; }
  &:nth-of-type(5) { --_idx: 5; }
  /* etc. */
}
Enter fullscreen mode Exit fullscreen mode

这样我们只需要再添加一行CSS代码:

li {
  rotate: calc(360deg / var(--_items) * calc(var(--_idx) - 1));
}
Enter fullscreen mode Exit fullscreen mode

旋转

快完成了!让我们使用相同的变量来创建一些颜色变化:

li {
  background: hsl(calc(360deg / var(--_items) *
  calc(var(--_idx))), 100%, 75%);
}
Enter fullscreen mode Exit fullscreen mode

颜色变化


π 的一片

要计算楔形块的高度,我们需要用圆的周长除以物品的数量。你可能还记得,圆的周长是:

C=2πr
Enter fullscreen mode Exit fullscreen mode

因为我们使用的是容器单位,所以半径为0.0000 英寸50cqi,因此我们需要在 CSS 中使用以下公式:

li {
  height: calc((2 * pi * 50cqi) / var(--_items));
}
Enter fullscreen mode Exit fullscreen mode

现在CSS里有了π,是不是很酷?!

以π表示高度

现在,我们给clip-path每个楔形区域添加一个简单的图形。我们从左上角开始,移动到右侧中心,然后再回到左下角:

li {
  clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
}
Enter fullscreen mode Exit fullscreen mode

使用裁剪路径

让我们从边缘减去一些:

li {
  clip-path: polygon(0% -2%, 100% 50%, 0% 102%);
}
Enter fullscreen mode Exit fullscreen mode

不太确定,有没有数学上正确的方法来解决这个问题?

总之,现在我们只需要border-radius: 50%在包装器中添加以下内容:

带圆角

嗯,不太好。我们改用 a clip-path,加上insetand round

.wheel-of-fortune {
  clip-path: inset(0 0 0 0 round 50%);
}
Enter fullscreen mode Exit fullscreen mode

好多了:

命运之轮,最终章

因为我们对楔形使用了容器单元,所以font-size它是完全响应式的!


让它旋转

现在,让我们添加一个旋转元素<button>(参见下方代码示例中的 CSS),并使用 JavaScript 触发该旋转元素:

function wheelOfFortune(selector) {
  const node = document.querySelector(selector);
  if (!node) return;

  const spin = node.querySelector('button');
  const wheel = node.querySelector('ul');
  let animation;
  let previousEndDegree = 0;

  spin.addEventListener('click', () => {
    if (animation) {
      animation.cancel(); // Reset the animation if it already exists
    }

    const randomAdditionalDegrees = Math.random() * 360 + 1800;
    const newEndDegree = previousEndDegree + randomAdditionalDegrees;

    animation = wheel.animate([
      { transform: `rotate(${previousEndDegree}deg)` },
      { transform: `rotate(${newEndDegree}deg)` }
    ], {
      duration: 4000,
      direction: 'normal',
      easing: 'cubic-bezier(0.440, -0.205, 0.000, 1.130)',
      fill: 'forwards',
      iterations: 1
    });

    previousEndDegree = newEndDegree;
  });
}
Enter fullscreen mode Exit fullscreen mode

我没有选择添加和删除 css 类并@property使用新的旋转角度更新,而是选择了最简单的解决方案:Web Animations API

完整代码如下:

更新:形状大师 Temani Atif 提供了一种更优雅的方法来创建楔形tanaspect-ratio见下方评论)。


更多想法

我鼓励你尝试其他样式!比如添加虚线边框?

虚线边框

文章来源:https://dev.to/madsstoumann/wheel-of-fortune-with-css-p-pi-1ne9