- var w=400,h=400;
body
  main 
    svg#svg(width=w, height=h)
      circle#circle.circles(cx=0, cy=0, r=0, fill="#FD554F")
      circle#circle.circles(cx=0, cy=0, r=0, fill="#FD554F")
      circle#circle.circles(cx=0, cy=0, r=0, fill="#FD554F")
      circle#circle.circles(cx=0, cy=0, r=0, fill="#FD554F")
      circle#circle.circles(cx=0, cy=0, r=0, fill="#FD554F")
      text.text(
        x=w / 2,
        y=h / 2,
        text-anchor="middle",
        stroke="red",
        stroke-width="1px",
        alignment-baseline="middle",
        style="opacity: 0",
        dy="3px"
      ) 😍
      text.text(
        x=w / 2,
        y=h / 2,
        text-anchor="middle",
        stroke="red",
        stroke-width="1px",
        alignment-baseline="middle",
        style="opacity: 0",
        dy="3px"
      ) 👌
      text.text(
        x=w / 2,
        y=h / 2,
        text-anchor="middle",
        stroke="red",
        stroke-width="1px",
        alignment-baseline="middle",
        style="opacity: 0",
        dy="3px"
      ) 😳
      text.text(
        x=w / 2,
        y=h / 2,
        text-anchor="middle",
        stroke="red",
        stroke-width="1px",
        alignment-baseline="middle",
        style="opacity: 0",
        dy="3px"
      ) 👀
      text.text(
        x=w / 2,
        y=h / 2,
        text-anchor="middle",
        stroke="red",
        stroke-width="1px",
        alignment-baseline="middle",
        style="opacity: 0",
        dy="3px"
      ) 🤩
      circle#menu(
        cx=w / 2,
        cy=h / 2,
        fill="#FD554F",
        r=0,
        style="cursor: pointer"
      )
      g#icon(
        transform="translate(188.5,188.5)",
        style="opacity: 0; transform-origin: 7px 7px; cursor: pointer"
      )
        path(
          d="M12 5.5V19.5",
          stroke="#fff",
          stroke-width="3",
          stroke-linecap="round",
          stroke-linejoin="round"
        )
        path(
          d="M5 12.5H19",
          stroke="#fff",
          stroke-width="3",
          stroke-linecap="round",
          stroke-linejoin="round"
        )
  #desc Draw Some Circles
  br
  #equation1.eq
  #equation2.eq
  #options
    button#left.button <
    #step 0
    button#right.button >
View Compiled
body {
  background: #192734;
  display: flex;
  font-family: Consolas, monospace;
  justify-content: center;
  flex-direction: column;
}
#svg {
  margin: 0 auto;
  display: block;
  background: #192734;
}
#options {
  margin-top: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  #step {
    margin: 0 5px;
    color: white;
  }
}
#desc,
.eq {
  color: white;
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  width: 70%;
  margin: 0 auto;
  display: block;
}
View Compiled
const $ = (x) => document.querySelector(x);

let circle = $("#circle"),
  step = $("#step"),
  eq1 = $("#equation1"),
  eq2 = $("#equation2"),
  desc = $("#desc"),
  w = 400,
  h = 400,
  stepNo = 0;

function clamp(num, min, max) {
  return Math.min(Math.max(num, min), max);
}

document.onkeydown = checkKey;

function checkKey(e) {
  e = e || window.event;
  if (e.keyCode == "37") {
    stepNo--;
    stepNo = clamp(stepNo, 0, 4);
    step.innerText = stepNo;
    animate();
  } else if (e.keyCode == "39") {
    stepNo++;
    stepNo = clamp(stepNo, 0, 4);
    step.innerText = stepNo;
    animate();
  }
}

$("#left").addEventListener("click", () => {
  stepNo--;
  stepNo = clamp(stepNo, 0, 4);
  step.innerText = stepNo;
  animate();
});

$("#right").addEventListener("click", () => {
  stepNo++;
  stepNo = clamp(stepNo, 0, 4);
  step.innerText = stepNo;
  animate();
});

gsap.defaults({
  ease: Elastic.easeOut.config(0.1, 0.5),
  duration: 0.9
});
gsap.to(".circles", { r: 15 });
animate();

function eqFadeIn() {
  gsap.fromTo(
    ".eq",
    { opacity: 0, y: "100%" },
    {
      opacity: 1,
      y: "0%"
    }
  );
}

