CSS三角函数:`sin()`, `cos()`, `tan()`在圆形布局与动画轨迹中的应用

CSS 三角函数:sin(), cos(), tan()在圆形布局与动画轨迹中的应用

大家好,今天我们来深入探讨 CSS 中的三角函数 sin(), cos(), 和 tan(),以及它们在圆形布局和动画轨迹生成中的应用。虽然 CSS 函数主要用于样式定义,但结合 CSS 变量和 JavaScript,我们可以实现一些非常酷炫的效果,远远超出静态样式的范畴。

1. 三角函数基础回顾

在开始之前,我们快速回顾一下三角函数的基本概念。在一个直角三角形中,对于一个非直角的角 θ:

  • 正弦(sin θ): 对边 / 斜边
  • 余弦(cos θ): 邻边 / 斜边
  • 正切(tan θ): 对边 / 邻边

在单位圆(半径为 1 的圆)中,我们可以更直观地理解这些函数。圆心位于坐标原点 (0, 0),一个角 θ 从 x 轴正方向逆时针旋转。此时,角 θ 的终边与单位圆的交点坐标为 (cos θ, sin θ)。tan θ 则是 sin θ / cos θ。

CSS 中的角度单位可以是 deg(度)、rad(弧度)、grad(梯度)或 turn(圈)。sin(), cos(), tan() 函数接受的角度参数单位是弧度(radians)。因此,如果使用角度,需要将其转换为弧度。转换公式为:

弧度 = 角度 * π / 180

2. 在 CSS 中使用三角函数

CSS 自 calc() 函数出现后,允许进行简单的数学运算。但是,直到 sin(), cos(), tan() 等数学函数的引入,CSS 才真正具备了处理复杂几何问题的能力。

示例:简单的位置计算

假设我们有一个容器,想要将一个元素放置在容器中心,并沿 x 轴偏移一段距离。我们可以使用 calc()cos() 函数来实现。

<div class="container">
  <div class="element"></div>
</div>
.container {
  position: relative;
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

.element {
  position: absolute;
  width: 20px;
  height: 20px;
  background-color: red;
  left: calc(50% + cos(45deg) * 50px - 10px); /* 50% 容器宽度的一半,50px 偏移距离,10px 元素宽度的一半 */
  top: calc(50% - 10px); /* 垂直居中 */
}

在这个例子中,cos(45deg) 计算出的值乘以 50px,得到沿 x 轴的偏移量。减去 10px 是为了让元素的中心点位于计算出的坐标上。

注意: 直接使用 cos(45deg) 会报错,因为 CSS 期望的是弧度。我们需要将其转换为弧度。

3. 圆形布局

利用三角函数,我们可以轻松实现圆形布局。核心思想是:将元素放置在以容器中心为圆心的圆周上,每个元素之间的角度相等。

实现方法:

  1. 确定圆心坐标: 通常是容器的中心点。
  2. 确定半径: 圆形布局的半径。
  3. 计算每个元素的角度: 将 360 度(或 2π 弧度)除以元素数量,得到每个元素之间的角度差。
  4. 使用 cos()sin() 函数计算每个元素的 x 和 y 坐标:

    • x 坐标 = 圆心 x + 半径 * cos(角度)
    • y 坐标 = 圆心 y + 半径 * sin(角度)

代码示例:

<div class="circle-container">
  <div class="circle-item">1</div>
  <div class="circle-item">2</div>
  <div class="circle-item">3</div>
  <div class="circle-item">4</div>
  <div class="circle-item">5</div>
  <div class="circle-item">6</div>
</div>
.circle-container {
  position: relative;
  width: 300px;
  height: 300px;
  border: 1px solid blue;
  border-radius: 50%; /* 使容器看起来像一个圆 */
}

.circle-item {
  position: absolute;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: lightgreen;
  text-align: center;
  line-height: 40px;
}

.circle-container {
  --item-count: 6; /*  总共6个元素 */
  --radius: 120px; /* 圆的半径 */
}

.circle-item:nth-child(1) {
  left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * 0)) - 20px);
  top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * 0)) - 20px);
}

.circle-item:nth-child(2) {
  left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * 1)) - 20px);
  top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * 1)) - 20px);
}

.circle-item:nth-child(3) {
  left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * 2)) - 20px);
  top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * 2)) - 20px);
}

.circle-item:nth-child(4) {
  left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * 3)) - 20px);
  top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * 3)) - 20px);
}

.circle-item:nth-child(5) {
  left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * 4)) - 20px);
  top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * 4)) - 20px);
}

.circle-item:nth-child(6) {
  left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * 5)) - 20px);
  top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * 5)) - 20px);
}

优化:使用 CSS 变量和循环

上面的代码很冗长,每个元素都需要单独定义。我们可以使用 CSS 变量和预处理器(如 Sass 或 Less)来简化代码。

Sass 示例:

