JSFiddle - React, Tailwind, and code Playground

HTML

<canvas id="myCanvas" width="500" height="500"></canvas>

JavaScript

function RingBuffer(length) {
    this.length = length;
    this.pointer = 0;
    this.buffer = [];
}
RingBuffer.prototype.get = function(index) {
    if (index < 0) {
        index += this.length;        
    }
    return this.buffer[index];
}
RingBuffer.prototype.push = function(value) {
    this.buffer[this.pointer] = value;
    this.pointer = (this.length + this.pointer +1) % this.length;    
}
    
var c = document.getElementById("myCanvas");
var context =c.getContext("2d");

timer = window.setInterval(draw_line, 30);
function Point(x,y) {
    this.x = x;
    this.y = y;
}
Point.prototype.translateX = function(x) {
    return this.x += x;
};
Point.prototype.translateY = function(y) {
    return this.y += y;
};

function draw_line()
{
    context.fillStyle = "#000";
    context.fillRect(0, 0, c.width, c.height);

    var pointer = history.pointer;
    context.beginPath();
    context.lineWidth = 2;
    context.lineJoin = "round";
    context.strokeStyle = '#F00';
    var p1 = history.get(--pointer);
    if (p1) {
        context.moveTo(p1.x, p1.y);
        for (iteration = 0, count = 15; iteration < count; iteration += 3) {
            var p2 = history.get(--pointer);
            var p3 = history.get(--pointer);
            var p4 = history.get(--pointer);        
    
            if (p1 && p2 && p3 && p4) {
                context.bezierCurveTo(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y);
            }
            p1 = p4;
        }
    }
    context.stroke();
    context.closePath();    
    
}

var history = new RingBuffer(16);
var lastGrab = new Date();
c.addEventListener('mousemove', function() {
    now = new Date();
    if (now - lastGrab > 15) {
        history.push(new Point(event.clientX - c.offsetLeft, event.clientY - c.offsetTop)); 
        lastGrab = now;
    }    
});