Vector Attractor Field
🔵 InteractiveA highly optimized grid of minimalist vector line segments that dynamically rotate to align with the cursor. Proximal cells seamlessly grow and glow with interactive cyan highlights.
Responsive Horizontal Leaderboard
Slot ID: ca-pub-detail-below-preview
Interactive Settings
Medium Rectangle Block
Slot ID: ca-pub-detail-after-customization
Quick Copy Actions
Download File Packages
Keyboard Shortcuts
Responsive Horizontal Leaderboard
Slot ID: ca-pub-detail-before-related
More in Interactive & Cursor-Driven
Loading Canvas Shader...
Magnetic Fluid Grid
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.
Loading Canvas Shader...
Semantic Warp Lens
A gorgeous, high-fidelity monospaced text grid that warps, rotates, and inverts colors like an optical lens centered directly on the user's cursor.
Loading Canvas Shader...
Ethereal Smoke Trails
An ultra-responsive interactive canvas trail that renders smooth, glowing fluid-like ribbons accompanied by drifting and slowly dissolving micro-smoke particles.
Vector Attractor Field - HTML5 Canvas & JavaScript Interactive & Cursor-Driven background for React & Tailwind
Integrate the Vector Attractor Field 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>Vector Attractor Field</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #09090b;
}
.vector-attractor-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #06060a;
overflow: hidden;
}
#canvas-vector-attractor {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}
</style>
</head>
<body>
<div class="vector-attractor-container">
<canvas id="canvas-vector-attractor"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-vector-attractor');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
const spacing = 35;
let cols = 0;
let rows = 0;
let lines = [];
let targetMouseX = -1000;
let targetMouseY = -1000;
let mouseX = -1000;
let mouseY = -1000;
let isMouseOnScreen = false;
let dpr = 1;
let width = 0;
let height = 0;
function initGrid() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = rect ? rect.width : window.innerWidth;
height = rect ? rect.height : window.innerHeight;
dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
cols = Math.ceil(width / spacing) + 1;
rows = Math.ceil(height / spacing) + 1;
const oldLinesMap = new Map();
lines.forEach(l => {
const key = Math.round(l.cx / spacing) + ',' + Math.round(l.cy / spacing);
oldLinesMap.set(key, l);
});
const newLines = [];
for (let r = 0; r < rows; r++) {
const y = (r + 0.5) * spacing;
for (let c = 0; c < cols; c++) {
const x = (c + 0.5) * spacing;
const key = c + ',' + r;
const existing = oldLinesMap.get(key);
newLines.push({
cx: x,
cy: y,
currentAngle: existing ? existing.currentAngle : Math.random() * Math.PI * 2,
factor: existing ? existing.factor : 0,
});
}
}
lines = newLines;
if (targetMouseX === -1000) {
targetMouseX = width / 2;
targetMouseY = height / 2;
mouseX = width / 2;
mouseY = height / 2;
}
}
function handleMouseMove(e) {
isMouseOnScreen = true;
const rect = canvas.getBoundingClientRect();
targetMouseX = e.clientX - rect.left;
targetMouseY = e.clientY - rect.top;
}
function handleMouseLeave() {
isMouseOnScreen = false;
}
function handleTouchStart(e) {
isMouseOnScreen = true;
if (e.touches.length > 0) {
const rect = canvas.getBoundingClientRect();
targetMouseX = e.touches[0].clientX - rect.left;
targetMouseY = e.touches[0].clientY - rect.top;
}
}
function handleTouchMove(e) {
isMouseOnScreen = true;
if (e.touches.length > 0) {
const rect = canvas.getBoundingClientRect();
targetMouseX = e.touches[0].clientX - rect.left;
targetMouseY = e.touches[0].clientY - rect.top;
}
}
function handleTouchEnd() {
isMouseOnScreen = false;
}
function handleMessage(e) {
if (e.data) {
if (e.data.type === 'mousemove') {
isMouseOnScreen = true;
targetMouseX = e.data.x;
targetMouseY = e.data.y;
} else if (e.data.type === 'mouseleave') {
isMouseOnScreen = false;
}
}
}
window.addEventListener('resize', initGrid);
window.addEventListener('mousemove', handleMouseMove, { passive: true });
window.addEventListener('mouseleave', handleMouseLeave, { passive: true });
window.addEventListener('touchstart', handleTouchStart, { passive: true });
window.addEventListener('touchmove', handleTouchMove, { passive: true });
window.addEventListener('touchend', handleTouchEnd, { passive: true });
window.addEventListener('message', handleMessage);
let observer;
if (canvas.parentNode) {
observer = new MutationObserver(initGrid);
observer.observe(canvas.parentNode, { attributes: true });
}
initGrid();
function animate() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
if (isMouseOnScreen) {
mouseX += (targetMouseX - mouseX) * 0.08;
mouseY += (targetMouseY - mouseY) * 0.08;
} else {
const centerX = width / 2;
const centerY = height / 2;
mouseX += (centerX - mouseX) * 0.03;
mouseY += (centerY - mouseY) * 0.03;
}
ctx.fillStyle = '#06060a';
ctx.fillRect(0, 0, width, height);
const glowRadius = 240;
const gradient = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, glowRadius);
gradient.addColorStop(0, 'rgba(6, 182, 212, 0.07)');
gradient.addColorStop(0.5, 'rgba(124, 92, 255, 0.03)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
const maxRadiusSq = 200 * 200;
const totalLines = lines.length;
for (let i = 0; i < totalLines; i++) {
const line = lines[i];
const dx = mouseX - line.cx;
const dy = mouseY - line.cy;
const distSq = dx * dx + dy * dy;
let targetFactor = 0;
if (distSq < maxRadiusSq) {
targetFactor = 1.0 - distSq / maxRadiusSq;
targetFactor = targetFactor * targetFactor * targetFactor;
}
line.factor += (targetFactor - line.factor) * 0.08;
const targetAngle = Math.atan2(dy, dx);
let diff = targetAngle - line.currentAngle;
diff = Math.atan2(Math.sin(diff), Math.cos(diff));
line.currentAngle += diff * 0.08;
const lineLen = 8 + line.factor * 12;
const thickness = 1.0 + line.factor * 1.5;
const r = Math.round(75 + (6 - 75) * line.factor);
const g = Math.round(85 + (182 - 85) * line.factor);
const b = Math.round(99 + (212 - 99) * line.factor);
const a = 0.25 + line.factor * 0.65;
ctx.save();
ctx.translate(line.cx, line.cy);
ctx.rotate(line.currentAngle);
ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
ctx.lineWidth = thickness;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-lineLen / 2, 0);
ctx.lineTo(lineLen / 2, 0);
ctx.stroke();
ctx.restore();
}
requestAnimationFrame(animate);
}
function cleanup() {
active = false;
window.removeEventListener('resize', initGrid);
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseleave', handleMouseLeave);
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('touchend', handleTouchEnd);
window.removeEventListener('message', handleMessage);
if (observer) {
observer.disconnect();
}
}
requestAnimationFrame(animate);
})();
</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 VectorAttractorFieldBackground() {
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: `
.vector-attractor-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #06060a;
overflow: hidden;
}
#canvas-vector-attractor {
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="vector-attractor-container">
<canvas id="canvas-vector-attractor"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-vector-attractor');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
const spacing = 35;
let cols = 0;
let rows = 0;
let lines = [];
let targetMouseX = -1000;
let targetMouseY = -1000;
let mouseX = -1000;
let mouseY = -1000;
let isMouseOnScreen = false;
let dpr = 1;
let width = 0;
let height = 0;
function initGrid() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = rect ? rect.width : window.innerWidth;
height = rect ? rect.height : window.innerHeight;
dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
cols = Math.ceil(width / spacing) + 1;
rows = Math.ceil(height / spacing) + 1;
const oldLinesMap = new Map();
lines.forEach(l => {
const key = Math.round(l.cx / spacing) + ',' + Math.round(l.cy / spacing);
oldLinesMap.set(key, l);
});
const newLines = [];
for (let r = 0; r < rows; r++) {
const y = (r + 0.5) * spacing;
for (let c = 0; c < cols; c++) {
const x = (c + 0.5) * spacing;
const key = c + ',' + r;
const existing = oldLinesMap.get(key);
newLines.push({
cx: x,
cy: y,
currentAngle: existing ? existing.currentAngle : Math.random() * Math.PI * 2,
factor: existing ? existing.factor : 0,
});
}
}
lines = newLines;
if (targetMouseX === -1000) {
targetMouseX = width / 2;
targetMouseY = height / 2;
mouseX = width / 2;
mouseY = height / 2;
}
}
function handleMouseMove(e) {
isMouseOnScreen = true;
const rect = canvas.getBoundingClientRect();
targetMouseX = e.clientX - rect.left;
targetMouseY = e.clientY - rect.top;
}
function handleMouseLeave() {
isMouseOnScreen = false;
}
function handleTouchStart(e) {
isMouseOnScreen = true;
if (e.touches.length > 0) {
const rect = canvas.getBoundingClientRect();
targetMouseX = e.touches[0].clientX - rect.left;
targetMouseY = e.touches[0].clientY - rect.top;
}
}
function handleTouchMove(e) {
isMouseOnScreen = true;
if (e.touches.length > 0) {
const rect = canvas.getBoundingClientRect();
targetMouseX = e.touches[0].clientX - rect.left;
targetMouseY = e.touches[0].clientY - rect.top;
}
}
function handleTouchEnd() {
isMouseOnScreen = false;
}
function handleMessage(e) {
if (e.data) {
if (e.data.type === 'mousemove') {
isMouseOnScreen = true;
targetMouseX = e.data.x;
targetMouseY = e.data.y;
} else if (e.data.type === 'mouseleave') {
isMouseOnScreen = false;
}
}
}
window.addEventListener('resize', initGrid);
window.addEventListener('mousemove', handleMouseMove, { passive: true });
window.addEventListener('mouseleave', handleMouseLeave, { passive: true });
window.addEventListener('touchstart', handleTouchStart, { passive: true });
window.addEventListener('touchmove', handleTouchMove, { passive: true });
window.addEventListener('touchend', handleTouchEnd, { passive: true });
window.addEventListener('message', handleMessage);
let observer;
if (canvas.parentNode) {
observer = new MutationObserver(initGrid);
observer.observe(canvas.parentNode, { attributes: true });
}
initGrid();
function animate() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
if (isMouseOnScreen) {
mouseX += (targetMouseX - mouseX) * 0.08;
mouseY += (targetMouseY - mouseY) * 0.08;
} else {
const centerX = width / 2;
const centerY = height / 2;
mouseX += (centerX - mouseX) * 0.03;
mouseY += (centerY - mouseY) * 0.03;
}
ctx.fillStyle = '#06060a';
ctx.fillRect(0, 0, width, height);
const glowRadius = 240;
const gradient = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, glowRadius);
gradient.addColorStop(0, 'rgba(6, 182, 212, 0.07)');
gradient.addColorStop(0.5, 'rgba(124, 92, 255, 0.03)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
const maxRadiusSq = 200 * 200;
const totalLines = lines.length;
for (let i = 0; i < totalLines; i++) {
const line = lines[i];
const dx = mouseX - line.cx;
const dy = mouseY - line.cy;
const distSq = dx * dx + dy * dy;
let targetFactor = 0;
if (distSq < maxRadiusSq) {
targetFactor = 1.0 - distSq / maxRadiusSq;
targetFactor = targetFactor * targetFactor * targetFactor;
}
line.factor += (targetFactor - line.factor) * 0.08;
const targetAngle = Math.atan2(dy, dx);
let diff = targetAngle - line.currentAngle;
diff = Math.atan2(Math.sin(diff), Math.cos(diff));
line.currentAngle += diff * 0.08;
const lineLen = 8 + line.factor * 12;
const thickness = 1.0 + line.factor * 1.5;
const r = Math.round(75 + (6 - 75) * line.factor);
const g = Math.round(85 + (182 - 85) * line.factor);
const b = Math.round(99 + (212 - 99) * line.factor);
const a = 0.25 + line.factor * 0.65;
ctx.save();
ctx.translate(line.cx, line.cy);
ctx.rotate(line.currentAngle);
ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
ctx.lineWidth = thickness;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-lineLen / 2, 0);
ctx.lineTo(lineLen / 2, 0);
ctx.stroke();
ctx.restore();
}
requestAnimationFrame(animate);
}
function cleanup() {
active = false;
window.removeEventListener('resize', initGrid);
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseleave', handleMouseLeave);
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('touchend', handleTouchEnd);
window.removeEventListener('message', handleMessage);
if (observer) {
observer.disconnect();
}
}
requestAnimationFrame(animate);
})();
</script>
` }}
/>
</div>
);
}Next.js App Router Component (use client)
'use client';
import React, { useEffect, useRef } from 'react';
export default function VectorAttractorFieldBackground() {
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: `
.vector-attractor-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #06060a;
overflow: hidden;
}
#canvas-vector-attractor {
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="vector-attractor-container">
<canvas id="canvas-vector-attractor"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-vector-attractor');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
const spacing = 35;
let cols = 0;
let rows = 0;
let lines = [];
let targetMouseX = -1000;
let targetMouseY = -1000;
let mouseX = -1000;
let mouseY = -1000;
let isMouseOnScreen = false;
let dpr = 1;
let width = 0;
let height = 0;
function initGrid() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = rect ? rect.width : window.innerWidth;
height = rect ? rect.height : window.innerHeight;
dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
cols = Math.ceil(width / spacing) + 1;
rows = Math.ceil(height / spacing) + 1;
const oldLinesMap = new Map();
lines.forEach(l => {
const key = Math.round(l.cx / spacing) + ',' + Math.round(l.cy / spacing);
oldLinesMap.set(key, l);
});
const newLines = [];
for (let r = 0; r < rows; r++) {
const y = (r + 0.5) * spacing;
for (let c = 0; c < cols; c++) {
const x = (c + 0.5) * spacing;
const key = c + ',' + r;
const existing = oldLinesMap.get(key);
newLines.push({
cx: x,
cy: y,
currentAngle: existing ? existing.currentAngle : Math.random() * Math.PI * 2,
factor: existing ? existing.factor : 0,
});
}
}
lines = newLines;
if (targetMouseX === -1000) {
targetMouseX = width / 2;
targetMouseY = height / 2;
mouseX = width / 2;
mouseY = height / 2;
}
}
function handleMouseMove(e) {
isMouseOnScreen = true;
const rect = canvas.getBoundingClientRect();
targetMouseX = e.clientX - rect.left;
targetMouseY = e.clientY - rect.top;
}
function handleMouseLeave() {
isMouseOnScreen = false;
}
function handleTouchStart(e) {
isMouseOnScreen = true;
if (e.touches.length > 0) {
const rect = canvas.getBoundingClientRect();
targetMouseX = e.touches[0].clientX - rect.left;
targetMouseY = e.touches[0].clientY - rect.top;
}
}
function handleTouchMove(e) {
isMouseOnScreen = true;
if (e.touches.length > 0) {
const rect = canvas.getBoundingClientRect();
targetMouseX = e.touches[0].clientX - rect.left;
targetMouseY = e.touches[0].clientY - rect.top;
}
}
function handleTouchEnd() {
isMouseOnScreen = false;
}
function handleMessage(e) {
if (e.data) {
if (e.data.type === 'mousemove') {
isMouseOnScreen = true;
targetMouseX = e.data.x;
targetMouseY = e.data.y;
} else if (e.data.type === 'mouseleave') {
isMouseOnScreen = false;
}
}
}
window.addEventListener('resize', initGrid);
window.addEventListener('mousemove', handleMouseMove, { passive: true });
window.addEventListener('mouseleave', handleMouseLeave, { passive: true });
window.addEventListener('touchstart', handleTouchStart, { passive: true });
window.addEventListener('touchmove', handleTouchMove, { passive: true });
window.addEventListener('touchend', handleTouchEnd, { passive: true });
window.addEventListener('message', handleMessage);
let observer;
if (canvas.parentNode) {
observer = new MutationObserver(initGrid);
observer.observe(canvas.parentNode, { attributes: true });
}
initGrid();
function animate() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
if (isMouseOnScreen) {
mouseX += (targetMouseX - mouseX) * 0.08;
mouseY += (targetMouseY - mouseY) * 0.08;
} else {
const centerX = width / 2;
const centerY = height / 2;
mouseX += (centerX - mouseX) * 0.03;
mouseY += (centerY - mouseY) * 0.03;
}
ctx.fillStyle = '#06060a';
ctx.fillRect(0, 0, width, height);
const glowRadius = 240;
const gradient = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, glowRadius);
gradient.addColorStop(0, 'rgba(6, 182, 212, 0.07)');
gradient.addColorStop(0.5, 'rgba(124, 92, 255, 0.03)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
const maxRadiusSq = 200 * 200;
const totalLines = lines.length;
for (let i = 0; i < totalLines; i++) {
const line = lines[i];
const dx = mouseX - line.cx;
const dy = mouseY - line.cy;
const distSq = dx * dx + dy * dy;
let targetFactor = 0;
if (distSq < maxRadiusSq) {
targetFactor = 1.0 - distSq / maxRadiusSq;
targetFactor = targetFactor * targetFactor * targetFactor;
}
line.factor += (targetFactor - line.factor) * 0.08;
const targetAngle = Math.atan2(dy, dx);
let diff = targetAngle - line.currentAngle;
diff = Math.atan2(Math.sin(diff), Math.cos(diff));
line.currentAngle += diff * 0.08;
const lineLen = 8 + line.factor * 12;
const thickness = 1.0 + line.factor * 1.5;
const r = Math.round(75 + (6 - 75) * line.factor);
const g = Math.round(85 + (182 - 85) * line.factor);
const b = Math.round(99 + (212 - 99) * line.factor);
const a = 0.25 + line.factor * 0.65;
ctx.save();
ctx.translate(line.cx, line.cy);
ctx.rotate(line.currentAngle);
ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
ctx.lineWidth = thickness;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-lineLen / 2, 0);
ctx.lineTo(lineLen / 2, 0);
ctx.stroke();
ctx.restore();
}
requestAnimationFrame(animate);
}
function cleanup() {
active = false;
window.removeEventListener('resize', initGrid);
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseleave', handleMouseLeave);
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('touchend', handleTouchEnd);
window.removeEventListener('message', handleMessage);
if (observer) {
observer.disconnect();
}
}
requestAnimationFrame(animate);
})();
</script>
` }}
/>
</div>
);
}Raw CSS Stylesheet Snippet
.vector-attractor-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #06060a;
overflow: hidden;
}
#canvas-vector-attractor {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}