.circle-container {
  position: relative;
  width: 300px;
  height: 300px;
  border: 1px solid blue;
  border-radius: 50%;
  --item-count: 6;
  --radius: 120px;

  .circle-item {
    position: absolute;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background-color: lightgreen;
    text-align: center;
    line-height: 40px;
    @for $i from 0 through (var(--item-count) - 1) {
      &:nth-child(#{$i + 1}) {
        left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * #{$i})) - 20px);
        top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * #{$i})) - 20px);
      }
    }
  }
}

这种方式更加简洁,易于维护。只需要修改 --item-count--radius 变量,就可以调整圆形布局的元素数量和半径。

更进一步:使用 JavaScript 控制

如果需要在运行时动态调整元素数量,可以使用 JavaScript 来生成 CSS 变量并更新样式。

<div class="circle-container" id="circleContainer">
  </div>
const container = document.getElementById('circleContainer');
const itemCount = 12; // 动态设置元素数量
const radius = 100; //动态设置半径

container.style.setProperty('--item-count', itemCount);
container.style.setProperty('--radius', radius);

for (let i = 0; i < itemCount; i++) {
  const item = document.createElement('div');
  item.classList.add('circle-item');
  item.textContent = i + 1;
  container.appendChild(item);
}

// 动态添加样式
const style = document.createElement('style');
style.textContent = `
.circle-container {
  position: relative;
  width: 300px;
  height: 300px;
  border: 1px solid blue;
  border-radius: 50%;
}

.circle-item {
  position: absolute;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: lightgreen;
  text-align: center;
  line-height: 40px;
}

.circle-item:nth-child(n) {
    left: calc(50% + var(--radius) * cos(calc(360deg / var(--item-count) * (n - 1))) - 20px);
    top: calc(50% + var(--radius) * sin(calc(360deg / var(--item-count) * (n - 1))) - 20px);
  }
`;

document.head.appendChild(style);

这个例子展示了如何使用 JavaScript 动态生成元素和样式,实现更灵活的圆形布局。

4. 动画轨迹

三角函数不仅可以用于静态布局,还可以用于创建复杂的动画轨迹。例如,可以让元素沿着圆形、椭圆形或更复杂的曲线运动。

圆形轨迹动画

<div class="orbit-container">
  <div class="orbit-item"></div>
</div>
.orbit-container {
  position: relative;
  width: 200px;
  height: 200px;
  border: 1px solid red;
  border-radius: 50%;
  margin: 50px auto; /* 居中 */
}

.orbit-item {
  position: absolute;
  width: 20px;
  height: 20px;
  background-color: blue;
  border-radius: 50%;
  top: calc(50% - 10px);
  left: calc(50% - 10px); /* 初始位置在圆心 */
  animation: orbit 5s linear infinite;
}

@keyframes orbit {
  0% {
    transform: translate(calc(cos(0deg) * 80px), calc(sin(0deg) * 80px));
  }
  100% {
    transform: translate(calc(cos(360deg) * 80px), calc(sin(360deg) * 80px));
  }
}

在这个例子中,orbit-item 元素沿着以 orbit-container 中心为圆心的圆形轨迹运动。animation 属性指定了动画名称、持续时间、速度曲线和循环次数。@keyframes 规则定义了动画的关键帧,使用 cos()sin() 函数计算每个关键帧的 x 和 y 偏移量。

优化:使用 CSS 变量和 transform: rotate()

上述方法虽然可行,但效率不高,因为每次动画帧都需要重新计算 cos()sin() 函数。更高效的方法是使用 transform: rotate() 属性。

.orbit-container {
  position: relative;
  width: 200px;
  height: 200px;
  border: 1px solid red;
  border-radius: 50%;
  margin: 50px auto;
}

.orbit-item {
  position: absolute;
  width: 20px;
  height: 20px;
  background-color: blue;
  border-radius: 50%;
  top: calc(50% - 10px);
  left: calc(50% - 10px);
  transform: translateX(80px); /* 将元素移动到圆周上 */
  animation: rotate 5s linear infinite;
}

@keyframes rotate {
  from {
    transform: translateX(80px) rotate(0deg);
  }
  to {
    transform: translateX(80px) rotate(360deg);
  }
}

这种方法首先使用 translateX() 将元素移动到圆周上,然后使用 rotate() 属性使其围绕圆心旋转。 这种方法利用了transform的特性,避免了在动画的每一帧都计算sin和cos,性能更好。

椭圆形轨迹动画

椭圆形轨迹动画与圆形轨迹动画类似,只需要修改 cos()sin() 函数的参数,使其分别乘以椭圆的半长轴和半短轴。

<div class="ellipse-container">
  <div class="ellipse-item"></div>
</div>
.ellipse-container {
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid green;
  margin: 50px auto;
}

.ellipse-item {
  position: absolute;
  width: 20px;
  height: 20px;
  background-color: orange;
  border-radius: 50%;
  top: calc(50% - 10px);
  left: calc(50% - 10px);
  animation: ellipse 5s linear infinite;
}

