Как анимировать элемент SVG вдоль пути от его последней / текущей позиции (только SMIL, без CSS и без js)? - PullRequest
1 голос
/ 14 января 2020

Я ищу анимировать элемент SVG вдоль пути БЕЗ CSS или Javascript, только с помощью SMIL. Что я хочу сделать, это:

  1. Воспроизвести анимацию на элементе
  2. Получить этот элемент, чтобы остаться в конечной позиции, когда анимация заканчивается
  3. Start следующая анимация, примененная к этому элементу ОТ его текущей позиции

До сих пор я выяснял, как добраться до точки 2 с помощью & установки атрибута fill="freeze", но я не могу получить чтобы найти способ добраться до моей точки 3 ... Каждый раз, элемент перезапускается с его первоначальной позиции.

Здесь фрагмент, чтобы посмотреть желаемый эффект (сделано с js там). Мне было интересно, если бы это было на самом деле возможно только с SMIL? И если так, что я должен исследовать, чтобы сделать это?

let animation = document.querySelector('#behavior_desired');

animation.addEventListener('endEvent', () => {
  let circle=document.querySelector('#element_animated');
  let newCX = circle.cx.animVal.value + 180;
  let newCY = circle.cy.animVal.value + 35; 
  circle.setAttribute("cx", newCX);
  circle.setAttribute("cy", newCY);
  animation.setAttribute("fill", "remove");
})

animation.addEventListener('beginEvent', () => {
  animation.setAttribute("fill", "freeze");
})
<h2>Element final position (after animation): INITIAL</h2>
<svg preserveAspectRatio="xMidYMid meet" width="auto" height="auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 200" style="background-color: #0010ff3b;">
  <path fill="none" stroke-linejoin="bevel" stroke="black" id="draw" d="m 100, 100 c 40,0 45,35 180,35"></path>
  <circle r="25" stroke="black" stroke-width="3" fill="red" cx="100" cy="100">
    <animateMotion dur="1s" fill="" begin="click">
      <mpath xlink:href="#motionpath"></mpath>
    </animateMotion>
  </circle>
  <path fill="none" stroke-linejoin="bevel" id="motionpath" stroke="none" d="m 0, 0 c 40,0 45,35 180,35"></path>
</svg>

<h2>Element final position (after animation): LAST FRAME</h2>
<svg preserveAspectRatio="xMidYMid meet" width="auto" height="auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 200" style="background-color: #0010ff3b;">
  <path fill="none" stroke-linejoin="bevel" stroke="black" id="draw2" d="m 100, 100 c 40,0 45,35 180,35"></path>
  <circle r="25" stroke="black" stroke-width="3" fill="red" cx="100" cy="100">
    <animateMotion dur="1s" fill="freeze" begin="click">
      <mpath xlink:href="#motionpath2"></mpath>
    </animateMotion>
  </circle>
  <path fill="none" stroke-linejoin="bevel" id="motionpath2" stroke="none" d="m 0, 0 c 40,0 45,35 180,35"></path>
</svg>

<h2>Desired behavior:</h2>
<p>After being animated, if we click on the element to re-launch the animation on it, I want that the animation starts with the element  at its current position</p>
<svg preserveAspectRatio="xMidYMid meet" width="auto" height="auto" xmlns="http://www.w3.org/2000/svg" viewBox="-30 -30 800 400" style="background-color: #0010ff3b;">
  <path fill="none" stroke-linejoin="bevel" stroke="black" id="draw3" d="m 0, 100 c 40,0 45,35 180,35"></path>
  <path fill="none" stroke-linejoin="bevel" stroke="black" stroke-dasharray="5,5" id="draw4" d="m 180, 135 c 40,0 45,35 180,35"></path>
  <path fill="none" stroke-linejoin="bevel" stroke="black" stroke-dasharray="2,2" id="draw5" d="m 360, 170 c 40,0 45,35 180,35"></path>
  <path fill="none" stroke-linejoin="bevel" stroke="black" stroke-dasharray="1,1" id="draw5" d="m 540, 205 c 40,0 45,35 180,35"></path>
  <circle id="element_animated" r="25" stroke="black" stroke-width="3" fill="red" cx="0" cy="100">
    <animateMotion id="behavior_desired" dur="1s" fill="freeze" begin="click">
      <mpath xlink:href="#motionpath3"></mpath>
    </animateMotion>
  </circle>
  <path fill="none" stroke-linejoin="bevel" id="motionpath3" stroke="none" d="m 0, 0 c 40,0 45,35 180,35"></path>
