. Cursor Dragon Canvas JavaScript – Free Fire Breathing | Coodeverse Skip to main content

Realistic Cursor Dragon – Free Vanilla JavaScript Fire-Breathing Canvas Animation

Realistic Cursor Dragon is a free, copy-paste ready canvas JavaScript animation built with pure HTML, CSS, and vanilla JavaScript — zero external libraries. A 50-segment physics-based dragon follows your mouse cursor with smooth inverse kinematics-style body movement: the head lerps toward the pointer at 0.12 speed, and each of the 50 body segments lerps toward a target point 20px behind the previous segment at 0.25 speed, creating a fluid, serpentine trailing motion. The dragon features a fully drawn head with quadratic curve jaw, 5 scale bumps, glowing yellow eyes (rgba(255,204,0,0.9) ellipses), nostrils, and curved horns via quadraticCurveTo. The body uses per-segment createLinearGradient rendering with tapering width down to 3px at the tail.

Click anywhere to breathe fire: 10 fireball objects launch from 30px ahead of the dragon's head in the facing direction. Each fireball maintains a 10-frame trail array rendered as a smoky gradient, a radial gradient glowing core (white → orange → transparent), and shrinks at r *= 0.96 per frame over 70 frames of life. On death, each fireball spawns 12 explosion particles with random velocities and 40-frame fade-out life. A dragon roar audio clip plays on every click via the Web Audio API. The entire animation runs in a requestAnimationFrame loop at 60fps. Canvas auto-resizes on window resize.

What's Included in the Download

How the Physics Body Trail Works

The chain physics algorithm: elems[0] (head) uses elems[0].x += (pointer.x - elems[0].x) * 0.12 to lerp toward the mouse. For segments 1–49, the angle to the previous segment is found with Math.atan2(prev.y - curr.y, prev.x - curr.x). The target position is prev.x - Math.cos(angle) * 20 (20px behind). Each segment lerps at 0.25 — fast enough to stay connected, loose enough for fluid snake motion. The head orientation angle for drawDragonHead is Math.atan2(elems[1].y - elems[0].y, elems[1].x - elems[0].x).

How to Change Dragon Color Schemes

The body gradients use three dark red hex values: #4a1c1c (dark red), #2e0e0e (very dark red), #1a0a0a (near black). For a green forest dragon: replace with #1c4a1c, #0e2e0e, #0a1a0a. For a blue ice dragon: use #1c1c4a, #0e0e2e, #0a0a1a. For a purple shadow dragon: use #4a1c4a, #2e0e2e. Change eye color from rgba(255,204,0,0.9) to match. To layer the dragon over a website as a custom cursor effect: set canvas to position: fixed; pointer-events: none; z-index: 9999.

Best Use Cases

Realistic Cursor Dragon is ideal for: interactive portfolio websites, game landing pages, fantasy and RPG game sites, creative coding showcases, JavaScript canvas tutorials, hackathon demos, Halloween or themed event pages, kids entertainment websites, and as a foundation for learning Canvas API, requestAnimationFrame, particle systems, inverse kinematics chain physics, and radialGradient glow effects in vanilla JavaScript.

License

Released under Creative Commons Attribution 4.0 International (CC BY 4.0). Free for personal and commercial use with attribution to Coodeverse.

Source Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Realistic Cursor Dragon</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background: radial-gradient(circle at center, #0b0b0f, #1a0e0e);
    }
    canvas { display: block; }
  </style>
