.
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.
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).
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.
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.
Released under Creative Commons Attribution 4.0 International (CC BY 4.0). Free for personal and commercial use with attribution to Coodeverse.
<!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>
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.