【问题标题】:Anime JS animate incrementally to a specific point along a path动画 JS 沿路径递增动画到特定点
【发布时间】:2020-08-13 03:16:21
【问题描述】:

https://codepen.io/jesserosenfield/pen/LYNGRXV

var path = anime.path('#prog-svg path'),
    pathEl = document.querySelectorAll('#prog-svg path')[0],
    mylength = pathEl.getTotalLength(),
    mypt1 = pathEl.getPointAtLength(mylength * .10),
    mypt2 = pathEl.getPointAtLength(mylength * .25);

var motionPath = anime({
    targets: '.prog-circ',
    translateX: path('x'),
    translateY: path('y'),
    rotate: path('angle'),
    easing: 'easeInOutCirc',
    duration: 5000,
    direction: 'alternate',
    autoplay: false,
    elasticity: 200,
    loop: false,
    update: function(anim){
      console.log(path('x'));
    }
  });

motionPath.seek(1210);

motionPath.play();

这段代码在广义上做了我希望它做的事情,但我有一个更具体的用例。

我将此 SVG 用作表单上的进度条:

当用户完成表单的第 1 步时,我希望圆圈从 A 点动画到 B 点。当用户完成表单的第 2 步时,我希望圆圈从 B 点动画到 C 点...等等。

虽然 motionpath.seek() 将我带到路径上的正确点,但它会在没有动画的情况下设置圆圈 - 是否有与 seek() 等效的函数来为圆圈设置动画而不是仅仅设置它?

此外,我尝试使用 getTotalLength()getPointAtLength() 尝试像这样制作动画:

