JSFiddle - React, Tailwind, and code Playground

by Sergei Sokolov

HTML

<canvas width="300" height="300"></canvas>

CSS

canvas {border:1px solid #CCC}

JavaScript

/**
 * для вопроса https://qna.habr.com/q/1031618
 * Как нарисовать неправильный многоугольник?
 */
const ctx = document.querySelector('canvas')
  .getContext('2d');
  
ctx.translate(150, 150); // (0, 0) в центр канвы

// стиль линий
ctx.strokeStyle = "darkslategrey";
ctx.lineWidth = 2;
ctx.lineJoin = 'round';

class Arm {
  constructor(length) {
    this.length = length;
    this.a = Math.random() * Math.PI * 2;
    this.da = -0.5 + Math.random();
    this.scale = 7;
  }
    
  render(dt) {
    this.a += dt * this.da / 5e2; // пересчитать угол
    ctx.rotate(this.a); // повернуть
    ctx.translate(this.scale * this.length, 0); // отъехать
    ctx.lineTo(0, 0); // нарисовать плечо
  }
}

const lengths = [1, 2, 5, 8, 11]; // длины плечей
const arms = lengths.map((len) => new Arm(len));

const tail = [];
const tailLimit = 1e3; // max length

let prev = 0; // время предыдущего кадра
const render = (ts) => {
  const dt = prev ? ts - prev : 0; // ск. времени прошло между кадрами
  prev = ts;
  
  ctx.clearRect(-150, -150, 300, 300); // очистить кадр
  ctx.save(); // запомнить несмещенное состояние
  
  ctx.beginPath();
  ctx.moveTo(0, 0);
  arms.forEach((arm) => arm.render(dt));
  ctx.stroke(); 
  addTailPoint();
  
  ctx.restore(); // восстановить сохранённое состояние
  
  renderTail();
 
  requestAnimationFrame(render); // запросить рисование след. кадра
}

const addTailPoint = () => {
  const tailPoint = ctx.getTransform()
    .inverse().translate(150, 150).inverse()
    .transformPoint(new DOMPoint(0, 0));
  tail.unshift(tailPoint);
  tail.length = Math.min(tailLimit, tail.length);
}

const renderTail = () => {
  ctx.save();
  
  ctx.strokeStyle = 'rgb(0, 0, 0, 0.8)';
  ctx.lineWidth = 0.5;
  
  ctx.beginPath();
  tail.forEach((p) => ctx.lineTo(p.x, p.y));
  ctx.stroke();

  ctx.restore();
}

requestAnimationFrame(render);