</head>
<body>
  <canvas></canvas>

  <!-- Dragon roar sound -->
  <audio id="roarSound" src="https://actions.google.com/sounds/v1/animals/dragon_growl.ogg" preload="auto"></audio>

  <script>
    const canvas = document.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const pointer = { x: canvas.width / 2, y: canvas.height / 2 };
    document.addEventListener("mousemove", e => { pointer.x = e.clientX; pointer.y = e.clientY; });

    const N = 50; 
    const elems = [];
    for (let i = 0; i < N; i++) elems.push({ x: pointer.x, y: pointer.y });

    let fireballs = [];
    let explosions = [];

    function drawDragonHead(x, y, angle) {
      ctx.save();
      ctx.translate(x, y);
      ctx.rotate(angle);

      // Head shape with jaw
      ctx.fillStyle = "#2e0e0e";
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(40, -20);
      ctx.quadraticCurveTo(50, 0, 40, 20);
      ctx.lineTo(20, 15);
      ctx.lineTo(0, 0);
      ctx.closePath();
      ctx.fill();
      ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
      ctx.shadowBlur = 10;

      // Scales on head
      ctx.fillStyle = "#4a1c1c";
      for (let i = 0; i < 5; i++) {
        ctx.beginPath();
        ctx.arc(20 + i * 5, -10 + Math.sin(i) * 5, 3, 0, Math.PI * 2);
        ctx.fill();
      }

      // Nostrils
      ctx.fillStyle = "#1a0a0a";
      ctx.beginPath();
      ctx.ellipse(35, -5, 3, 1.5, Math.PI / 4, 0, Math.PI * 2);
      ctx.ellipse(35, 5, 3, 1.5, -Math.PI / 4, 0, Math.PI * 2);
      ctx.fill();

      // Eyes with glow
      ctx.fillStyle = "rgba(255, 204, 0, 0.9)";
      ctx.beginPath();
      ctx.ellipse(25, -6, 5, 3, Math.PI / 6, 0, Math.PI * 2);
      ctx.ellipse(25, 6, 5, 3, -Math.PI / 6, 0, Math.PI * 2);
      ctx.fill();

      // Horns
      ctx.strokeStyle = "#b0a090";
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.moveTo(10, -15);
      ctx.quadraticCurveTo(0, -25, -10, -35);
      ctx.moveTo(10, 15);
      ctx.quadraticCurveTo(0, 25, -10, 35);
      ctx.stroke();

      ctx.restore();
    }

    function drawTailTip(x, y, angle, time) {
      ctx.save();
      ctx.translate(x, y);
      ctx.rotate(angle + Math.sin(time * 0.005) * 0.2);

      ctx.fillStyle = "#2e0e0e";
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(-20, -12);
      ctx.quadraticCurveTo(-30, 0, -20, 12);
      ctx.closePath();
      ctx.fill();

      // Spikes on tail
      ctx.fillStyle = "#4a1c1c";
      ctx.beginPath();
      ctx.moveTo(-10, 0);
      ctx.lineTo(-15, -5);
      ctx.lineTo(-20, 0);
      ctx.lineTo(-15, 5);
      ctx.closePath();
      ctx.fill();

      ctx.restore();
    }

    function animate(time) {
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      ctx.shadowColor = "rgba(0, 0, 0, 0.3)";
      ctx.shadowBlur = 15;

      elems[0].x += (pointer.x - elems[0].x) * 0.12;
      elems[0].y += (pointer.y - elems[0].y) * 0.12;

      for (let i = 1; i < N; i++) {
        const prev = elems[i - 1];
        const curr = elems[i];

        const dx = prev.x - curr.x;
        const dy = prev.y - curr.y;
        const angle = Math.atan2(dy, dx);

        const targetX = prev.x - Math.cos(angle) * 20;
        const targetY = prev.y - Math.sin(angle) * 20;

        curr.x += (targetX - curr.x) * 0.25;
        curr.y += (targetY - curr.y) * 0.25;

        const width = Math.max(3, 16 - i * 0.3);
        const gradient = ctx.createLinearGradient(curr.x, curr.y, prev.x, prev.y);
        gradient.addColorStop(0, "#4a1c1c");
        gradient.addColorStop(0.5, "#2e0e0e");
        gradient.addColorStop(1, "#1a0a0a");

        ctx.strokeStyle = gradient;
        ctx.lineWidth = width;
        ctx.beginPath();
        ctx.moveTo(prev.x, prev.y);
        ctx.lineTo(curr.x, curr.y);
        ctx.stroke();

        // Scales along body
        if (i % 3 === 0) {
          ctx.fillStyle = `rgba(74, 28, 28, ${1 - i / N})`;
          ctx.beginPath();
          ctx.arc(curr.x, curr.y, width / 2, 0, Math.PI * 2);
          ctx.fill();
        }

        if (i === N - 1) {
          drawTailTip(curr.x, curr.y, angle, time);
        }
      }

      // Head
      const dx = elems[1].x - elems[0].x;
      const dy = elems[1].y - elems[0].y;
      const headAngle = Math.atan2(dy, dx);
      drawDragonHead(elems[0].x, elems[0].y, headAngle);

      // Fireballs with glowing core
      fireballs.forEach((f, index) => {
        f.trail.push({ x: f.x, y: f.y, r: f.r });
        if (f.trail.length > 10) f.trail.shift();

        // Draw smoky trail
        f.trail.forEach((t, i) => {
          const alpha = (1 - i / 10) * 0.5;
          ctx.fillStyle = `rgba(255, ${Math.floor(220 - i * 15)}, 0, ${alpha})`;
          ctx.beginPath();
          ctx.arc(t.x, t.y, t.r * (1 - i / 12), 0, Math.PI * 2);
          ctx.fill();
        });

        // Glowing fireball
        const gradient = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, f.r);
        gradient.addColorStop(0, "rgba(255, 255, 255, 0.9)");
        gradient.addColorStop(0.5, "rgba(255, 100, 0, 0.7)");
        gradient.addColorStop(1, "rgba(255, 0, 0, 0)");
        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2);
        ctx.fill();

        f.x += f.vx;
        f.y += f.vy;
        f.life--;
        f.r *= 0.96;

        if (f.life <= 0 || f.r < 2) {
          for (let i = 0; i < 12; i++) {
            explosions.push({
              x: f.x,
              y: f.y,
              vx: (Math.random() - 0.5) * 8,
              vy: (Math.random() - 0.5) * 8,
              r: Math.random() * 5 + 2,
              life: 40
            });
          }
          fireballs.splice(index, 1);
        }
      });

      // Explosions with varied particles
      explosions.forEach((p, index) => {
        p.x += p.vx;
        p.y += p.vy;
        p.life--;
        p.r *= 0.98;

        const gradient = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.r);
        gradient.addColorStop(0, `rgba(255, ${150 + Math.random() * 100}, 0, ${p.life / 40})`);
        gradient.addColorStop(1, "rgba(255, 0, 0, 0)");
        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
        ctx.fill();

        if (p.life <= 0) explosions.splice(index, 1);
      });

      requestAnimationFrame(animate);
    }

    animate(0);

    // Fire breath + roar
    document.addEventListener("click", () => {
      const roar = document.getElementById("roarSound");
      roar.currentTime = 0;
      roar.play();

      const dx = elems[1].x - elems[0].x;
      const dy = elems[1].y - elems[0].y;
      const angle = Math.atan2(dy, dx);

      for (let i = 0; i < 10; i++) {
        fireballs.push({
          x: elems[0].x + Math.cos(angle) * 30,
          y: elems[0].y + Math.sin(angle) * 30,
          vx: Math.cos(angle) * (6 + Math.random() * 4) + (Math.random() - 0.5) * 3,
          vy: Math.sin(angle) * (6 + Math.random() * 4) + (Math.random() - 0.5) * 3,
          r: 20,
          life: 70,
          trail: []
        });
      }
    });

    window.addEventListener("resize", () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    });
  </script>
