【问题标题】:How do you implement animation along a path in theory?你如何在理论上实现沿路径的动画?
【发布时间】:2021-11-05 13:58:00
【问题描述】:

我正在查看 CSS 中的 offset-path,您实际上是这样做的:

* {
  box-sizing: border-box;
}

:root {
  --delay: 0ms;
  --path: path("M.4 76.8C102-24.9 266.9-24.9 368.5 76.8c81.3 81.3 81.3 213.2 0 294.5-65.1 65.1-170.6 65.1-235.6 0-52.1-52.1-52.1-136.5 0-188.5 41.6-41.6 109.2-41.6 150.8 0 33.3 33.3 33.3 87.3 0 120.6-26.7 26.7-69.9 26.7-96.5 0-21.3-21.3-21.3-55.9 0-77.2 17.1-17.1 44.7-17.1 61.8 0 13.6 13.6 13.6 35.8 0 49.4-10.9 10.9-28.6 10.9-39.5 0-8.7-8.7-8.7-22.9 0-31.6 7-7 18.3-7 25.3 0");
}

body {
  margin: 0;
  padding: 2rem;
  min-height: 100vh;
  background-color: #1b1b24;
  display: flex;
  justify-content: center;
  align-items: center;
}

.wrapper {
  position: relative;
}

.obj {
  --color: salmon;
  position: absolute;
  top: 0;
  left: 0;
  offset-path: var(--path);
  animation: move 4500ms infinite ease-in-out var(--delay);
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background-color: var(--color);
  opacity: 0;
  transform: scale(0);
}

.obj--2 {
  --delay: 1500ms;
  --color: hotpink;
}

.obj--3 {
  --delay: 3000ms;
  --color: turquoise;
}

svg {
  width: 429px;
}

@keyframes appear {
  100% {
    opacity: 1;
  }
}
@keyframes move {
  10% {
    opacity: 1;
    offset-distance: 0%;
    transform: scale(1);
  }
  30% {
    box-shadow: -0.5rem 0 0.3rem var(--color, white);
  }
  70% {
    box-shadow: -0.5rem 0 0.3rem var(--color, white);
  }
  90% {
    opacity: 1;
    offset-distance: 100%;
    transform: scale(0.2);
    box-shadow: none;
  }
  100% {
    opacity: 0;
    offset-distance: 100%;
    transform: scale(0.2);
  }
}
<div class="wrapper">
  <div class="obj"></div>
  <div class="obj obj--2"></div>
  <div class="obj obj--3"></div>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 431.7 422.6"><path d="M1.1 77.8c101.7-101.7 266.5-101.7 368.2 0 81.3 81.3 81.3 213.2 0 294.5-65.1 65.1-170.6 65.1-235.6 0-52.1-52.1-52.1-136.5 0-188.5 41.6-41.6 109.2-41.6 150.8 0 33.3 33.3 33.3 87.3 0 120.6-26.7 26.7-69.9 26.7-96.5 0-21.3-21.3-21.3-55.9 0-77.2 17.1-17.1 44.7-17.1 61.8 0 13.6 13.6 13.6 35.8 0 49.4-10.9 10.9-28.6 10.9-39.5 0-8.7-8.7-8.7-22.9 0-31.6 7-7 18.3-7 25.3 0" fill="none" stroke="#5e5e7d" stroke-width="3" stroke-miterlimit="10"/></svg>
</div>

他们有这个变量:

  --path: path("M.4 76.8C102-24.9 266.9-24.9 368.5 76.8c81.3 81.3 81.3 213.2 0 294.5-65.1 65.1-170.6 65.1-235.6 0-52.1-52.1-52.1-136.5 0-188.5 41.6-41.6 109.2-41.6 150.8 0 33.3 33.3 33.3 87.3 0 120.6-26.7 26.7-69.9 26.7-96.5 0-21.3-21.3-21.3-55.9 0-77.2 17.1-17.1 44.7-17.1 61.8 0 13.6 13.6 13.6 35.8 0 49.4-10.9 10.9-28.6 10.9-39.5 0-8.7-8.7-8.7-22.9 0-31.6 7-7 18.3-7 25.3 0");

