https://motioncanvas.online/preview/magnetic-fluid-grid
Scroll Down

Loading Canvas Shader...

Next-Gen Scroll Mechanics

Experience Beautiful Ambient Depth

This browser window is simulating a production webpage. As you scroll down, the ambient Magnetic Fluid Grid background dynamically zooms and pulls focus in real-time, creating beautiful, cinematic parallax depth.

Built for Ultra Performance

Modern web layouts require buttery-smooth animations. Our styles run completely on the GPU, avoiding CPU reflow and main-thread layout jank.

Focus Pull Parallax

Scroll depth controls blur intensity and lens scaling simultaneously to guide focus elegantly.

GPU-Accelerated

Uses hardware transforms and native filters, optimized for stable 120fps scrolling.

Highly Customizable

Adjust speed, maximum blur limit, zoom multipliers, and filters in real-time.

© 2026 MOTIONCANVAS. ALL RIGHTS RESERVED.ACTIVE BACKGROUND: MAGNETIC FLUID GRID

Magnetic Fluid Grid

🔵 Interactive

A highly responsive minimalist geometric grid that warps, bends, and reacts under the user's cursor using custom spring-mass physics and high-performance distance calculation.

#interactive#spring-physics#grid#fluid-ripple#magnetic-repulsion#canvas
Advertisement
Sponsor Advertisement
728 × 90 | Responsive Banner

Responsive Horizontal Leaderboard

Slot ID: ca-pub-detail-below-preview

Google AdSense Placeholder

Interactive Settings

Motion PresetsCustom Tuning
Animation Speed1.0x
Blur (Aesthetic diffusion)0px
Layer Opacity100%
Scale Zoom1.0x
Rotation Angle0°
Advertisement
Sponsor Advertisement
300 × 250 | Rectangle

Medium Rectangle Block

Slot ID: ca-pub-detail-after-customization

Google AdSense Placeholder

Quick Copy Actions

Download File Packages

Keyboard Shortcuts

Space Play/Pause
F Fullscreen
R Reset Slider
C Copy Code
Advertisement
Sponsor Advertisement
728 × 90 | Responsive Banner

Responsive Horizontal Leaderboard

Slot ID: ca-pub-detail-before-related

Google AdSense Placeholder

More in Interactive & Cursor-Driven

Magnetic Fluid Grid - HTML5 Canvas & JavaScript Interactive & Cursor-Driven background for React & Tailwind

Integrate the Magnetic Fluid Grid directly into your website. This asset is rendered using HTML5 Canvas 2D render loop. It is optimized for zero layout-shifts and runs with high-performance hardware-accelerated processing.

Performance Specifications

  • Render Mode: INTERACTIVE (HTML5 Canvas & JavaScript)
  • Fluidity: Locked at 60fps dynamic loop
  • Bundle Footprint: Zero external NPM dependencies
  • SEO Indexing status: 100% Crawlable static semantic HTML

🛠️ Integration Capabilities

Our templates expose inline design tokens like --color-1 and --color-2 for infinite color palettes. This template is designed to fit inside hero elements, full-screen landing pages, and interactive presentation cards.

Technical Code Reference & Syntaxes for Googlebot & Crawlers

The snippets below display the direct, unminified source code utilized for rendering this background.

HTML & Inline CSS Snippet (Vanilla)

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Magnetic Fluid Grid</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
      overflow: hidden;
      background: #09090b;
    }
    
    .magnetic-grid-container {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: #08090c;
      overflow: hidden;
    }
    #canvas-magnetic-fluid-grid {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      border: none;
      display: block;
    }
  </style>