</svg>

1 Ответ

1 голос
/ 21 февраля 2020

Я бы подумал, что это сработало бы (см .: https://www.w3.org/TR/2001/REC-smil-animation-20010904/#Accumulate):

  <path transform="translate(100, 100)" fill="none" stroke-linejoin="bevel" id="motionpath" stroke="black" d="m 0, 0 c 40,0 45,35 180,35"></path>
  <circle id="circle" r="25" stroke="black" stroke-width="3" fill="red" cx="100" cy="100">
    <animateMotion begin="click" dur="1s" fill="freeze" addative="replace" accumulate="sum">
      <mpath xlink:href="#motionpath"></mpath>
    </animateMotion>
  </circle> 

Но это явно не так.

Я попробовал куча способов, в том числе поиск такой идеи:

  <circle id="circle" r="25" stroke="black" stroke-width="3" fill="red" cx="100" cy="100">    
    <animate attributeName="cx" from="100" by="+180" begin="circle.click" dur="1s" fill="freeze"  additive="replace" accumulate="sum"></animate>
    <animate attributeName="cy" from="100" by="+35" begin="circle.click" dur="1s" fill="freeze"  additive="replace" accumulate="sum"></animate>
  </circle>  

(фокусировался на поведении [перед рассмотрением пути], так как я думал, что это добавит 180 к cx и добавит 35 к cy каждый клик но это не так).

Ни одна из моих попыток не сработала.

Единственный способ, который я нашел, был с JS (используя накопление из первого примера), однако, я думаю, что мой JS способ более надежный и многократно используемый, чем в вопросе, так что, может быть, это немного поможет?

(Вы можете установить repeatCount в теге <animateMotion>, и это будет рассмотрено в JS и на каждой итерации он не прыгает, как в демонстрационной версии вопроса et c.)

Демонстрация на CodePen (или ниже): https://codepen.io/Alexander9111/pen/VwLmKVd

enter image description here

const svg = document.querySelector('svg');
const circle = document.querySelector('#circle');
const motionP = document.querySelector('#motionpath');
let animateM = document.querySelector('#circle > animateMotion');

let currentRepeatCount = 0;
const RepeatCount = animateM.getAttribute('repeatCount');
animateM.setAttribute('repeatCount', 'indefinite');

animateM.addEventListener('repeatEvent', (e) => {
  currentRepeatCount +=1;
  console.log(currentRepeatCount);
  svg.pauseAnimations();
});

circle.addEventListener('click', () => {
  animateM = document.querySelector('#circle > animateMotion');
  if (currentRepeatCount == 0){
    animateM.beginElement();
  } else if (currentRepeatCount == RepeatCount){
    return void(0);
  } else {    
    svg.unpauseAnimations();
  }  
})
<svg preserveAspectRatio="xMidYMid meet" width="auto" height="auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 400" style="background-color: #0010ff3b;">
  <path transform="translate(100, 100)" fill="none" stroke-linejoin="bevel" id="motionpath" stroke="black" d="m 0, 0 c 40,0 45,35 180,35"></path>
  <circle id="circle" r="25" stroke="black" stroke-width="3" fill="red" cx="100" cy="100">
    <animateMotion begin="indefinite" dur="1s" fill="freeze" addative="replace" accumulate="sum" repeatCount="4">
      <mpath xlink:href="#motionpath"></mpath>
    </animateMotion>
  </circle>  
</svg>

Если вы хотите переместить путь вместе с нами, мы можем отредактировать событие повтора:

animateM.addEventListener('repeatEvent', (e) => {
  currentRepeatCount +=1;
  console.log(currentRepeatCount);
  svg.pauseAnimations();
  if (currentRepeatCount < RepeatCount){
    const box = circle.getBoundingClientRect();
    var pt = svg.createSVGPoint();
    pt.x = (box.left + box.right) / 2;
    pt.y = (box.top + box.bottom) / 2;
    var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
    console.log(svgP.x,svgP.y)
    motionP.setAttribute('transform', `translate(${svgP.x}, ${svgP.y})`)
  }
});

И мы получаем это хорошее поведение :

enter image description here

...