如何解决动画JS沿路径逐渐动画到特定点
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],
但是这并没有使路径上的圆圈产生动画。
任何帮助深表感谢。谢谢!
解决方法
沿着一条很长的路,我认为很难支持在点之间移动,因为您需要跟踪当前进度并将其根据缓动功能转换为实际长度。
我将您的<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,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,19,4.1,38.6,14.4
c20.7,10.9,40.7,40.6,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,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>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。