Canvas Cursor Effect
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
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) {91e.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}100document.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;