不知何故,它被转换成一些底层数据结构,然后每一帧,你都会沿着那个“路径”移动。它是如何工作的,你如何在高层次上实现它?

基本上,我想您已经以某种方式将 SVG d 路径转换为向量(点数组?),但这似乎不对,因为有些点不在曲线上(控制点)。然后不知何故,时钟的每一次滴答/更新,它都会移动对象的 x 和 y 位置,使其沿着路径更远。不过,我并没有进行精神上的飞跃/联系,以了解如何实现这一点。有什么见解吗?

我想部分问题是,path 是如何在后台实现的?以及如何沿着路径/曲线计算时钟的每个刻度的下一个位置? CSS 以某种方式抽象了所有这些,我想知道它的内部工作原理。

【问题讨论】:

标签: animation math svg


【解决方案1】:

This 可能会有所帮助:

function setAnimationsProgress(insTime) {
  let i = 0;
  const animations = instance.animations;
  const animationsLength = animations.length;
  while (i < animationsLength) {
    const anim = animations[i];
    const animatable = anim.animatable;
    const tweens = anim.tweens;
    const tweenLength = tweens.length - 1;
    let tween = tweens[tweenLength];
    // Only check for keyframes if there is more than one tween
    if (tweenLength) tween = filterArray(tweens, t => (insTime < t.end))[0] || tween;
    const elapsed = minMax(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration;
    const eased = isNaN(elapsed) ? 1 : tween.easing(elapsed);
    const strings = tween.to.strings;
    const round = tween.round;
    const numbers = [];
    const toNumbersLength = tween.to.numbers.length;
    let progress;
    for (let n = 0; n < toNumbersLength; n++) {
      let value;
      const toNumber = tween.to.numbers[n];
      const fromNumber = tween.from.numbers[n] || 0;
      if (!tween.isPath) {
        value = fromNumber + (eased * (toNumber - fromNumber));
      } else {
        value = getPathProgress(tween.value, eased * toNumber, tween.isPathTargetInsideSVG);
      }
      if (round) {
        if (!(tween.isColor && n > 2)) {
          value = Math.round(value * round) / round;
        }
      }
      numbers.push(value);
    }
    // Manual Array.reduce for better performances
    const stringsLength = strings.length;
    if (!stringsLength) {
      progress = numbers[0];
    } else {
      progress = strings[0];
      for (let s = 0; s < stringsLength; s++) {
        const a = strings[s];
        const b = strings[s + 1];
        const n = numbers[s];
        if (!isNaN(n)) {
          if (!b) {
            progress += n + ' ';
          } else {
            progress += n + b;
          }
        }
      }
    }
    setProgressValue[anim.type](animatable.target, anim.property, progress, animatable.transforms);
    anim.currentValue = progress;
    i++;
  }
}

function getPathProgress(path, progress, isPathTargetInsideSVG) {
  function point(offset = 0) {
    const l = progress + offset >= 1 ? progress + offset : 0;
    return path.el.getPointAtLength(l);
  }
  const svg = getParentSvg(path.el, path.svg)
  const p = point();
  const p0 = point(-1);
  const p1 = point(+1);
  const scaleX = isPathTargetInsideSVG ? 1 : svg.w / svg.vW;
  const scaleY = isPathTargetInsideSVG ? 1 : svg.h / svg.vH;
  switch (path.property) {
    case 'x': return (p.x - svg.x) * scaleX;
    case 'y': return (p.y - svg.y) * scaleY;
    case 'angle': return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
  }
}

它正在计算每个动画步骤的路径点。不知道怎么解释。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 2012-10-29
    • 1970-01-01
    • 2022-10-26
    • 2023-03-25
    • 2011-06-29
    • 1970-01-01
    相关资源
    最近更新 更多