Interactive Reptile Cursor

Interactive creature animation that follows mouse

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Interactive Reptile Cursor
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Interactive creature animation that follows mouse
// @author       You
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Wait for page to load
    window.addEventListener('load', function() {
        // Create and inject styles
        const style = document.createElement('style');
        style.textContent = `
            #creatureCanvas {
                position: fixed !important;
                top: 0 !important;
                left: 0 !important;
                width: 100vw !important;
                height: 100vh !important;
                z-index: 999999 !important;
                pointer-events: none !important;
                background-color: transparent !important;
            }
        `;
        document.head.appendChild(style);

        // Input handling
        var Input = {
            keys: [],
            mouse: {
                left: false,
                right: false,
                middle: false,
                x: 0,
                y: 0
            }
        };

        for (var i = 0; i < 230; i++) {
            Input.keys.push(false);
        }

        document.addEventListener("keydown", function(event) {
            Input.keys[event.keyCode] = true;
        });

        document.addEventListener("keyup", function(event) {
            Input.keys[event.keyCode] = false;
        });

        document.addEventListener("mousedown", function(event) {
            if (event.button === 0) {
                Input.mouse.left = true;
            }
            if (event.button === 1) {
                Input.mouse.middle = true;
            }
            if (event.button === 2) {
                Input.mouse.right = true;
            }
        });

        document.addEventListener("mouseup", function(event) {
            if (event.button === 0) {
                Input.mouse.left = false;
            }
            if (event.button === 1) {
                Input.mouse.middle = false;
            }
            if (event.button === 2) {
                Input.mouse.right = false;
            }
        });

        document.addEventListener("mousemove", function(event) {
            Input.mouse.x = event.clientX;
            Input.mouse.y = event.clientY;
        });

        // Create canvas
        var canvas = document.createElement("canvas");
        canvas.id = "creatureCanvas";
        document.body.appendChild(canvas);

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        // Handle window resize
        window.addEventListener('resize', function() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        });

        var ctx = canvas.getContext("2d");

        // Necessary classes
        var segmentCount = 0;

        class Segment {
            constructor(parent, size, angle, range, stiffness) {
                segmentCount++;
                this.isSegment = true;
                this.parent = parent;
                if (typeof parent.children == "object") {
                    parent.children.push(this);
                }
                this.children = [];
                this.size = size;
                this.relAngle = angle;
                this.defAngle = angle;
                this.absAngle = parent.absAngle + angle;
                this.range = range;
                this.stiffness = stiffness;
                this.updateRelative(false, true);
            }

            updateRelative(iter, flex) {
                this.relAngle = this.relAngle - 2 * Math.PI * Math.floor((this.relAngle - this.defAngle) / 2 / Math.PI + 1 / 2);
                if (flex) {
                    this.relAngle = Math.min(
                        this.defAngle + this.range / 2,
                        Math.max(
                            this.defAngle - this.range / 2,
                            (this.relAngle - this.defAngle) / this.stiffness + this.defAngle
                        )
                    );
                }
                this.absAngle = this.parent.absAngle + this.relAngle;
                this.x = this.parent.x + Math.cos(this.absAngle) * this.size;
                this.y = this.parent.y + Math.sin(this.absAngle) * this.size;
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].updateRelative(iter, flex);
                    }
                }
            }

            draw(iter) {
                ctx.beginPath();
                ctx.moveTo(this.parent.x, this.parent.y);
                ctx.lineTo(this.x, this.y);
                ctx.stroke();
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].draw(true);
                    }
                }
            }

            follow(iter) {
                var x = this.parent.x;
                var y = this.parent.y;
                var dist = Math.sqrt((this.x - x) ** 2 + (this.y - y) ** 2);
                this.x = x + this.size * (this.x - x) / dist;
                this.y = y + this.size * (this.y - y) / dist;
                this.absAngle = Math.atan2(this.y - y, this.x - x);
                this.relAngle = this.absAngle - this.parent.absAngle;
                this.updateRelative(false, true);
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].follow(true);
                    }
                }
            }
        }

        class LimbSystem {
            constructor(end, length, speed, creature) {
                this.end = end;
                this.length = Math.max(1, length);
                this.creature = creature;
                this.speed = speed;
                creature.systems.push(this);
                this.nodes = [];
                var node = end;
                for (var i = 0; i < length; i++) {
                    this.nodes.unshift(node);
                    node = node.parent;
                    if (!node.isSegment) {
                        this.length = i + 1;
                        break;
                    }
                }
                this.hip = this.nodes[0].parent;
            }

            moveTo(x, y) {
                this.nodes[0].updateRelative(true, true);
                var dist = Math.sqrt((x - this.end.x) ** 2 + (y - this.end.y) ** 2);
                var len = Math.max(0, dist - this.speed);
                for (var i = this.nodes.length - 1; i >= 0; i--) {
                    var node = this.nodes[i];
                    var ang = Math.atan2(node.y - y, node.x - x);
                    node.x = x + len * Math.cos(ang);
                    node.y = y + len * Math.sin(ang);
                    x = node.x;
                    y = node.y;
                    len = node.size;
                }
                for (var i = 0; i < this.nodes.length; i++) {
                    var node = this.nodes[i];
                    node.absAngle = Math.atan2(node.y - node.parent.y, node.x - node.parent.x);
                    node.relAngle = node.absAngle - node.parent.absAngle;
                    for (var ii = 0; ii < node.children.length; ii++) {
                        var childNode = node.children[ii];
                        if (!this.nodes.includes(childNode)) {
                            childNode.updateRelative(true, false);
                        }
                    }
                }
            }

            update() {
                this.moveTo(Input.mouse.x, Input.mouse.y);
            }
        }

        class LegSystem extends LimbSystem {
            constructor(end, length, speed, creature) {
                super(end, length, speed, creature);
                this.goalX = end.x;
                this.goalY = end.y;
                this.step = 0;
                this.forwardness = 0;
                this.reach = 0.9 * Math.sqrt((this.end.x - this.hip.x) ** 2 + (this.end.y - this.hip.y) ** 2);
                var relAngle = this.creature.absAngle - Math.atan2(this.end.y - this.hip.y, this.end.x - this.hip.x);
                relAngle -= 2 * Math.PI * Math.floor(relAngle / 2 / Math.PI + 1 / 2);
                this.swing = -relAngle + (2 * (relAngle < 0) - 1) * Math.PI / 2;
                this.swingOffset = this.creature.absAngle - this.hip.absAngle;
            }

            update(x, y) {
                this.moveTo(this.goalX, this.goalY);
                if (this.step == 0) {
                    var dist = Math.sqrt((this.end.x - this.goalX) ** 2 + (this.end.y - this.goalY) ** 2);
                    if (dist > 1) {
                        this.step = 1;
                        this.goalX = this.hip.x + this.reach * Math.cos(this.swing + this.hip.absAngle + this.swingOffset) + (2 * Math.random() - 1) * this.reach / 2;
                        this.goalY = this.hip.y + this.reach * Math.sin(this.swing + this.hip.absAngle + this.swingOffset) + (2 * Math.random() - 1) * this.reach / 2;
                    }
                } else if (this.step == 1) {
                    var theta = Math.atan2(this.end.y - this.hip.y, this.end.x - this.hip.x) - this.hip.absAngle;
                    var dist = Math.sqrt((this.end.x - this.hip.x) ** 2 + (this.end.y - this.hip.y) ** 2);
                    var forwardness2 = dist * Math.cos(theta);
                    var dF = this.forwardness - forwardness2;
                    this.forwardness = forwardness2;
                    if (dF * dF < 1) {
                        this.step = 0;
                        this.goalX = this.hip.x + (this.end.x - this.hip.x);
                        this.goalY = this.hip.y + (this.end.y - this.hip.y);
                    }
                }
            }
        }

        class Creature {
            constructor(x, y, angle, fAccel, fFric, fRes, fThresh, rAccel, rFric, rRes, rThresh) {
                this.x = x;
                this.y = y;
                this.absAngle = angle;
                this.fSpeed = 0;
                this.fAccel = fAccel;
                this.fFric = fFric;
                this.fRes = fRes;
                this.fThresh = fThresh;
                this.rSpeed = 0;
                this.rAccel = rAccel;
                this.rFric = rFric;
                this.rRes = rRes;
                this.rThresh = rThresh;
                this.children = [];
                this.systems = [];
            }

            follow(x, y) {
                var dist = Math.sqrt((this.x - x) ** 2 + (this.y - y) ** 2);
                var angle = Math.atan2(y - this.y, x - this.x);
                var accel = this.fAccel;
                if (this.systems.length > 0) {
                    var sum = 0;
                    for (var i = 0; i < this.systems.length; i++) {
                        sum += this.systems[i].step == 0;
                    }
                    accel *= sum / this.systems.length;
                }
                this.fSpeed += accel * (dist > this.fThresh);
                this.fSpeed *= 1 - this.fRes;
                this.speed = Math.max(0, this.fSpeed - this.fFric);
                var dif = this.absAngle - angle;
                dif -= 2 * Math.PI * Math.floor(dif / (2 * Math.PI) + 1 / 2);
                if (Math.abs(dif) > this.rThresh && dist > this.fThresh) {
                    this.rSpeed -= this.rAccel * (2 * (dif > 0) - 1);
                }
                this.rSpeed *= 1 - this.rRes;
                if (Math.abs(this.rSpeed) > this.rFric) {
                    this.rSpeed -= this.rFric * (2 * (this.rSpeed > 0) - 1);
                } else {
                    this.rSpeed = 0;
                }
                this.absAngle += this.rSpeed;
                this.absAngle -= 2 * Math.PI * Math.floor(this.absAngle / (2 * Math.PI) + 1 / 2);
                this.x += this.speed * Math.cos(this.absAngle);
                this.y += this.speed * Math.sin(this.absAngle);
                this.absAngle += Math.PI;
                for (var i = 0; i < this.children.length; i++) {
                    this.children[i].follow(true, true);
                }
                for (var i = 0; i < this.systems.length; i++) {
                    this.systems[i].update(x, y);
                }
                this.absAngle -= Math.PI;
                this.draw(true);
            }

            draw(iter) {
                var r = 4;
                ctx.beginPath();
                ctx.arc(this.x, this.y, r, Math.PI / 4 + this.absAngle, 7 * Math.PI / 4 + this.absAngle);
                ctx.moveTo(this.x + r * Math.cos(7 * Math.PI / 4 + this.absAngle), this.y + r * Math.sin(7 * Math.PI / 4 + this.absAngle));
                ctx.lineTo(this.x + r * Math.cos(this.absAngle) * Math.sqrt(2), this.y + r * Math.sin(this.absAngle) * Math.sqrt(2));
                ctx.lineTo(this.x + r * Math.cos(Math.PI / 4 + this.absAngle), this.y + r * Math.sin(Math.PI / 4 + this.absAngle));
                ctx.stroke();
                if (iter) {
                    for (var i = 0; i < this.children.length; i++) {
                        this.children[i].draw(true);
                    }
                }
            }
        }

        function setupLizard(size, legs, tail) {
            var s = size;
            var critter = new Creature(canvas.width / 2, canvas.height / 2, 0, s * 10, s * 2, 0.5, 16, 0.5, 0.085, 0.5, 0.3);
            var spinal = critter;

            // Neck
            for (var i = 0; i < 6; i++) {
                spinal = new Segment(spinal, s * 4, 0, 3.1415 * 2 / 3, 1.1);
                for (var ii = -1; ii <= 1; ii += 2) {
                    var node = new Segment(spinal, s * 3, ii, 0.1, 2);
                    for (var iii = 0; iii < 3; iii++) {
                        node = new Segment(node, s * 0.1, -ii * 0.1, 0.1, 2);
                    }
                }
            }

            // Torso and legs
            for (var i = 0; i < legs; i++) {
                if (i > 0) {
                    for (var ii = 0; ii < 6; ii++) {
                        spinal = new Segment(spinal, s * 4, 0, 1.571, 1.5);
                        for (var iii = -1; iii <= 1; iii += 2) {
                            var node = new Segment(spinal, s * 3, iii * 1.571, 0.1, 1.5);
                            for (var iv = 0; iv < 3; iv++) {
                                node = new Segment(node, s * 3, -iii * 0.3, 0.1, 2);
                            }
                        }
                    }
                }

                for (var ii = -1; ii <= 1; ii += 2) {
                    var node = new Segment(spinal, s * 12, ii * 0.785, 0, 8);
                    node = new Segment(node, s * 16, -ii * 0.785, 6.28, 1);
                    node = new Segment(node, s * 16, ii * 1.571, 3.1415, 2);
                    for (var iii = 0; iii < 4; iii++) {
                        new Segment(node, s * 4, (iii / 3 - 0.5) * 1.571, 0.1, 4);
                    }
                    new LegSystem(node, 3, s * 12, critter);
                }
            }

            // Tail
            for (var i = 0; i < tail; i++) {
                spinal = new Segment(spinal, s * 4, 0, 3.1415 * 2 / 3, 1.1);
                for (var ii = -1; ii <= 1; ii += 2) {
                    var node = new Segment(spinal, s * 3, ii, 0.1, 2);
                    for (var iii = 0; iii < 3; iii++) {
                        node = new Segment(node, s * 3 * (tail - i) / tail, -ii * 0.1, 0.1, 2);
                    }
                }
            }

            // Animation loop
            function animate() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.strokeStyle = "white";
                critter.follow(Input.mouse.x, Input.mouse.y);
                requestAnimationFrame(animate);
            }

            animate();
        }

        // Start the animation with random parameters
        ctx.strokeStyle = "white";
        var legNum = Math.floor(1 + Math.random() * 12);
        setupLizard(8 / Math.sqrt(legNum), legNum, Math.floor(4 + Math.random() * legNum * 8));
    });
})();