我如何 "perfectly" 在 Anime.js 中按照 SVG 路径使对象居中?

How do I "perfectly" center an object following an SVG path in Anime.js?

我正在 a circle path in SVG, using Anime.js 周围移动一个 20px 的正方形。然而,正方形并没有完全位于其圆形路径的中心:

anime({
    targets: '.square',
    translateX: path('x'),
    translateY: path('y'),
    rotate: path('angle'),
    easing: 'linear',
    duration: 10000,
    loop: true,
});

https://codepen.io/gremo/pen/wvKjrMW

我找到了类似的示例,但是我无法理解数学 来完成这项工作:

发生这种情况是因为您的对象(正方形)确实在沿着路径行驶。但是,对象是根据左上角值的第一个像素进行平移的(因此,如果制作 1px x 1px 正方形,您应该会看到该正方形很好地遵循路径)。这就是为什么对象的左上角总是粘在直线上的原因。你想要的是物体的中间部分粘在线上。

使用静态和手动计算的值(盒子原始宽度和高度的 50%)是合理的(例如,将 -10px 赋予 topleft)。但是,当有许多对象被动画化时,您可能不想这样做(因为发生更改时您需要更新所有 CSS 代码)。相反,我们可以在 .square 上使用伪元素 ::after 并将其宽度的 50% 向左移动,将其高度的 50% 移动到顶部。这样,物体的附着点就是正方形的中心部分。现在,当您更新原始 .square 元素的宽度和高度时,您无需更新 topleft 值。您不能简单地将 transform: translate(-50%, -50%) 值添加到原始 .square div,因为它将使用 Anime.js 进行动画处理,并且初始变换值将会丢失。从 JS 获取计算的初始变换值是棘手的。

document.addEventListener('DOMContentLoaded', function() {
  const path = anime.path('.circle path');

  anime({
    targets: '.square',
    translateX: path('x'),
    translateY: path('y'),
    easing: 'linear',
    duration: 10000,
    loop: true,
  });
});
body {
  background-color: #001f3f;
}

.container {
  position: relative;
  margin: 0 auto;
  max-width: 500px;
}

.circle {
  fill: none;
  stroke: #b10dc9;
}

.square {
  position: absolute;
  top: 0;
  left: 0;
  display: inline-block;
  width: 20px;
  height: 20px;
}

.square::after {
  content: '';
  position: absolute;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%);
  background-color: #f012be;
}
<script src="https://unpkg.com/animejs@3.2.0/lib/anime.min.js"></script>
<div class="container">
  <svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
    <path d="M400,250c0,82.84-67.16,150-150,150s-150-67.16-150-150s67.16-150,150-150S400,167.16,400,250z" />
  </svg>
  <span class="square"></span>
</div>

要理解逻辑,请添加另一个具有不同 width/height 的元素以查看每个对象的引用:

document.addEventListener('DOMContentLoaded', function() {
  const path = anime.path('.circle path');

  anime({
    targets: '.square',
    translateX:path('x'),
    translateY: path('y'),
    easing: 'linear',
    duration: 10000,
    loop: true,
  });
  anime({
    targets: '.square2',
    translateX: path('x'),
    translateY: path('y'),
    easing: 'linear',
    duration: 10000,
    loop: true,
  });
});
body {
  background-color: #001f3f;
}

.container {
  position: relative;
  margin: 0 auto;
  max-width: 500px;
}

.circle {
  fill: none;
  stroke: #b10dc9;
}

.square {
  position: absolute;
  top: 0;
  left: 0;
  display: inline-block;
  width: 20px;
  height: 20px;
  background-color: #f012be;
}

.square2 {
  position: absolute;
  top: 0;
  left: 0;
  display: inline-block;
  width: 40px;
  height: 40px;
  background-color: blue;
}
<script src="https://unpkg.com/animejs@3.2.0/lib/anime.min.js"></script>
<div class="container">
  <svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
    <path d="M400,250c0,82.84-67.16,150-150,150s-150-67.16-150-150s67.16-150,150-150S400,167.16,400,250z" />
  </svg>
  <span class="square2"></span>
  <span class="square"></span>
</div>

如您所见,top/left 角沿着路径。为避免这种情况,您可以使用负 left/top 来偏移元素并使其居中,或者使用不同的 CSS 来制作通用的东西。

我用 0 维制作元素并考虑 box-shadw 创建正方形:

document.addEventListener('DOMContentLoaded', function() {
  const path = anime.path('.circle path');

  anime({
    targets: '.square',
    translateX:path('x'),
    translateY: path('y'),
    easing: 'linear',
    duration: 10000,
    loop: true,
  });
  anime({
    targets: '.square2',
    translateX: path('x'),
    translateY: path('y'),
    easing: 'linear',
    duration: 10000,
    loop: true,
  });
});
body {
  background-color: #001f3f;
}

.container {
  position: relative;
  margin: 0 auto;
  max-width: 500px;
}

.circle {
  fill: none;
  stroke: #b10dc9;
}

.square {
  content:"";
  position: absolute;
  left:0;
  right:0;
  width: 0;
  height: 0;
  box-shadow:0 0 0 10px #f012be;
}

.square2 {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  box-shadow:0 0 0 20px blue;
}
<script src="https://unpkg.com/animejs@3.2.0/lib/anime.min.js"></script>
<div class="container">
  <svg class="circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
    <path d="M400,250c0,82.84-67.16,150-150,150s-150-67.16-150-150s67.16-150,150-150S400,167.16,400,250z" />
  </svg>
  <span class="square2"></span>
  <span class="square"></span>
</div>