</head>
<body>

  <div class="magnetic-grid-container">
    <canvas id="canvas-magnetic-fluid-grid"></canvas>
  </div>
  <script>
  (function() {
    const canvas = document.getElementById('canvas-magnetic-fluid-grid');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;
  
    let cols = 0;
    let rows = 0;
    let nodes = [];
    const spacing = 42;
  
    const springK = 0.045;
    const friction = 0.83;
    const repulsionRadius = 160;
    const repulsionStrength = 4.2;
  
    let mouseX = -1000;
    let mouseY = -1000;
    let targetMouseX = -1000;
    let targetMouseY = -1000;
  
    function initNodes() {
      const width = canvas.width;
      const height = canvas.height;
      
      cols = Math.ceil(width / spacing) + 1;
      rows = Math.ceil(height / spacing) + 1;
      nodes = [];
  
      const offsetX = (width - (cols - 1) * spacing) / 2;
      const offsetY = (height - (rows - 1) * spacing) / 2;
  
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const ox = offsetX + c * spacing;
          const oy = offsetY + r * spacing;
          nodes.push({
            x: ox,
            y: oy,
            ox: ox,
            oy: oy,
            vx: 0,
            vy: 0,
            disp: 0,
            col: c,
            row: r
          });
        }
      }
    }
  
    function resize() {
      const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
      const width = rect ? rect.width : window.innerWidth;
      const height = rect ? rect.height : window.innerHeight;
      
      if (canvas.width !== width || canvas.height !== height) {
        canvas.width = width;
        canvas.height = height;
        initNodes();
      }
    }
  
    function onMouseMove(e) {
      const rect = canvas.getBoundingClientRect();
      targetMouseX = e.clientX - rect.left;
      targetMouseY = e.clientY - rect.top;
    }
  
    function onMouseLeave() {
      targetMouseX = -1000;
      targetMouseY = -1000;
    }
  
    function onTouchMove(e) {
      if (e.touches.length > 0) {
        const touch = e.touches[0];
        const rect = canvas.getBoundingClientRect();
        targetMouseX = touch.clientX - rect.left;
        targetMouseY = touch.clientY - rect.top;
      }
    }
  
    function onTouchEnd() {
      targetMouseX = -1000;
      targetMouseY = -1000;
    }
  
    window.addEventListener('resize', resize);
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseleave', onMouseLeave);
    window.addEventListener('touchmove', onTouchMove, { passive: true });
    window.addEventListener('touchstart', onTouchMove, { passive: true });
    window.addEventListener('touchend', onTouchEnd);
    window.addEventListener('touchcancel', onTouchEnd);
  
    let observer;
    if (canvas.parentNode) {
      observer = new MutationObserver(resize);
      observer.observe(canvas.parentNode, { attributes: true });
    }
  
    resize();
  
    function cleanup() {
      window.removeEventListener('resize', resize);
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseleave', onMouseLeave);
      window.removeEventListener('touchmove', onTouchMove);
      window.removeEventListener('touchstart', onTouchMove);
      window.removeEventListener('touchend', onTouchEnd);
      window.removeEventListener('touchcancel', onTouchEnd);
      if (observer) {
        observer.disconnect();
      }
    }
  
    function update() {
      if (targetMouseX === -1000) {
        mouseX = -1000;
        mouseY = -1000;
      } else {
        if (mouseX === -1000) {
          mouseX = targetMouseX;
          mouseY = targetMouseY;
        } else {
          mouseX += (targetMouseX - mouseX) * 0.16;
          mouseY += (targetMouseY - mouseY) * 0.16;
        }
      }
  
      const rSq = repulsionRadius * repulsionRadius;
  
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
  
        const ax = (node.ox - node.x) * springK;
        const ay = (node.oy - node.y) * springK;
  
        node.vx += ax;
        node.vy += ay;
  
        if (mouseX !== -1000) {
          const dx = node.x - mouseX;
          const dy = node.y - mouseY;
          const distSq = dx * dx + dy * dy;
  
          if (distSq < rSq) {
            const dist = Math.sqrt(distSq) || 0.001;
            const force = (repulsionRadius - dist) / repulsionRadius;
            const push = force * force * force * repulsionStrength;
            
            node.vx += (dx / dist) * push;
            node.vy += (dy / dist) * push;
          }
        }
  
        node.vx *= friction;
        node.vy *= friction;
        node.x += node.vx;
        node.y += node.vy;
  
        node.disp = Math.hypot(node.x - node.ox, node.y - node.oy);
      }
    }
  
    function draw() {
      ctx.fillStyle = '#08090b';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
  
      if (mouseX !== -1000) {
        ctx.beginPath();
        const spotlight = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, repulsionRadius * 1.8);
        spotlight.addColorStop(0, 'rgba(14, 165, 233, 0.08)');
        spotlight.addColorStop(0.5, 'rgba(99, 102, 241, 0.02)');
        spotlight.addColorStop(1, 'rgba(0, 0, 0, 0)');
        ctx.fillStyle = spotlight;
        ctx.arc(mouseX, mouseY, repulsionRadius * 1.8, 0, Math.PI * 2);
        ctx.fill();
      }
  
      const inactiveLinesPath = new Path2D();
      const activeLines = [];
  
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const idx = r * cols + c;
          const node = nodes[idx];
  
          if (c < cols - 1) {
            const rightNode = nodes[idx + 1];
            if (node.disp < 0.25 && rightNode.disp < 0.25) {
              inactiveLinesPath.moveTo(node.ox, node.oy);
              inactiveLinesPath.lineTo(rightNode.ox, rightNode.oy);
            } else {
              activeLines.push([node, rightNode]);
            }
          }
  
          if (r < rows - 1) {
            const bottomNode = nodes[idx + cols];
            if (node.disp < 0.25 && bottomNode.disp < 0.25) {
              inactiveLinesPath.moveTo(node.ox, node.oy);
              inactiveLinesPath.lineTo(bottomNode.ox, bottomNode.oy);
            } else {
              activeLines.push([node, bottomNode]);
            }
          }
        }
      }
  
      ctx.strokeStyle = 'rgba(71, 85, 105, 0.09)';
      ctx.lineWidth = 1;
      ctx.stroke(inactiveLinesPath);
  
      for (let i = 0; i < activeLines.length; i++) {
        const [n1, n2] = activeLines[i];
        const avgDisp = (n1.disp + n2.disp) / 2;
        const t = Math.min(avgDisp / 48, 1.0);
  
        ctx.beginPath();
        ctx.moveTo(n1.x, n1.y);
        ctx.lineTo(n2.x, n2.y);
        
        const r_val = Math.floor(71 + t * (14 - 71));
        const g_val = Math.floor(85 + t * (165 - 85));
        const b_val = Math.floor(105 + t * (233 - 105));
        const alpha = 0.09 + t * 0.42;
  
        ctx.strokeStyle = "rgba(" + r_val + ", " + g_val + ", " + b_val + ", " + alpha + ")";
        ctx.lineWidth = 1 + t * 1.5;
        ctx.stroke();
      }
  
      const inactiveNodesPath = new Path2D();
      const activeNodes = [];
  
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        if (node.disp < 0.25) {
          inactiveNodesPath.rect(node.ox - 1, node.oy - 1, 2, 2);
        } else {
          activeNodes.push(node);
        }
      }
  
      ctx.fillStyle = 'rgba(100, 116, 139, 0.15)';
      ctx.fill(inactiveNodesPath);
  
      for (let i = 0; i < activeNodes.length; i++) {
        const node = activeNodes[i];
        const t = Math.min(node.disp / 48, 1.0);
  
        ctx.beginPath();
        ctx.arc(node.x, node.y, 1.5 + t * 2, 0, Math.PI * 2);
        ctx.fillStyle = "rgba(56, 189, 248, " + (0.25 + t * 0.75) + ")";
        ctx.fill();
  
        if (t > 0.35) {
          ctx.beginPath();
          ctx.arc(node.x, node.y, 3 + t * 6, 0, Math.PI * 2);
          ctx.strokeStyle = "rgba(56, 189, 248, " + ((t - 0.35) * 0.22) + ")";
          ctx.lineWidth = 1;
          ctx.stroke();
        }
      }
    }
  
    function render() {
      if (!canvas.isConnected) {
        cleanup();
        return;
      }
  
      update();
      draw();
      requestAnimationFrame(render);
    }
  
    requestAnimationFrame(render);
  })();
  </script>

  
  <script>
    // Throttled interactive cursor coordinates mapping
    (function() {
      document.documentElement.style.setProperty('--mouse-x', '0');
      document.documentElement.style.setProperty('--mouse-y', '0');

      let targetX = 0, targetY = 0;
      let currentX = 0, currentY = 0;

      window.addEventListener('mousemove', (e) => {
        targetX = (e.clientX / window.innerWidth) - 0.5;
        targetY = (e.clientY / window.innerHeight) - 0.5;
      });

      window.addEventListener('mouseleave', () => {
        targetX = 0;
        targetY = 0;
      });

      function update() {
        currentX += (targetX - currentX) * 0.08;
        currentY += (targetY - currentY) * 0.08;
        
        document.documentElement.style.setProperty('--mouse-x', currentX.toFixed(4));
        document.documentElement.style.setProperty('--mouse-y', currentY.toFixed(4));
        
        requestAnimationFrame(update);
      }
      requestAnimationFrame(update);
    })();
  </script>
  