var motionPath = anime({
    targets: '.prog-circ',
    translateX: [mypt1.x, mypt2.x],
    translateY: [mypt1.y, mypt2.y],

但这并没有为路径上的圆圈设置动画。

非常感谢任何帮助。谢谢!

【问题讨论】:

    标签: javascript animation anime.js


    【解决方案1】:

    对于一条长路径,我认为很难支持点之间的移动,因为您需要跟踪当前进度并根据缓动函数将其转换为实际长度。

    我会将您的 <path/> 分成 3 部分,为这 3 部分之间的动画生成时间线,然后轻松地来回操纵移动圆圈。

    这是一个如何完成的示例:

    const svg = document.getElementById('prog-svg');
    const pathEl = document.querySelector('#prog-svg path');
    const totalLength = pathEl.getTotalLength();
    
    const points = [['A', 10], ['B', 25], ['C', 75], ['D', 90]];
    
    function splitPath() {
      const interval = 3;
      const toLen = percentage => percentage * totalLength / 100;
      const paths = [];
      for (let i = 0; i < points.length; i++) {
        const from = toLen(points[i][1]);
    
        for (let j = i + 1; j < points.length; j++) {
          const to = toLen(points[j][1]);
          const segments = [];
          for (let k = from; k <= to; k += interval) {
            const { x, y } = pathEl.getPointAtLength(k);
            segments.push([x, y]);
          }
          paths.push({
            segments, path: `${i}-${j}`
          });
        }
      }
    
      paths.forEach(subPath => {
        const subPathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        subPathEl.setAttribute('class', `st0 st0--hidden`);
        subPathEl.setAttribute('d', `M ${subPath.segments.map(([x, y]) => `${x},${y}`).join(' ')}`);
        svg.appendChild(subPathEl);
        subPath.el = subPathEl;
      });
    
      return paths;
    }
    
    const subPaths = splitPath();
    
    function addPoint(name, progress) {
      const point = pathEl.getPointAtLength(totalLength * progress / 100);
    
      const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
      text.setAttribute('fill', '#fff');
      text.setAttribute('font-size', '1.6em');
      text.textContent = name;
    
      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      circle.setAttribute('r', '30');
      circle.setAttribute('fill', '#000');
    
      const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
      g.setAttribute('transform', `translate(${point.x},${point.y})`);
      g.appendChild(circle);
    
      g.appendChild(text);
    
      svg.appendChild(g);
    
      // center text
      const textBB = text.getBBox();
      const centerX = textBB.width / 2;
      const centerY = textBB.height / 4;
      text.setAttribute('transform', `translate(${-centerX},${centerY})`);
    
      return circle;
    }
    
    points.forEach(([name, progress]) => addPoint(name, progress));
    
    const progressCircle = document.querySelector('.prog-circ');
    progressCircle.style.display = 'block';
    
    const animations = subPaths.map(subPath => {
      const animePath = anime.path(subPath.el);
      return anime({
        targets: progressCircle,
        easing: 'easeInOutCirc',
        autoplay: false,
        duration: 1000,
        translateX: animePath('x'),
        translateY: animePath('y'),
        rotate: animePath('angle'),
      });
    });
    // move circle to the first point
    animations[0].reset();
    
    let currentStep = 0;
    
    function moveTo(step) {
      if (step < 0 || step > animations.length) return;
      const delta = step - currentStep;
    
      const path = delta > 0 ? `${currentStep}-${step}` : `${step}-${currentStep}`;
      const animationIndex = subPaths.findIndex(subPath => subPath.path === path);
      const animationToPlay = animations[animationIndex];
    
      if (delta < 0 &&  !animationToPlay.reversed) {
        animationToPlay.reverse();
      }
      if (delta > 0 &&  animationToPlay.reversed) {
        animationToPlay.reverse();
      }
      animationToPlay.reset();
      animationToPlay.play();
    
      currentStep = step;
      pagination.selectedIndex = step;
    }
    
    const btnPrev = document.getElementById('btn-prev');
    const btnNext = document.getElementById('btn-next');
    const pagination = document.getElementById('pagination');
    btnPrev.addEventListener('click', () => moveTo(currentStep - 1));
    btnNext.addEventListener('click', () => moveTo(currentStep + 1));
    pagination.addEventListener('change', (e) => moveTo(+e.target.value));
    body {
      margin: 0;
    }
    
    .st0 {
      fill: none;
      stroke: #000000;
      stroke-width: 5;
      stroke-linecap: round;
      stroke-miterlimit: 160;
      stroke-dasharray: 28;
    }
    
    .st0--hidden {
      stroke: none;
    }
    .prog-circ {
      display: none;
      position: absolute;
      border-radius: 100%;
      height: 30px;
      width: 30px;
      top: -15px;
      left: -15px;
      background: #ccc;
      opacity: .7;
    }
    
    .form-actions {
      margin-top: 2em;
      display: flex;
      justify-content: center;
    }
    
    #pagination,
    .form-actions button + button {
      margin-left: 2em;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.0/anime.min.js"></script>
    <svg id="prog-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1919.1 155.4">
      <g>
        <path class="st0" d="M4,84.1c0,0,58.8-57.1,235.1,17.9s348.1,18.9,470.2-44.6C800.6,9.7,869.6-2,953.5,6.6c0,0,19,4.1,38.6,14.4
          c20.7,10.9,40.7,40.6,40.7,65.6c0,40.2-29.5,64.8-69.7,64.8s-70.1-29.2-70.1-69.4c0-32.3,31.2-59.6,61.8-61.8
          c67.2-4.7,103.5-46.8,375.6,70.1c164.9,70.8,220.1-1.1,371.1-11.7c120.5-8.4,213.7,28.6,213.7,28.6"/>
      </g>
    </svg>
    
    <div class="prog-circ"></div>
    <div class="form-actions">
      <button id="btn-prev">Prev</button>
      <button id="btn-next">Next</button>
      <select id="pagination">
        <option value="0">A</option>
        <option value="1">B</option>
        <option value="2">C</option>
        <option value="3">D</option>
      </select>
    </div>

    【讨论】:

    • 你是最棒的。谢谢!
    猜你喜欢
    • 2015-07-15
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 2012-10-29
    • 1970-01-01
    • 2018-05-23
    • 2014-02-05
    相关资源
    最近更新 更多