Brutalist Chevron Matrix
🔵 InteractiveA premium, high-performance canvas-rendered matrix of sharp brutalist chevron symbols reacting dynamically with intense spring-physics cursor tracking.
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 Brutalist & Monochrome
Brutalist Chevron Matrix - HTML5 Canvas & JavaScript Brutalist & Monochrome background for React & Tailwind
Integrate the Brutalist Chevron Matrix 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>Brutalist Chevron Matrix</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #09090b;
}
.brutalist-chevron-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050505;
overflow: hidden;
}
#canvas-brutalist-chevron {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}
</style>
</head>
<body>
<div class="brutalist-chevron-container">
<canvas id="canvas-brutalist-chevron"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-brutalist-chevron');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
const spacingX = 40;
const spacingY = 40;
let cols = 0;
let rows = 0;
let chevrons = [];
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 / spacingX) + 1;
rows = Math.ceil(height / spacingY) + 1;
const oldChevronsMap = new Map();
chevrons.forEach(c => {
const key = Math.round(c.cx / spacingX) + ',' + Math.round(c.cy / spacingY);
oldChevronsMap.set(key, c);
});
const newChevrons = [];
for (let r = 0; r < rows; r++) {
const cy = (r + 0.5) * spacingY;
for (let c = 0; c < cols; c++) {
const cx = (c + 0.5) * spacingX;
const key = c + ',' + r;
const existing = oldChevronsMap.get(key);
const restingAngle = (c % 2 === 0) ? Math.PI / 2 : -Math.PI / 2;
newChevrons.push({
cx: cx,
cy: cy,
restingAngle: restingAngle,
currentAngle: existing ? existing.currentAngle : restingAngle,
angleVelocity: existing ? existing.angleVelocity : 0,
colorFactor: existing ? existing.colorFactor : 0,
colorVelocity: existing ? existing.colorVelocity : 0,
scaleFactor: existing ? existing.scaleFactor : 1,
scaleVelocity: existing ? existing.scaleVelocity : 0,
});
}
}
chevrons = newChevrons;
}
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);
initGrid();
const stiffness = 0.18;
const damping = 0.76;
function animate() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
if (isMouseOnScreen) {
mouseX += (targetMouseX - mouseX) * 0.15;
mouseY += (targetMouseY - mouseY) * 0.15;
} else {
mouseX += (-1000 - mouseX) * 0.1;
mouseY += (-1000 - mouseY) * 0.1;
}
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, width, height);
const maxRadius = 150;
const maxRadiusSq = maxRadius * maxRadius;
const len = chevrons.length;
for (let i = 0; i < len; i++) {
const c = chevrons[i];
const dx = mouseX - c.cx;
const dy = mouseY - c.cy;
const distSq = dx * dx + dy * dy;
let targetAngle = c.restingAngle;
let targetColor = 0;
let targetScale = 1;
if (distSq < maxRadiusSq) {
const dist = Math.sqrt(distSq);
const ratio = 1 - dist / maxRadius;
const angleToMouse = Math.atan2(dy, dx);
targetAngle = c.restingAngle + (Math.PI / 2) * ratio + angleToMouse * 0.15;
targetColor = ratio;
targetScale = 1 + ratio * 0.4;
}
let angleDiff = targetAngle - c.currentAngle;
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
const angleAcc = angleDiff * stiffness;
c.angleVelocity = (c.angleVelocity + angleAcc) * damping;
c.currentAngle += c.angleVelocity;
const colorDiff = targetColor - c.colorFactor;
const colorAcc = colorDiff * stiffness;
c.colorVelocity = (c.colorVelocity + colorAcc) * damping;
c.colorFactor += c.colorVelocity;
const scaleDiff = targetScale - c.scaleFactor;
const scaleAcc = scaleDiff * stiffness;
c.scaleVelocity = (c.scaleVelocity + scaleAcc) * damping;
c.scaleFactor += c.scaleVelocity;
ctx.save();
ctx.translate(c.cx, c.cy);
ctx.rotate(c.currentAngle);
ctx.scale(c.scaleFactor, c.scaleFactor);
const greyR = 42, greyG = 46, greyB = 56;
const whiteR = 255, whiteG = 255, whiteB = 255;
const r = Math.round(greyR + (whiteR - greyR) * c.colorFactor);
const g = Math.round(greyG + (whiteG - greyG) * c.colorFactor);
const b = Math.round(greyB + (whiteB - greyB) * c.colorFactor);
const opacity = 0.2 + c.colorFactor * 0.75;
ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
ctx.lineWidth = 1.5 + c.colorFactor * 1.5;
ctx.lineCap = 'square';
ctx.lineJoin = 'miter';
const size = 10;
ctx.beginPath();
ctx.moveTo(-size, -size * 0.4);
ctx.lineTo(0, size * 0.6);
ctx.lineTo(size, -size * 0.4);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-size * 0.6, -size * 0.8);
ctx.lineTo(0, -size * 0.2);
ctx.lineTo(size * 0.6, -size * 0.8);
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);
}
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 BrutalistChevronMatrixBackground() {
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: `
.brutalist-chevron-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050505;
overflow: hidden;
}
#canvas-brutalist-chevron {
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="brutalist-chevron-container">
<canvas id="canvas-brutalist-chevron"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-brutalist-chevron');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
const spacingX = 40;
const spacingY = 40;
let cols = 0;
let rows = 0;
let chevrons = [];
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 / spacingX) + 1;
rows = Math.ceil(height / spacingY) + 1;
const oldChevronsMap = new Map();
chevrons.forEach(c => {
const key = Math.round(c.cx / spacingX) + ',' + Math.round(c.cy / spacingY);
oldChevronsMap.set(key, c);
});
const newChevrons = [];
for (let r = 0; r < rows; r++) {
const cy = (r + 0.5) * spacingY;
for (let c = 0; c < cols; c++) {
const cx = (c + 0.5) * spacingX;
const key = c + ',' + r;
const existing = oldChevronsMap.get(key);
const restingAngle = (c % 2 === 0) ? Math.PI / 2 : -Math.PI / 2;
newChevrons.push({
cx: cx,
cy: cy,
restingAngle: restingAngle,
currentAngle: existing ? existing.currentAngle : restingAngle,
angleVelocity: existing ? existing.angleVelocity : 0,
colorFactor: existing ? existing.colorFactor : 0,
colorVelocity: existing ? existing.colorVelocity : 0,
scaleFactor: existing ? existing.scaleFactor : 1,
scaleVelocity: existing ? existing.scaleVelocity : 0,
});
}
}
chevrons = newChevrons;
}
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);
initGrid();
const stiffness = 0.18;
const damping = 0.76;
function animate() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
if (isMouseOnScreen) {
mouseX += (targetMouseX - mouseX) * 0.15;
mouseY += (targetMouseY - mouseY) * 0.15;
} else {
mouseX += (-1000 - mouseX) * 0.1;
mouseY += (-1000 - mouseY) * 0.1;
}
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, width, height);
const maxRadius = 150;
const maxRadiusSq = maxRadius * maxRadius;
const len = chevrons.length;
for (let i = 0; i < len; i++) {
const c = chevrons[i];
const dx = mouseX - c.cx;
const dy = mouseY - c.cy;
const distSq = dx * dx + dy * dy;
let targetAngle = c.restingAngle;
let targetColor = 0;
let targetScale = 1;
if (distSq < maxRadiusSq) {
const dist = Math.sqrt(distSq);
const ratio = 1 - dist / maxRadius;
const angleToMouse = Math.atan2(dy, dx);
targetAngle = c.restingAngle + (Math.PI / 2) * ratio + angleToMouse * 0.15;
targetColor = ratio;
targetScale = 1 + ratio * 0.4;
}
let angleDiff = targetAngle - c.currentAngle;
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
const angleAcc = angleDiff * stiffness;
c.angleVelocity = (c.angleVelocity + angleAcc) * damping;
c.currentAngle += c.angleVelocity;
const colorDiff = targetColor - c.colorFactor;
const colorAcc = colorDiff * stiffness;
c.colorVelocity = (c.colorVelocity + colorAcc) * damping;
c.colorFactor += c.colorVelocity;
const scaleDiff = targetScale - c.scaleFactor;
const scaleAcc = scaleDiff * stiffness;
c.scaleVelocity = (c.scaleVelocity + scaleAcc) * damping;
c.scaleFactor += c.scaleVelocity;
ctx.save();
ctx.translate(c.cx, c.cy);
ctx.rotate(c.currentAngle);
ctx.scale(c.scaleFactor, c.scaleFactor);
const greyR = 42, greyG = 46, greyB = 56;
const whiteR = 255, whiteG = 255, whiteB = 255;
const r = Math.round(greyR + (whiteR - greyR) * c.colorFactor);
const g = Math.round(greyG + (whiteG - greyG) * c.colorFactor);
const b = Math.round(greyB + (whiteB - greyB) * c.colorFactor);
const opacity = 0.2 + c.colorFactor * 0.75;
ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
ctx.lineWidth = 1.5 + c.colorFactor * 1.5;
ctx.lineCap = 'square';
ctx.lineJoin = 'miter';
const size = 10;
ctx.beginPath();
ctx.moveTo(-size, -size * 0.4);
ctx.lineTo(0, size * 0.6);
ctx.lineTo(size, -size * 0.4);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-size * 0.6, -size * 0.8);
ctx.lineTo(0, -size * 0.2);
ctx.lineTo(size * 0.6, -size * 0.8);
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);
}
requestAnimationFrame(animate);
})();
</script>
` }}
/>
</div>
);
}Next.js App Router Component (use client)
'use client';
import React, { useEffect, useRef } from 'react';
export default function BrutalistChevronMatrixBackground() {
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: `
.brutalist-chevron-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050505;
overflow: hidden;
}
#canvas-brutalist-chevron {
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="brutalist-chevron-container">
<canvas id="canvas-brutalist-chevron"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-brutalist-chevron');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
const spacingX = 40;
const spacingY = 40;
let cols = 0;
let rows = 0;
let chevrons = [];
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 / spacingX) + 1;
rows = Math.ceil(height / spacingY) + 1;
const oldChevronsMap = new Map();
chevrons.forEach(c => {
const key = Math.round(c.cx / spacingX) + ',' + Math.round(c.cy / spacingY);
oldChevronsMap.set(key, c);
});
const newChevrons = [];
for (let r = 0; r < rows; r++) {
const cy = (r + 0.5) * spacingY;
for (let c = 0; c < cols; c++) {
const cx = (c + 0.5) * spacingX;
const key = c + ',' + r;
const existing = oldChevronsMap.get(key);
const restingAngle = (c % 2 === 0) ? Math.PI / 2 : -Math.PI / 2;
newChevrons.push({
cx: cx,
cy: cy,
restingAngle: restingAngle,
currentAngle: existing ? existing.currentAngle : restingAngle,
angleVelocity: existing ? existing.angleVelocity : 0,
colorFactor: existing ? existing.colorFactor : 0,
colorVelocity: existing ? existing.colorVelocity : 0,
scaleFactor: existing ? existing.scaleFactor : 1,
scaleVelocity: existing ? existing.scaleVelocity : 0,
});
}
}
chevrons = newChevrons;
}
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);
initGrid();
const stiffness = 0.18;
const damping = 0.76;
function animate() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
if (isMouseOnScreen) {
mouseX += (targetMouseX - mouseX) * 0.15;
mouseY += (targetMouseY - mouseY) * 0.15;
} else {
mouseX += (-1000 - mouseX) * 0.1;
mouseY += (-1000 - mouseY) * 0.1;
}
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, width, height);
const maxRadius = 150;
const maxRadiusSq = maxRadius * maxRadius;
const len = chevrons.length;
for (let i = 0; i < len; i++) {
const c = chevrons[i];
const dx = mouseX - c.cx;
const dy = mouseY - c.cy;
const distSq = dx * dx + dy * dy;
let targetAngle = c.restingAngle;
let targetColor = 0;
let targetScale = 1;
if (distSq < maxRadiusSq) {
const dist = Math.sqrt(distSq);
const ratio = 1 - dist / maxRadius;
const angleToMouse = Math.atan2(dy, dx);
targetAngle = c.restingAngle + (Math.PI / 2) * ratio + angleToMouse * 0.15;
targetColor = ratio;
targetScale = 1 + ratio * 0.4;
}
let angleDiff = targetAngle - c.currentAngle;
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
const angleAcc = angleDiff * stiffness;
c.angleVelocity = (c.angleVelocity + angleAcc) * damping;
c.currentAngle += c.angleVelocity;
const colorDiff = targetColor - c.colorFactor;
const colorAcc = colorDiff * stiffness;
c.colorVelocity = (c.colorVelocity + colorAcc) * damping;
c.colorFactor += c.colorVelocity;
const scaleDiff = targetScale - c.scaleFactor;
const scaleAcc = scaleDiff * stiffness;
c.scaleVelocity = (c.scaleVelocity + scaleAcc) * damping;
c.scaleFactor += c.scaleVelocity;
ctx.save();
ctx.translate(c.cx, c.cy);
ctx.rotate(c.currentAngle);
ctx.scale(c.scaleFactor, c.scaleFactor);
const greyR = 42, greyG = 46, greyB = 56;
const whiteR = 255, whiteG = 255, whiteB = 255;
const r = Math.round(greyR + (whiteR - greyR) * c.colorFactor);
const g = Math.round(greyG + (whiteG - greyG) * c.colorFactor);
const b = Math.round(greyB + (whiteB - greyB) * c.colorFactor);
const opacity = 0.2 + c.colorFactor * 0.75;
ctx.strokeStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
ctx.lineWidth = 1.5 + c.colorFactor * 1.5;
ctx.lineCap = 'square';
ctx.lineJoin = 'miter';
const size = 10;
ctx.beginPath();
ctx.moveTo(-size, -size * 0.4);
ctx.lineTo(0, size * 0.6);
ctx.lineTo(size, -size * 0.4);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-size * 0.6, -size * 0.8);
ctx.lineTo(0, -size * 0.2);
ctx.lineTo(size * 0.6, -size * 0.8);
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);
}
requestAnimationFrame(animate);
})();
</script>
` }}
/>
</div>
);
}Raw CSS Stylesheet Snippet
.brutalist-chevron-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050505;
overflow: hidden;
}
#canvas-brutalist-chevron {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}