</body>
</html>

React Component Wrapper (TSX)

import React, { useEffect, useRef } from 'react';

export default function MagneticFluidGridBackground() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let targetX = 0, targetY = 0;
    let currentX = 0, currentY = 0;
    let frameId: number;

    const handleMouseMove = (e: MouseEvent) => {
      targetX = (e.clientX / window.innerWidth) - 0.5;
      targetY = (e.clientY / window.innerHeight) - 0.5;
    };

    const handleMouseLeave = () => {
      targetX = 0;
      targetY = 0;
    };

    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseleave', handleMouseLeave);

    const updateCoordinates = () => {
      currentX += (targetX - currentX) * 0.08;
      currentY += (targetY - currentY) * 0.08;

      if (containerRef.current) {
        containerRef.current.style.setProperty('--mouse-x', currentX.toFixed(4));
        containerRef.current.style.setProperty('--mouse-y', currentY.toFixed(4));
      }
      frameId = requestAnimationFrame(updateCoordinates);
    };
    
    frameId = requestAnimationFrame(updateCoordinates);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseleave', handleMouseLeave);
      cancelAnimationFrame(frameId);
    };
  }, []);

  return (
    <div 
      ref={containerRef} 
      style={{ width: '100%', height: '100%', position: 'relative', overflow: 'hidden' }}
    >
      <style dangerouslySetInnerHTML={{ __html: `
        .magnetic-grid-container {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background: #08090c;
          overflow: hidden;
        }
        #canvas-magnetic-fluid-grid {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          border: none;
          display: block;
        }
      ` }} />
      
      {/* HTML Structure */}
      <div 
        style={{ width: '100%', height: '100%' }}
        dangerouslySetInnerHTML={{ __html: `
          <div class="magnetic-grid-container">
            <canvas id="canvas-magnetic-fluid-grid"></canvas>
          </div>
          <script>
          (function() {
            const canvas = document.getElementById('canvas-magnetic-fluid-grid');
            if (!canvas) return;
            const ctx = canvas.getContext('2d');
            if (!ctx) return;
          
            let cols = 0;
            let rows = 0;
            let nodes = [];
            const spacing = 42;
          
            const springK = 0.045;
            const friction = 0.83;
            const repulsionRadius = 160;
            const repulsionStrength = 4.2;
          
            let mouseX = -1000;
            let mouseY = -1000;
            let targetMouseX = -1000;
            let targetMouseY = -1000;
          
            function initNodes() {
              const width = canvas.width;
              const height = canvas.height;
              
              cols = Math.ceil(width / spacing) + 1;
              rows = Math.ceil(height / spacing) + 1;
              nodes = [];
          
              const offsetX = (width - (cols - 1) * spacing) / 2;
              const offsetY = (height - (rows - 1) * spacing) / 2;
          
              for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                  const ox = offsetX + c * spacing;
                  const oy = offsetY + r * spacing;
                  nodes.push({
                    x: ox,
                    y: oy,
                    ox: ox,
                    oy: oy,
                    vx: 0,
                    vy: 0,
                    disp: 0,
                    col: c,
                    row: r
                  });
                }
              }
            }
          
            function resize() {
              const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
              const width = rect ? rect.width : window.innerWidth;
              const height = rect ? rect.height : window.innerHeight;
              
              if (canvas.width !== width || canvas.height !== height) {
                canvas.width = width;
                canvas.height = height;
                initNodes();
              }
            }
          
            function onMouseMove(e) {
              const rect = canvas.getBoundingClientRect();
              targetMouseX = e.clientX - rect.left;
              targetMouseY = e.clientY - rect.top;
            }
          
            function onMouseLeave() {
              targetMouseX = -1000;
              targetMouseY = -1000;
            }
          
            function onTouchMove(e) {
              if (e.touches.length > 0) {
                const touch = e.touches[0];
                const rect = canvas.getBoundingClientRect();
                targetMouseX = touch.clientX - rect.left;
                targetMouseY = touch.clientY - rect.top;
              }
            }
          
            function onTouchEnd() {
              targetMouseX = -1000;
              targetMouseY = -1000;
            }
          
            window.addEventListener('resize', resize);
            window.addEventListener('mousemove', onMouseMove);
            window.addEventListener('mouseleave', onMouseLeave);
            window.addEventListener('touchmove', onTouchMove, { passive: true });
            window.addEventListener('touchstart', onTouchMove, { passive: true });
            window.addEventListener('touchend', onTouchEnd);
            window.addEventListener('touchcancel', onTouchEnd);
          
            let observer;
            if (canvas.parentNode) {
              observer = new MutationObserver(resize);
              observer.observe(canvas.parentNode, { attributes: true });
            }
          
            resize();
          
            function cleanup() {
              window.removeEventListener('resize', resize);
              window.removeEventListener('mousemove', onMouseMove);
              window.removeEventListener('mouseleave', onMouseLeave);
              window.removeEventListener('touchmove', onTouchMove);
              window.removeEventListener('touchstart', onTouchMove);
              window.removeEventListener('touchend', onTouchEnd);
              window.removeEventListener('touchcancel', onTouchEnd);
              if (observer) {
                observer.disconnect();
              }
            }
          
            function update() {
              if (targetMouseX === -1000) {
                mouseX = -1000;
                mouseY = -1000;
              } else {
                if (mouseX === -1000) {
                  mouseX = targetMouseX;
                  mouseY = targetMouseY;
                } else {
                  mouseX += (targetMouseX - mouseX) * 0.16;
                  mouseY += (targetMouseY - mouseY) * 0.16;
                }
              }
          
              const rSq = repulsionRadius * repulsionRadius;
          
              for (let i = 0; i < nodes.length; i++) {
                const node = nodes[i];
          
                const ax = (node.ox - node.x) * springK;
                const ay = (node.oy - node.y) * springK;
          
                node.vx += ax;
                node.vy += ay;
          
                if (mouseX !== -1000) {
                  const dx = node.x - mouseX;
                  const dy = node.y - mouseY;
                  const distSq = dx * dx + dy * dy;
          
                  if (distSq < rSq) {
                    const dist = Math.sqrt(distSq) || 0.001;
                    const force = (repulsionRadius - dist) / repulsionRadius;
                    const push = force * force * force * repulsionStrength;
                    
                    node.vx += (dx / dist) * push;
                    node.vy += (dy / dist) * push;
                  }
                }
          
                node.vx *= friction;
                node.vy *= friction;
                node.x += node.vx;
                node.y += node.vy;
          
                node.disp = Math.hypot(node.x - node.ox, node.y - node.oy);
              }
            }
          
            function draw() {
              ctx.fillStyle = '#08090b';
              ctx.fillRect(0, 0, canvas.width, canvas.height);
          
              if (mouseX !== -1000) {
                ctx.beginPath();
                const spotlight = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, repulsionRadius * 1.8);
                spotlight.addColorStop(0, 'rgba(14, 165, 233, 0.08)');
                spotlight.addColorStop(0.5, 'rgba(99, 102, 241, 0.02)');
                spotlight.addColorStop(1, 'rgba(0, 0, 0, 0)');
                ctx.fillStyle = spotlight;
                ctx.arc(mouseX, mouseY, repulsionRadius * 1.8, 0, Math.PI * 2);
                ctx.fill();
              }
          
              const inactiveLinesPath = new Path2D();
              const activeLines = [];
          
              for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                  const idx = r * cols + c;
                  const node = nodes[idx];
          
                  if (c < cols - 1) {
                    const rightNode = nodes[idx + 1];
                    if (node.disp < 0.25 && rightNode.disp < 0.25) {
                      inactiveLinesPath.moveTo(node.ox, node.oy);
                      inactiveLinesPath.lineTo(rightNode.ox, rightNode.oy);
                    } else {
                      activeLines.push([node, rightNode]);
                    }
                  }
          
                  if (r < rows - 1) {
                    const bottomNode = nodes[idx + cols];
                    if (node.disp < 0.25 && bottomNode.disp < 0.25) {
                      inactiveLinesPath.moveTo(node.ox, node.oy);
                      inactiveLinesPath.lineTo(bottomNode.ox, bottomNode.oy);
                    } else {
                      activeLines.push([node, bottomNode]);
                    }
                  }
                }
              }
          
              ctx.strokeStyle = 'rgba(71, 85, 105, 0.09)';
              ctx.lineWidth = 1;
              ctx.stroke(inactiveLinesPath);
          
              for (let i = 0; i < activeLines.length; i++) {
                const [n1, n2] = activeLines[i];
                const avgDisp = (n1.disp + n2.disp) / 2;
                const t = Math.min(avgDisp / 48, 1.0);
          
                ctx.beginPath();
                ctx.moveTo(n1.x, n1.y);
                ctx.lineTo(n2.x, n2.y);
                
                const r_val = Math.floor(71 + t * (14 - 71));
                const g_val = Math.floor(85 + t * (165 - 85));
                const b_val = Math.floor(105 + t * (233 - 105));
                const alpha = 0.09 + t * 0.42;
          
                ctx.strokeStyle = "rgba(" + r_val + ", " + g_val + ", " + b_val + ", " + alpha + ")";
                ctx.lineWidth = 1 + t * 1.5;
                ctx.stroke();
              }
          
              const inactiveNodesPath = new Path2D();
              const activeNodes = [];
          
              for (let i = 0; i < nodes.length; i++) {
                const node = nodes[i];
                if (node.disp < 0.25) {
                  inactiveNodesPath.rect(node.ox - 1, node.oy - 1, 2, 2);
                } else {
                  activeNodes.push(node);
                }
              }
          
              ctx.fillStyle = 'rgba(100, 116, 139, 0.15)';
              ctx.fill(inactiveNodesPath);
          
              for (let i = 0; i < activeNodes.length; i++) {
                const node = activeNodes[i];
                const t = Math.min(node.disp / 48, 1.0);
          
                ctx.beginPath();
                ctx.arc(node.x, node.y, 1.5 + t * 2, 0, Math.PI * 2);
                ctx.fillStyle = "rgba(56, 189, 248, " + (0.25 + t * 0.75) + ")";
                ctx.fill();
          
                if (t > 0.35) {
                  ctx.beginPath();
                  ctx.arc(node.x, node.y, 3 + t * 6, 0, Math.PI * 2);
                  ctx.strokeStyle = "rgba(56, 189, 248, " + ((t - 0.35) * 0.22) + ")";
                  ctx.lineWidth = 1;
                  ctx.stroke();
                }
              }
            }
          
            function render() {
              if (!canvas.isConnected) {
                cleanup();
                return;
              }
          
              update();
              draw();
              requestAnimationFrame(render);
            }
          
            requestAnimationFrame(render);
          })();
          </script>
        ` }}
      />
    </div>
  );
}

