Canvas Cursor Effect
An interactive React component that creates dynamic trailing lines following the cursor, generating smooth and fluid motion trails that react in real time to cursor movement.
An interactive React component that creates dynamic trailing lines following the cursor, generating smooth and fluid motion trails that react in real time to cursor movement.
1'use client';23import useCanvasCursor from '@/hooks/use-canvasCursor';45const CanvasCursor = () => {6useCanvasCursor();78return <canvas className='pointer-events-none fixed inset-0' id='canvas' />;9};10export default CanvasCursor;11
1// @ts-nocheck23import { useEffect } from 'react';45const useCanvasCursor = () => {6function n(e) {7this.init(e || {});8}9n.prototype = {10init: function (e) {11this.phase = e.phase || 0;12this.offset = e.offset || 0;13this.frequency = e.frequency || 0.001;14this.amplitude = e.amplitude || 1;15},16update: function () {17return (18(this.phase += this.frequency),19(e = this.offset + Math.sin(this.phase) * this.amplitude)20);21},22value: function () {23return e;24},25};2627function Line(e) {28this.init(e || {});29}3031Line.prototype = {32init: function (e) {33this.spring = e.spring + 0.1 * Math.random() - 0.02;34this.friction = E.friction + 0.01 * Math.random() - 0.002;35this.nodes = [];36for (var t, n = 0; n < E.size; n++) {37t = new Node();38t.x = pos.x;39t.y = pos.y;40this.nodes.push(t);41}42},43update: function () {44var e = this.spring,45t = this.nodes[0];46t.vx += (pos.x - t.x) * e;47t.vy += (pos.y - t.y) * e;48for (var n, i = 0, a = this.nodes.length; i < a; i++)49((t = this.nodes[i]),500 < i &&51((n = this.nodes[i - 1]),52(t.vx += (n.x - t.x) * e),53(t.vy += (n.y - t.y) * e),54(t.vx += n.vx * E.dampening),55(t.vy += n.vy * E.dampening)),56(t.vx *= this.friction),57(t.vy *= this.friction),58(t.x += t.vx),59(t.y += t.vy),60(e *= E.tension));61},62draw: function () {63var e,64t,65n = this.nodes[0].x,66i = this.nodes[0].y;67ctx.beginPath();68ctx.moveTo(n, i);69for (var a = 1, o = this.nodes.length - 2; a < o; a++) {70e = this.nodes[a];71t = this.nodes[a + 1];72n = 0.5 * (e.x + t.x);73i = 0.5 * (e.y + t.y);74ctx.quadraticCurveTo(e.x, e.y, n, i);75}76e = this.nodes[a];77t = this.nodes[a + 1];78ctx.quadraticCurveTo(e.x, e.y, t.x, t.y);79ctx.stroke();80ctx.closePath();81},82};8384function onMousemove(e) {85function o() {86lines = [];87for (var e = 0; e < E.trails; e++)88lines.push(new Line({ spring: 0.4 + (e / E.trails) * 0.025 }));89}90function c(e) {91(e.touches92? ((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY))93: ((pos.x = e.clientX), (pos.y = e.clientY)),94e.preventDefault());95}96function l(e) {971 == e.touches.length &&98((pos.x = e.touches[0].pageX), (pos.y = e.touches[0].pageY));99}100(document.removeEventListener('mousemove', onMousemove),101document.removeEventListener('touchstart', onMousemove),102document.addEventListener('mousemove', c),103document.addEventListener('touchmove', c),104document.addEventListener('touchstart', l),105c(e),106o(),107render());108}109110function render() {111if (ctx.running) {112ctx.globalCompositeOperation = 'source-over';113ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);114ctx.globalCompositeOperation = 'lighter';115ctx.strokeStyle = 'hsla(' + Math.round(f.update()) + ',50%,50%,0.2)';116ctx.lineWidth = 1;117for (var e, t = 0; t < E.trails; t++) {118(e = lines[t]).update();119e.draw();120}121ctx.frame++;122window.requestAnimationFrame(render);123}124}125126function resizeCanvas() {127ctx.canvas.width = window.innerWidth - 20;128ctx.canvas.height = window.innerHeight;129}130131var ctx,132f,133e = 0,134pos = {},135lines = [],136E = {137debug: true,138friction: 0.5,139trails: 20,140size: 50,141dampening: 0.25,142tension: 0.98,143};144function Node() {145this.x = 0;146this.y = 0;147this.vy = 0;148this.vx = 0;149}150151const renderCanvas = function () {152ctx = document.getElementById('canvas').getContext('2d');153ctx.running = true;154ctx.frame = 1;155f = new n({156phase: Math.random() * 2 * Math.PI,157amplitude: 85,158frequency: 0.0015,159offset: 285,160});161document.addEventListener('mousemove', onMousemove);162document.addEventListener('touchstart', onMousemove);163document.body.addEventListener('orientationchange', resizeCanvas);164window.addEventListener('resize', resizeCanvas);165window.addEventListener('focus', () => {166if (!ctx.running) {167ctx.running = true;168render();169}170});171window.addEventListener('blur', () => {172ctx.running = true;173});174resizeCanvas();175};176177useEffect(() => {178renderCanvas();179180return () => {181ctx.running = false;182document.removeEventListener('mousemove', onMousemove);183document.removeEventListener('touchstart', onMousemove);184document.body.removeEventListener('orientationchange', resizeCanvas);185window.removeEventListener('resize', resizeCanvas);186window.removeEventListener('focus', () => {187if (!ctx.running) {188ctx.running = true;189render();190}191});192window.removeEventListener('blur', () => {193ctx.running = true;194});195};196}, []);197};198199export default useCanvasCursor;