@keyframes ellipse {
  0% {
    transform: translate(calc(cos(0deg) * 130px), calc(sin(0deg) * 80px));
  }
  100% {
    transform: translate(calc(cos(360deg) * 130px), calc(sin(360deg) * 80px));
  }
}

在这个例子中,ellipse-item 元素沿着以 ellipse-container 中心为圆心的椭圆形轨迹运动。半长轴为 130px,半短轴为 80px。

更复杂的轨迹:结合 JavaScript 和 CSS 变量

对于更复杂的轨迹,可以使用 JavaScript 计算轨迹上的点,并将这些点传递给 CSS 变量。然后,在 CSS 中使用这些变量来定义动画的关键帧。

<div class="custom-container">
  <div class="custom-item"></div>
</div>
.custom-container {
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid purple;
  margin: 50px auto;
}

.custom-item {
  position: absolute;
  width: 20px;
  height: 20px;
  background-color: yellow;
  border-radius: 50%;
  top: calc(50% - 10px);
  left: calc(50% - 10px);
  animation: custom-path 5s linear infinite;
}

@keyframes custom-path {
  /* 关键帧由 JavaScript 动态生成 */
}
const container = document.querySelector('.custom-container');
const item = document.querySelector('.custom-item');
const keyframes = [];
const numberOfFrames = 60; // 关键帧数量

for (let i = 0; i < numberOfFrames; i++) {
  const angle = (i / numberOfFrames) * 2 * Math.PI; // 角度
  const x = Math.cos(angle) * (100 + 50 * Math.sin(angle * 2)); // X 坐标(示例:花瓣曲线)
  const y = Math.sin(angle) * (100 + 50 * Math.sin(angle * 2)); // Y 坐标

  keyframes.push(`${(i / numberOfFrames) * 100}% { transform: translate(${x}px, ${y}px); }`);
}

const animationRule = `@keyframes custom-path { ${keyframes.join('')} }`;
const styleSheet = document.createElement("style");
styleSheet.type = "text/css";
document.head.appendChild(styleSheet);
styleSheet.sheet.insertRule(animationRule, 0);

在这个例子中,JavaScript 计算了一个花瓣曲线上的点,并将这些点作为关键帧添加到 custom-path 动画中。

5. 其他三角函数的应用:tan()

虽然 sin()cos() 在圆形布局和动画中应用更为广泛,但 tan() 也有其独特的用途。例如,可以用于创建倾斜效果或计算角度。

示例:计算倾斜角度

假设我们知道一个元素的宽度和高度,想要计算它的倾斜角度,可以使用 tan() 函数的反函数 atan()。但是,CSS 中没有直接的 atan() 函数。一种解决方法是使用 JavaScript 计算 atan(),并将结果传递给 CSS 变量。

<div class="skew-container">
  <div class="skew-item"></div>
</div>
.skew-container {
  width: 200px;
  height: 100px;
  border: 1px solid black;
}

.skew-item {
  width: 100%;
  height: 100%;
  background-color: lightblue;
  transform: skewY(var(--skew-angle));
}
const container = document.querySelector('.skew-container');
const width = container.offsetWidth;
const height = container.offsetHeight;
const angleInRadians = Math.atan(height / width);
const angleInDegrees = angleInRadians * 180 / Math.PI;

container.style.setProperty('--skew-angle', `${angleInDegrees}deg`);

在这个例子中,JavaScript 计算了 skew-item 元素的倾斜角度,并将结果传递给 --skew-angle CSS 变量。

6. 性能考量

在使用三角函数进行动画时,需要注意性能问题。频繁地计算 sin(), cos(), 和 tan() 函数可能会导致性能下降。以下是一些优化建议:

  • 尽量避免在动画的每一帧都重新计算三角函数。 可以预先计算好关键帧,或者使用 transform: rotate() 属性。
  • 使用 CSS 变量来缓存计算结果。 这样可以避免重复计算。
  • 使用 will-change 属性来提示浏览器优化动画。
  • 在移动设备上,尽量简化动画效果,避免使用过于复杂的计算。

7. 常见问题与注意事项

  • 角度单位: 确保使用正确的角度单位(弧度)。
  • 浏览器兼容性: sin(), cos(), 和 tan() 函数的浏览器兼容性较好,但建议进行测试。
  • 计算精度: CSS 的计算精度可能有限,对于需要高精度计算的场景,建议使用 JavaScript。
  • 避免过度使用: 虽然三角函数可以创建很酷炫的效果,但过度使用可能会影响用户体验。

今天的内容就到这里,希望大家能掌握三角函数在 CSS 中的应用,创造出更丰富的视觉效果。

总结:掌握三角函数,为CSS动画带来更多可能性

三角函数 sin(), cos(), 和 tan() 为 CSS 带来了强大的数学能力,使得创建圆形布局和复杂动画轨迹成为可能。 通过结合 CSS 变量和 JavaScript,可以实现更加灵活和动态的效果。

更多IT精英技术系列讲座,到智猿学院

发表回复

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