Next.js App Router Component (use client)

'use client';

import React, { useEffect, useRef } from 'react';

export default function MagneticFluidGridBackground() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let targetX = 0, targetY = 0;
    let currentX = 0, currentY = 0;
    let frameId: number;

    const handleMouseMove = (e: MouseEvent) => {
      targetX = (e.clientX / window.innerWidth) - 0.5;
      targetY = (e.clientY / window.innerHeight) - 0.5;
    };

    const handleMouseLeave = () => {
      targetX = 0;
      targetY = 0;
    };

    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseleave', handleMouseLeave);

    const updateCoordinates = () => {
      currentX += (targetX - currentX) * 0.08;
      currentY += (targetY - currentY) * 0.08;

      if (containerRef.current) {
        containerRef.current.style.setProperty('--mouse-x', currentX.toFixed(4));
        containerRef.current.style.setProperty('--mouse-y', currentY.toFixed(4));
      }
      frameId = requestAnimationFrame(updateCoordinates);
    };
    
    frameId = requestAnimationFrame(updateCoordinates);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseleave', handleMouseLeave);
      cancelAnimationFrame(frameId);
    };
  }, []);

  return (
    <div 
      ref={containerRef} 
      className="w-full h-full relative overflow-hidden"
    >
      <style dangerouslySetInnerHTML={{ __html: `
        .magnetic-grid-container {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background: #08090c;
          overflow: hidden;
        }
        #canvas-magnetic-fluid-grid {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          border: none;
          display: block;
        }
      ` }} />
      
      {/* HTML Structure */}
      <div 
        className="w-full h-full"
        dangerouslySetInnerHTML={{ __html: `
          <div class="magnetic-grid-container">
            <canvas id="canvas-magnetic-fluid-grid"></canvas>
          </div>
          <script>
          (function() {
            const canvas = document.getElementById('canvas-magnetic-fluid-grid');
            if (!canvas) return;
            const ctx = canvas.getContext('2d');
            if (!ctx) return;
          
            let cols = 0;
            let rows = 0;
            let nodes = [];
            const spacing = 42;
          
            const springK = 0.045;
            const friction = 0.83;
            const repulsionRadius = 160;
            const repulsionStrength = 4.2;
          
            let mouseX = -1000;
            let mouseY = -1000;
            let targetMouseX = -1000;
            let targetMouseY = -1000;
          
            function initNodes() {
              const width = canvas.width;
              const height = canvas.height;
              
              cols = Math.ceil(width / spacing) + 1;
              rows = Math.ceil(height / spacing) + 1;
              nodes = [];
          
              const offsetX = (width - (cols - 1) * spacing) / 2;
              const offsetY = (height - (rows - 1) * spacing) / 2;
          
              for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                  const ox = offsetX + c * spacing;
                  const oy = offsetY + r * spacing;
                  nodes.push({
                    x: ox,
                    y: oy,
                    ox: ox,
                    oy: oy,
                    vx: 0,
                    vy: 0,
                    disp: 0,
                    col: c,
                    row: r
                  });
                }
              }
            }
          
            function resize() {
              const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
              const width = rect ? rect.width : window.innerWidth;
              const height = rect ? rect.height : window.innerHeight;
              
              if (canvas.width !== width || canvas.height !== height) {
                canvas.width = width;
                canvas.height = height;
                initNodes();
              }
            }
          
            function onMouseMove(e) {
              const rect = canvas.getBoundingClientRect();
              targetMouseX = e.clientX - rect.left;
              targetMouseY = e.clientY - rect.top;
            }
          
            function onMouseLeave() {
              targetMouseX = -1000;
              targetMouseY = -1000;
            }
          
            function onTouchMove(e) {
              if (e.touches.length > 0) {
                const touch = e.touches[0];
                const rect = canvas.getBoundingClientRect();
                targetMouseX = touch.clientX - rect.left;
                targetMouseY = touch.clientY - rect.top;
              }
            }
          
            function onTouchEnd() {
              targetMouseX = -1000;
              targetMouseY = -1000;
            }
          
            window.addEventListener('resize', resize);
            window.addEventListener('mousemove', onMouseMove);
            window.addEventListener('mouseleave', onMouseLeave);
            window.addEventListener('touchmove', onTouchMove, { passive: true });
            window.addEventListener('touchstart', onTouchMove, { passive: true });
            window.addEventListener('touchend', onTouchEnd);
            window.addEventListener('touchcancel', onTouchEnd);
          
            let observer;
            if (canvas.parentNode) {
              observer = new MutationObserver(resize);
              observer.observe(canvas.parentNode, { attributes: true });
            }
          
            resize();
          
            function cleanup() {
              window.removeEventListener('resize', resize);
              window.removeEventListener('mousemove', onMouseMove);
              window.removeEventListener('mouseleave', onMouseLeave);
              window.removeEventListener('touchmove', onTouchMove);
              window.removeEventListener('touchstart', onTouchMove);
              window.removeEventListener('touchend', onTouchEnd);
              window.removeEventListener('touchcancel', onTouchEnd);
              if (observer) {
                observer.disconnect();
              }
            }
          
            function update() {
              if (targetMouseX === -1000) {
                mouseX = -1000;
                mouseY = -1000;
              } else {
                if (mouseX === -1000) {
                  mouseX = targetMouseX;
                  mouseY = targetMouseY;
                } else {
                  mouseX += (targetMouseX - mouseX) * 0.16;
                  mouseY += (targetMouseY - mouseY) * 0.16;
                }
              }
          
              const rSq = repulsionRadius * repulsionRadius;
          
              for (let i = 0; i < nodes.length; i++) {
                const node = nodes[i];
          
                const ax = (node.ox - node.x) * springK;
                const ay = (node.oy - node.y) * springK;
          
                node.vx += ax;
                node.vy += ay;
          
                if (mouseX !== -1000) {
                  const dx = node.x - mouseX;
                  const dy = node.y - mouseY;
                  const distSq = dx * dx + dy * dy;
          
                  if (distSq < rSq) {
                    const dist = Math.sqrt(distSq) || 0.001;
                    const force = (repulsionRadius - dist) / repulsionRadius;
                    const push = force * force * force * repulsionStrength;
                    
                    node.vx += (dx / dist) * push;
                    node.vy += (dy / dist) * push;
                  }
                }
          
                node.vx *= friction;
                node.vy *= friction;
                node.x += node.vx;
                node.y += node.vy;
          
                node.disp = Math.hypot(node.x - node.ox, node.y - node.oy);
              }
            }
          
            function draw() {
              ctx.fillStyle = '#08090b';
              ctx.fillRect(0, 0, canvas.width, canvas.height);
          
              if (mouseX !== -1000) {
                ctx.beginPath();
                const spotlight = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, repulsionRadius * 1.8);
                spotlight.addColorStop(0, 'rgba(14, 165, 233, 0.08)');
                spotlight.addColorStop(0.5, 'rgba(99, 102, 241, 0.02)');
                spotlight.addColorStop(1, 'rgba(0, 0, 0, 0)');
                ctx.fillStyle = spotlight;
                ctx.arc(mouseX, mouseY, repulsionRadius * 1.8, 0, Math.PI * 2);
                ctx.fill();
              }
          
              const inactiveLinesPath = new Path2D();
              const activeLines = [];
          
              for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                  const idx = r * cols + c;
                  const node = nodes[idx];
          
                  if (c < cols - 1) {
                    const rightNode = nodes[idx + 1];
                    if (node.disp < 0.25 && rightNode.disp < 0.25) {
                      inactiveLinesPath.moveTo(node.ox, node.oy);
                      inactiveLinesPath.lineTo(rightNode.ox, rightNode.oy);
                    } else {
                      activeLines.push([node, rightNode]);
                    }
                  }
          
                  if (r < rows - 1) {
                    const bottomNode = nodes[idx + cols];
                    if (node.disp < 0.25 && bottomNode.disp < 0.25) {
                      inactiveLinesPath.moveTo(node.ox, node.oy);
                      inactiveLinesPath.lineTo(bottomNode.ox, bottomNode.oy);
                    } else {
                      activeLines.push([node, bottomNode]);
                    }
                  }
                }
              }
          
              ctx.strokeStyle = 'rgba(71, 85, 105, 0.09)';
              ctx.lineWidth = 1;
              ctx.stroke(inactiveLinesPath);
          
              for (let i = 0; i < activeLines.length; i++) {
                const [n1, n2] = activeLines[i];
                const avgDisp = (n1.disp + n2.disp) / 2;
                const t = Math.min(avgDisp / 48, 1.0);
          
                ctx.beginPath();
                ctx.moveTo(n1.x, n1.y);
                ctx.lineTo(n2.x, n2.y);
                
                const r_val = Math.floor(71 + t * (14 - 71));
                const g_val = Math.floor(85 + t * (165 - 85));
                const b_val = Math.floor(105 + t * (233 - 105));
                const alpha = 0.09 + t * 0.42;
          
                ctx.strokeStyle = "rgba(" + r_val + ", " + g_val + ", " + b_val + ", " + alpha + ")";
                ctx.lineWidth = 1 + t * 1.5;
                ctx.stroke();
              }
          
              const inactiveNodesPath = new Path2D();
              const activeNodes = [];
          
              for (let i = 0; i < nodes.length; i++) {
                const node = nodes[i];
                if (node.disp < 0.25) {
                  inactiveNodesPath.rect(node.ox - 1, node.oy - 1, 2, 2);
                } else {
                  activeNodes.push(node);
                }
              }
          
              ctx.fillStyle = 'rgba(100, 116, 139, 0.15)';
              ctx.fill(inactiveNodesPath);
          
              for (let i = 0; i < activeNodes.length; i++) {
                const node = activeNodes[i];
                const t = Math.min(node.disp / 48, 1.0);
          
                ctx.beginPath();
                ctx.arc(node.x, node.y, 1.5 + t * 2, 0, Math.PI * 2);
                ctx.fillStyle = "rgba(56, 189, 248, " + (0.25 + t * 0.75) + ")";
                ctx.fill();
          
                if (t > 0.35) {
                  ctx.beginPath();
                  ctx.arc(node.x, node.y, 3 + t * 6, 0, Math.PI * 2);
                  ctx.strokeStyle = "rgba(56, 189, 248, " + ((t - 0.35) * 0.22) + ")";
                  ctx.lineWidth = 1;
                  ctx.stroke();
                }
              }
            }
          
            function render() {
              if (!canvas.isConnected) {
                cleanup();
                return;
              }
          
              update();
              draw();
              requestAnimationFrame(render);
            }
          
            requestAnimationFrame(render);
          })();
          </script>
        ` }}
      />
    </div>
  );
}

Raw CSS Stylesheet Snippet

.magnetic-grid-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #08090c;
  overflow: hidden;
}
#canvas-magnetic-fluid-grid {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
  display: block;
}