function animate() {
  switch (stepNo) {
    case 0: {
      gsap.to(".circles", { cx: 0, cy: 0, stagger: 0.1 });
      desc.innerText = "Draw Some Circles";
      eqFadeIn();
      eq1.innerText = "(use left and right arrows to goto next step)👇";
      eq2.innerText = "";
      break;
    }
    case 1: {
      gsap.to(".circles", { cx: w / 2, cy: h / 2, stagger: 0.1 });
      desc.innerText = "Center the Circles";
      eqFadeIn();
      eq1.innerText = "x = width/2";
      eq2.innerText = "y = height/2";
      break;
    }
    case 2: {
      let angStep = (2 * Math.PI) / 5;
      gsap.to(".circles", {
        cx: (idx) => w / 2 + Math.cos(idx * angStep),
        cy: (idx) => h / 2 + Math.sin(idx * angStep),
        stagger: 0.1
      });
      desc.innerText = "Add sin and cos with angles 0 to 2*PI for each circle";
      eqFadeIn();
      eq1.innerText = "x = width/2 + Math.cos(angle)";
      eq2.innerText = "y = height/2 + Math.sin(angle)";
      break;
    }
    case 3: {
      let angStep = (2 * Math.PI) / 5;
      gsap.to("#menu", { r: 0 });
      gsap.to("#icon", { rotate: 0, opacity: 0 });
      gsap.to(".text", { opacity: 0 });
      gsap.to(".circles", {
        cx: (idx) => w / 2 + 50 * Math.cos(idx * angStep),
        cy: (idx) => h / 2 + 50 * Math.sin(idx * angStep),
        stagger: 0.1
      });
      desc.innerText = "Add a multiplier/radius";
      eqFadeIn();
      eq1.innerText = "x = width/2 + 50*Math.cos(angle)";
      eq2.innerText = "y = height/2 + 50*Math.sin(angle)";
      break;
    }
    case 4: {
      let angStep = (2 * Math.PI) / 5;
      gsap.to("#menu", { r: 25 });
      gsap.to("#icon", { rotate: 360 - 45, opacity: 1 });
      gsap.to(".circles", {
        cx: (idx) => w / 2 + 70 * Math.cos(idx * angStep),
        cy: (idx) => h / 2 + 70 * Math.sin(idx * angStep),
        stagger: 0.1
      });
      gsap.to(".text", {
        opacity: 1,
        x: (idx) => 70 * Math.cos(idx * angStep),
        y: (idx) => 70 * Math.sin(idx * angStep),
        stagger: 0.1
      });
      desc.innerText =
        "Tada 🎉 You've got a radial menu! ✨ Try clicking on the center button";
      eqFadeIn();
      eq1.innerText = "x = width/2 + 70*Math.cos(angle)";
      eq2.innerText = "y = height/2 + 70*Math.sin(angle)";
      break;
    }
  }
}

function toggleOpen() {
  isOpen = !isOpen;
  if (isOpen) {
    let angStep = (2 * Math.PI) / 5;
    gsap.to("#icon", { rotate: 360 - 45, opacity: 1 });
    gsap.to(".circles", {
      cx: (idx) => w / 2 + 70 * Math.cos(idx * angStep),
      cy: (idx) => h / 2 + 70 * Math.sin(idx * angStep),
      stagger: 0.05
    });
    gsap.to(".text", {
      opacity: 1,
      x: (idx) => 70 * Math.cos(idx * angStep),
      y: (idx) => 70 * Math.sin(idx * angStep),
      stagger: 0.05
    });
    eqFadeIn();
    eq1.innerText = "x = width/2 + 70*Math.cos(angle)";
    eq2.innerText = "y = height/2 + 70*Math.sin(angle)";
  } else {
    gsap.to("#icon", { rotate: 360, opacity: 1 });
    gsap.to(".circles", {
      cx: (idx) => w / 2,
      cy: (idx) => h / 2,
      stagger: 0.05
    });
    gsap.to(".text", {
      x: (idx) => 0,
      y: (idx) => 0,
      stagger: 0.05
    });
    eqFadeIn();
    eq1.innerText = "x = width/2";
    eq2.innerText = "y = height/2";
  }
}

let isOpen = true;
$("#menu").addEventListener("click", () => {
  toggleOpen();
});
$("#icon").addEventListener("click", () => {
  toggleOpen();
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.co/gsap@3/dist/gsap.min.js