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>