</body>
</html>

Frequently Asked Questions

Common questions about the free Realistic Cursor Dragon JavaScript canvas animation.

Yes — 100% free under Creative Commons Attribution 4.0. No sign-up, no payment. Click Download Project and use in personal or commercial projects with attribution to Coodeverse.

Use a chain of 50 segment objects. The head lerps toward the pointer: elems[0].x += (pointer.x - elems[0].x) * 0.12. Each segment finds the angle to the previous segment with Math.atan2, computes a target 20px behind it, then lerps at 0.25. The head draw angle uses Math.atan2(elems[1].y - elems[0].y, elems[1].x - elems[0].x). A requestAnimationFrame loop updates all positions every frame.

On click, 10 fireball objects are pushed with position (30px ahead of head), velocity (cos/sin(angle) * 6–10), r: 20, life: 70, and an empty trail array. Each frame: current position is pushed to trail (capped at 10), trail draws with decreasing alpha, fireball draws as a radialGradient (white → orange → transparent). Each frame: r *= 0.96, life--. On death, 12 explosion particles spawn with random velocities and 40-frame fade-out.

ctx.save(), ctx.translate(x,y), ctx.rotate(angle) positions the head. The jaw uses moveTo/lineTo/quadraticCurveTo. Scales are 5 small arc circles. Eyes are ctx.ellipse() in yellow (rgba(255,204,0,0.9)). Nostrils are two small ellipses. Horns use quadraticCurveTo curving up and down. ctx.restore() resets the transform.

The body uses three colors: #4a1c1c, #2e0e0e, #1a0a0a. For a green dragon: #1c4a1c, #0e2e0e, #0a1a0a. For a blue/ice dragon: #1c1c4a, #0e0e2e, #0a0a1a. For purple: #4a1c4a, #2e0e2e. Change eye color from rgba(255,204,0,0.9) to match. To embed as a custom cursor overlay, set canvas to position: fixed; pointer-events: none; z-index: 9999.

Copy the <canvas>, <audio> element, and <script> block into your page. Add this CSS to the canvas: position: fixed; top: 0; left: 0; pointer-events: none; z-index: 9999;. The pointer-events: none lets clicks pass through the canvas to your page content. The dragon renders above everything, following the cursor across your entire site.

Browse the full collection at coodeverse.com/projects — VORTEX Interactive Canvas (800 physics particles), Particle Fireworks, Cosmos Star Animation, Word Typing Adventure, Holographic Login and more. All 100% free with complete HTML CSS JavaScript source code.