Как управлять анимацией движения с помощью Javascript
Этот вопрос инициирован ответом в топике Как заставить circle двигаться по path с определенного места?
Как выяснилось Chrome обрабатывает с ошибками атрибуты анимации SVG keyPoints и keyTimes.
Поэтому невозможно изменить стартовую точку и направление движения без применения JS.
У меня есть SVG анимация кругового движения шарика по заданной трассе движения.
.container {
width:35vw;
height:35vh;
}
.path {
fill:none;
stroke:#679AFF;
stroke-width:3;
}
#BigCircle {
fill:#679AFF;
opacity:0.9;
}
#redBall {
fill:tomato;
}
<div class="container">
<svg id="svg" viewBox="0 0 430 430" fill="none" xmlns="http://www.w3.org/2000/svg" style="border:1px solid;">
<g>
<path class="path" d="m381.4 107.3c-4.7-7.2-9.9-14-15.5-20.6-5.5-6.5-11.1-12.9-17.4-18.7-7.4-6.8-15.4-13.1-23.8-18.7-7.6-5-15.7-9-23.8-12.9-8.8-4.3-17.7-8.5-27.1-11.6-8.8-2.9-17.9-5-27.1-6.4-10-1.6-20.1-2.7-30.3-2.6-10.8 0.1-21.6 1.4-32.2 3.2-11.8 2-23.5 4.6-34.8 8.4-9.1 3.1-17.9 7.1-26.4 11.6-9.2 4.9-18 10.5-26.4 16.7-6.6 4.9-12.7 10.5-18.7 16.1-5.8 5.5-11.6 11.2-16.7 17.4-4.9 5.9-9.4 12.2-13.5 18.7-4.7 7.3-9.2 14.7-12.9 22.5-3.6 7.5-6.5 15.3-9 23.2-3.3 10.5-6.1 21.3-7.7 32.2C16.6 195.5 16.4 205.3 16.5 215c0.1 9 0.9 18 2.2 26.9 1.5 10.4 3.6 20.8 6.4 30.9 2.4 8.6 5.3 17 9 25.1 3.6 7.9 8.2 15.3 12.9 22.5 4.4 6.7 9.2 13.1 14.2 19.3 4.9 6 10.1 11.8 15.5 17.4 5 5.1 9.8 10.5 15.5 14.8 6.2 4.6 13.3 7.7 20 11.6 8.4 4.9 16.2 10.9 25.1 14.8 7.2 3.2 15 4.9 22.5 7.1 8.5 2.4 17 5.1 25.8 6.4 9.1 1.5 18.4 1.9 27.7 1.9 9.9 0 19.8-0.5 29.6-1.9 8.9-1.3 17.8-3.3 26.4-5.8 9.5-2.8 18.7-6.2 27.7-10.3 8.2-3.7 16.3-8 23.8-12.9 8-5.2 15.3-11.2 22.5-17.4 7.6-6.5 15.4-13 21.9-20.6 7.1-8.3 12.9-17.7 18.7-27.1 5.4-8.8 10.7-17.6 14.8-27.1 4.1-9.5 7.3-19.5 9.7-29.6 2.7-11.2 4.5-22.6 5.2-34.1 0.6-10.9-0.2-21.9-1.3-32.9-1.1-10.8-2.3-21.7-5.2-32.2-2.8-10.2-7-20.1-11.6-29.6-4.1-8.7-8.9-17.1-14.2-25.1z" />
<circle id="redBall" cx="378" cy="105" r="15" >
<animateTransform attributeName="transform" type="rotate" begin="start.click" dur="4s" values="0 215 215; 359 215 215" repeatCount="1" restart="whenNotActive" />
<animateTransform attributeName="transform" type="rotate" begin="reverse.click" dur="4s" values="359 215 215; 0 215 215" repeatCount="1" restart="whenNotActive"/>
</circle>
</g>
<circle id="BigCircle" cx="378" cy="105" r="45" />
<circle id="start" cx="50" cy="410" r="12" fill="#679AFF" />
<text x="20" y="385" font-size="18" fill="#679AFF"> Вперед </text>
<circle id="reverse" cx="380" cy="410" r="12" fill="tomato" />
<text x="360" y="385" font-size="18" fill="tomato"> Назад </text>
</svg>
</div>
Для решения в этом вопросе я не использовал keyPoints и keyTimes в animateMotion, но выбрал анимацию трансформации вращения. <animateTransform attributeName="transform" type="rotate"
Но при изменении направления вращения при клике на зелёные и красные кнопки анимация начинается со стартовой точки. Другими словами, если запущена анимация вращения по часовой стрелке, то при переключении другой кнопкой направления вращения, результат предыдущей анимации сбрасывается. Всё начинается из стартовой точки.
Вопрос:
Как добиться остановки и запуска анимации с того же места, а также реверса движения и отмены результатов предыдущих анимаций движения?
Ответы (2 шт):
Данная задача решается с помощью Web APIs
- Запуск анимации - Animation.play()
- Реверс направления движения анимации решается с помощью метода Animation.reverse()
- Пауза - animation.pause();
- Сброс результатов предыдущих анимаций - Animation.cancel()
Остаётся добавить кнопки, повесить на них слушателей с обработчиками клика
let spinner = document.getElementById('spinner');
let spinnerAnimation = spinner.animate([
{
transform: 'rotate(0)'
},
{
transform: 'rotate(359deg)'
}
], {
duration: 3000,
iterations: Infinity
});
document.getElementById('pause').addEventListener('click', function() {
spinnerAnimation.pause();
});
document.getElementById('play').addEventListener('click', function() {
spinnerAnimation.play();
});
document.getElementById('reverse').addEventListener('click', function() {
spinnerAnimation.reverse();
});
document.getElementById('cancel').addEventListener('click', function() {
spinnerAnimation.cancel();
});
.controll {
margin-left:4rem;
}
.container {
width:430px;
height:430px;
}
.path {
fill:none;
stroke:#679AFF;
stroke-width:3;
}
#BigCircle {
fill:#679AFF;
opacity:0.9;
}
#spinner {
transform-origin: 215px 215px;
fill:tomato;
}
button {
background-color: #679AFF;
color: #fff;
border-radius: 5px;
padding: 9px 15px;
line-height: 1;
font-weight: bold;
border: 0;
cursor: pointer;
outline: none;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.08);
}
<div class="container">
<svg id="svg1" width="430" height="430" viewBox="0 0 430 430" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="path" d="m381.4 107.3c-4.7-7.2-9.9-14-15.5-20.6-5.5-6.5-11.1-12.9-17.4-18.7-7.4-6.8-15.4-13.1-23.8-18.7-7.6-5-15.7-9-23.8-12.9-8.8-4.3-17.7-8.5-27.1-11.6-8.8-2.9-17.9-5-27.1-6.4-10-1.6-20.1-2.7-30.3-2.6-10.8 0.1-21.6 1.4-32.2 3.2-11.8 2-23.5 4.6-34.8 8.4-9.1 3.1-17.9 7.1-26.4 11.6-9.2 4.9-18 10.5-26.4 16.7-6.6 4.9-12.7 10.5-18.7 16.1-5.8 5.5-11.6 11.2-16.7 17.4-4.9 5.9-9.4 12.2-13.5 18.7-4.7 7.3-9.2 14.7-12.9 22.5-3.6 7.5-6.5 15.3-9 23.2-3.3 10.5-6.1 21.3-7.7 32.2C16.6 195.5 16.4 205.3 16.5 215c0.1 9 0.9 18 2.2 26.9 1.5 10.4 3.6 20.8 6.4 30.9 2.4 8.6 5.3 17 9 25.1 3.6 7.9 8.2 15.3 12.9 22.5 4.4 6.7 9.2 13.1 14.2 19.3 4.9 6 10.1 11.8 15.5 17.4 5 5.1 9.8 10.5 15.5 14.8 6.2 4.6 13.3 7.7 20 11.6 8.4 4.9 16.2 10.9 25.1 14.8 7.2 3.2 15 4.9 22.5 7.1 8.5 2.4 17 5.1 25.8 6.4 9.1 1.5 18.4 1.9 27.7 1.9 9.9 0 19.8-0.5 29.6-1.9 8.9-1.3 17.8-3.3 26.4-5.8 9.5-2.8 18.7-6.2 27.7-10.3 8.2-3.7 16.3-8 23.8-12.9 8-5.2 15.3-11.2 22.5-17.4 7.6-6.5 15.4-13 21.9-20.6 7.1-8.3 12.9-17.7 18.7-27.1 5.4-8.8 10.7-17.6 14.8-27.1 4.1-9.5 7.3-19.5 9.7-29.6 2.7-11.2 4.5-22.6 5.2-34.1 0.6-10.9-0.2-21.9-1.3-32.9-1.1-10.8-2.3-21.7-5.2-32.2-2.8-10.2-7-20.1-11.6-29.6-4.1-8.7-8.9-17.1-14.2-25.1z" />
<circle id="spinner" cx="378" cy="105" r="15" />
<circle id="BigCircle" cx="378" cy="105" r="45" />
</svg>
</div>
<div class="controll">
<button id="pause">Pause</button>
<button id="play">Play</button>
<button id="reverse">Reverse</button>
<button id="cancel">Cancel</button>
</div>
Собственно всё тоже самое только вручную и адаптивно.
(function() {
let w,h, hw,hh,
circle_radius, frame_id,
angle = 0, reverse_v = 1
resize(); onresize = resize
play_button.onclick = play
pause_button.onclick = pause
cancel_button.onclick = cancel
reverse_button.onclick = reverse
function play() {
!frame_id && animate()
}
function pause() {
cancelAnimationFrame(frame_id)
frame_id = null
}
function reverse() {
reverse_v *= -1
}
function cancel() {
pause()
reverse_v = 1
angle = 0
placeSpinner(angle)
}
function placeSpinner(angle) {
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const spinner_x = hw + cos * circle_radius
const spinner_y = hh + sin * circle_radius
spinner.setAttribute('cx', spinner_x)
spinner.setAttribute('cy', spinner_y)
}
function animate() {
angle += 0.01 * reverse_v
placeSpinner(angle)
frame_id = requestAnimationFrame(animate)
}
function resize() {
w = innerWidth; h = innerHeight * 0.8
hw = w/2; hh = h/2
circle_radius = Math.min(w,h) * 0.4
const spinner_radius = circle_radius * 0.1
spinner.setAttribute('r', spinner_radius)
svg.setAttribute('width', w)
svg.setAttribute('height', h)
circle.setAttribute('cx', hw)
circle.setAttribute('cy', hh)
circle.setAttribute('r', circle_radius)
placeSpinner(angle)
}
})()
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-size: calc(1vw + 1vh + 5px);
}
.container {
height: 100vh;
width: 100vw;
display: grid;
grid-template-areas: 80vh 20vh;
justify-items: center;
align-items: center;
}
#circle {
fill: none;
stroke: #679AFF;
stroke-width: 3;
}
#spinner {
fill: tomato;
}
button {
font-size: inherit;
background-color: #679AFF;
color: #fff;
border-radius: calc(2px + 0.1vw + 0.1vh);
padding: 3vh 3vw;
font-weight: bold;
border: 0;
cursor: pointer;
outline: none;
}
<div class="container">
<svg id="svg" >
<circle id="circle" />
<circle id="spinner" />
</svg>
<div class="controll">
<button id="pause_button">Pause</button>
<button id="play_button">Play</button>
<button id="reverse_button">Reverse</button>
<button id="cancel_button">Cancel</button>
</div>
</div>