const strokeWidth = 15;
const colors = ["#f7ff00", "#db36a4"];
const gradientPath = document.querySelector("#gradient-path");
const dotsDensity = .5 * strokeWidth;
const numberOfDots = Math.ceil(dotsDensity * gradientPath.getTotalLength() / strokeWidth);
const dotsGroup = document.querySelector(".dots");
createBasicGradient(dotsGroup);
function createBasicGradient(g) {
for (let idx = 0; idx < numberOfDots; idx++) {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
g.appendChild(circle);
gsap.set(circle, {
motionPath: {
path: gradientPath,
start: idx / numberOfDots,
end: idx / numberOfDots,
},
attr: {
cx: 0,
cy: 0,
r: .5 * strokeWidth,
fill: gsap.utils.interpolate(colors, (idx / numberOfDots))
}
});
}
}
html, body {
margin: 0;
padding: 0;
}
.container {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container svg {
height: 300px;
}
<svg width="0" height="0" viewBox="0 0 250 250">
<defs>
<path id="gradient-path" d="M36.5,91.2C-7.5,185.5,99.3,224.4,170,203.1c55-16.6,57.8-87.4,1.6-104C71,69.5,9.4,207.7,46,228.6c62.7,35.8,189.7-116,133-211"/>
<mask id="gradient-path-clip">
<use xlink:href="#gradient-path" stroke-width="15" fill="none" stroke="white"/>
</mask>
</defs>
</svg>
<div class="container">
<svg viewBox="0 0 250 250">
<g mask="url(#gradient-path-clip)" class="dots">
</g>
</svg>
</div>
<script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3/dist/MotionPathPlugin.min.js"></script>