* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  display: grid;
  place-items: center;
}

svg {
  width: 75vmin;
  height: 75vmin;
  overflow: visible;
}
import { SVG } from "https://cdn.skypack.dev/@svgdotjs/svg.js";
import { spline } from "https://cdn.skypack.dev/@georgedoescode/generative-utils";
import gsap from "https://cdn.skypack.dev/gsap@3.7.0";

gsap.registerPlugin(DrawSVGPlugin);

const width = 200;
const height = 200;

const svg = SVG().viewbox(0, 0, width, height).addTo("body");

function mulberry32(a) {
  return function () {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    var t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function lerp(position, target, amt) {
  return {
    x: (position.x += (target.x - position.x) * amt),
    y: (position.y += (target.y - position.y) * amt)
  };
}

function animate() {
  let seed = Math.random() * 1000;
  svg.clear();
  const random = mulberry32(seed);

  const radius = width / 2;

  const numPoints = 8;
  const basePoints = [];
  const lerpPoints = [];

  const pointElements = [];

  const center = {
    x: width / 2,
    y: height / 2
  };

  const angleStep = (Math.PI * 2) / numPoints;

  for (let i = 1; i <= numPoints; i++) {
    const angle = i * angleStep;
    const point = {
      x: center.x + Math.cos(angle) * radius,
      y: center.y + Math.sin(angle) * radius
    };

    basePoints.push(point);
    lerpPoints.push(lerp({ ...point }, center, 0.375 * random()));
  }

  basePoints.forEach((p) => {
    pointElements.push(
      svg
        .circle(8)
        .cx(p.x)
        .cy(p.y)
        .fill("#1D1934")
        .attr("class", "base-point")
        .scale(0).node
    );
  });

  lerpPoints.forEach((p) => {
    svg
      .circle(8)
      .cx(p.x)
      .cy(p.y)
      .fill("#1D1934")
      .attr("class", "lerp-point")
      .opacity(0);
  });

  const path = spline(lerpPoints, 1, true);

  svg
    .path(path)
    .fill("none")
    .stroke({
      width: 2,
      color: "#1D1934"
    })
    .fill("none");

  gsap.to(".base-point", {
    scale: 1,
    stagger: 0.075,
    transformOrigin: "center"
  });

  for (let i = 0; i < pointElements.length; i++) {
    gsap.to(pointElements[i], {
      attr: {
        cx: lerpPoints[i].x,
        cy: lerpPoints[i].y
      },
      delay: 2
    });
  }

  gsap.set("path", {
    drawSVG: 0
  });

  gsap.to("path", {
    duration: 1,
    drawSVG: "100%",
    delay: 3
  });

  gsap.to("path", {
    stroke: "none",
    fill: "#1D1934",
    delay: 4
  });

  gsap.to(".base-point", {
    scale: 0,
    delay: 4
  });

  gsap.to("path", {
    opacity: 0,
    delay: 6
  });

  setTimeout(() => {
    animate();
  }, 7000);
}

animate();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://assets.codepen.io/16327/DrawSVGPlugin3.min.js