JSFiddle - React, Tailwind, and code Playground

HTML

Demonstration from <a href="http://yal.cc/angular-rotations-explained" target="_blank">blog post</a>.<br>
<canvas id="_130717" width="360" height="360">

CSS

#_130717 {
    border: 1px solid #ddd;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.7);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

JavaScript

(function _130717js() {
var can = document.getElementById("_130717"), ctx,
    cycle = function cycle(v, m, x) {
        return m + ((v = (v -= m) % (x -= m)) < 0 ? v + x : v);
    },
    arotate = function arotate(a, b, s) {
        return a + Math.max(-s, Math.min(s, cycle(b - a, -Math.PI, Math.PI)));
    }, mouseX = can.width, mouseY = 0, angle = 0.1,
    onTimer = function _130717tween() {
        hasTimer = true;
        var t = Math.atan2(mouseY, mouseX);
        angle = arotate(angle, t, 0.1);
        draw();
        if (angle != t) setTimeout(onTimer, 30); else hasTimer = false;
    }, hasTimer = false,
    draw = function _130717draw() {
        can.width += 0;
        ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
        // transform:
        ctx.save();
        ctx.translate(can.width / 2, can.height / 2);
        ctx.rotate(angle);
        // fill triangle:
        ctx.beginPath();
        ctx.moveTo(-15, -20);
        ctx.lineTo(-15, 20);
        ctx.lineTo(25, 0);
        ctx.closePath();
        ctx.fill();
        // restore transform:
        ctx.restore();
    };
if (!can || !can.getContext) return;
ctx = can.getContext("2d");
draw();
can.addEventListener("mousemove", function _130717mouseMove(e) {
    mouseX = e.offsetX - can.width / 2;
    mouseY = e.offsetY - can.height / 2;
    if (!hasTimer) onTimer();
}, false);
})();