Viscous Chrome Fluid
🟣 CanvasA premium, edge-to-edge liquid chrome and viscous ink simulation responsive to high-velocity mouse 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 Abstract
Loading Canvas Shader...
Floating 3D Squares
Floating wireframe 3D-rotated square outlines drifting and spinning upwards on cosmic black.
Loading Canvas Shader...
Kinetic Gravity Well
An abstract, massive orbital simulation constructed from thousands of mathematically precise microscopic kinetic dots that violently react to cursor-driven gravity physics.
Loading Canvas Shader...
Tactile Claymorphic Spheres
Pristine candy-colored claymorphic spheres casting deep, soft lifelike shadows, morphing and reacting beautifully to mouse coordinates.
Viscous Chrome Fluid - HTML5 Canvas & JavaScript Abstract background for React & Tailwind
Integrate the Viscous Chrome Fluid 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: CANVAS (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>Viscous Chrome Fluid</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #09090b;
}
.viscous-chrome-container {
width: 100%;
height: 100%;
background: #050505;
position: relative;
overflow: hidden;
}
#chrome-canvas {
width: 100%;
height: 100%;
display: block;
}
</style>
</head>
<body>
<div class="viscous-chrome-container" style="width: 100%; height: 100%; overflow: hidden; background: #050505;">
<canvas id="chrome-canvas" style="width: 100%; height: 100%; display: block; filter: contrast(1.2) brightness(1.1);"></canvas>
<script>
(function() {
const canvas = document.getElementById('chrome-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let width = 0;
let height = 0;
const dpr = 0.5;
let mouse = { x: -1000, y: -1000, active: false };
let lerpedMouse = { x: -1000, y: -1000 };
const blobs = [];
const numBlobs = 6;
const pointsPerBlob = 16;
function initSimulation() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = rect ? rect.width : window.innerWidth;
height = rect ? rect.height : window.innerHeight;
canvas.width = width * dpr;
canvas.height = height * dpr;
blobs.length = 0;
for (let b = 0; b < numBlobs; b++) {
const points = [];
const baseRadius = Math.min(width, height) * (0.15 + b * 0.05);
const centerX = width * (0.2 + Math.random() * 0.6);
const centerY = height * (0.2 + Math.random() * 0.6);
for (let i = 0; i < pointsPerBlob; i++) {
const angle = (i / pointsPerBlob) * Math.PI * 2;
const px = centerX + Math.cos(angle) * baseRadius;
const py = centerY + Math.sin(angle) * baseRadius;
points.push({
x: px * dpr,
y: py * dpr,
ox: px * dpr,
oy: py * dpr,
vx: 0,
vy: 0,
noiseOffsetX: Math.random() * 1000,
noiseOffsetY: Math.random() * 1000,
});
}
blobs.push({
points,
baseRadius: baseRadius * dpr,
centerX: centerX * dpr,
centerY: centerY * dpr,
speed: 0.2 + Math.random() * 0.3,
angleOffset: Math.random() * Math.PI * 2,
});
}
}
initSimulation();
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * dpr;
mouse.y = (e.clientY - rect.top) * dpr;
mouse.active = true;
});
window.addEventListener('mouseleave', () => {
mouse.active = false;
mouse.x = -1000;
mouse.y = -1000;
});
window.addEventListener('touchmove', (e) => {
if (e.touches && e.touches[0]) {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.touches[0].clientX - rect.left) * dpr;
mouse.y = (e.touches[0].clientY - rect.top) * dpr;
mouse.active = true;
}
});
window.addEventListener('touchend', () => {
mouse.active = false;
mouse.x = -1000;
mouse.y = -1000;
});
window.addEventListener('resize', () => {
initSimulation();
});
let time = 0;
function renderLoop() {
time += 0.003;
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (mouse.active) {
if (lerpedMouse.x === -1000) {
lerpedMouse.x = mouse.x;
lerpedMouse.y = mouse.y;
} else {
lerpedMouse.x += (mouse.x - lerpedMouse.x) * 0.15;
lerpedMouse.y += (mouse.y - lerpedMouse.y) * 0.15;
}
} else {
lerpedMouse.x = -1000;
lerpedMouse.y = -1000;
}
const repellerRadius = 160 * dpr;
const springK = 0.04;
const damping = 0.88;
blobs.forEach((blob) => {
const driftRadius = 30 * dpr;
const currentCenterX = blob.centerX + Math.cos(time * blob.speed + blob.angleOffset) * driftRadius;
const currentCenterY = blob.centerY + Math.sin(time * blob.speed * 1.3 + blob.angleOffset) * driftRadius;
blob.points.forEach((pt, pIdx) => {
const angle = (pIdx / pointsPerBlob) * Math.PI * 2;
const noiseFactor = 25 * dpr;
const slowDriftX = Math.cos(time + pt.noiseOffsetX) * noiseFactor;
const slowDriftY = Math.sin(time * 0.7 + pt.noiseOffsetY) * noiseFactor;
const targetX = currentCenterX + Math.cos(angle) * blob.baseRadius + slowDriftX;
const targetY = currentCenterY + Math.sin(angle) * blob.baseRadius + slowDriftY;
pt.ox = targetX;
pt.oy = targetY;
if (lerpedMouse.x !== -1000) {
const dx = pt.x - lerpedMouse.x;
const dy = pt.y - lerpedMouse.y;
const dist = Math.hypot(dx, dy);
if (dist < repellerRadius && dist > 0.1) {
const force = (repellerRadius - dist) / repellerRadius;
const repulsionPower = force * force * 22 * dpr;
const pushAngle = Math.atan2(dy, dx);
pt.vx += Math.cos(pushAngle) * repulsionPower;
pt.vy += Math.sin(pushAngle) * repulsionPower;
}
}
pt.vx += (pt.ox - pt.x) * springK;
pt.vy += (pt.oy - pt.y) * springK;
pt.vx *= damping;
pt.vy *= damping;
pt.x += pt.vx;
pt.y += pt.vy;
});
drawChromePath(blob.points, ctx, 'outer', blob);
drawChromePath(blob.points, ctx, 'middle', blob);
drawChromePath(blob.points, ctx, 'highlight', blob);
drawChromePath(blob.points, ctx, 'stroke', blob);
});
requestAnimationFrame(renderLoop);
}
function drawChromePath(points, c, layer, blob) {
if (points.length < 3) return;
c.beginPath();
let startX = (points[0].x + points[points.length - 1].x) / 2;
let startY = (points[0].y + points[points.length - 1].y) / 2;
c.moveTo(startX, startY);
for (let i = 0; i < points.length; i++) {
const pCurrent = points[i];
const pNext = points[(i + 1) % points.length];
const xc = (pCurrent.x + pNext.x) / 2;
const yc = (pCurrent.y + pNext.y) / 2;
c.quadraticCurveTo(pCurrent.x, pCurrent.y, xc, yc);
}
c.closePath();
const grad = c.createLinearGradient(
blob.centerX - blob.baseRadius,
blob.centerY - blob.baseRadius,
blob.centerX + blob.baseRadius,
blob.centerY + blob.baseRadius
);
if (layer === 'outer') {
grad.addColorStop(0, '#000000');
grad.addColorStop(0.3, '#101015');
grad.addColorStop(0.5, '#404452');
grad.addColorStop(0.55, '#121217');
grad.addColorStop(0.8, '#08080a');
grad.addColorStop(1, '#000000');
c.fillStyle = grad;
c.shadowColor = 'rgba(0,0,0,0.8)';
c.shadowBlur = 40;
c.fill();
c.shadowBlur = 0;
} else if (layer === 'middle') {
c.save();
c.scale(0.85, 0.85);
c.translate(blob.centerX * 0.176, blob.centerY * 0.176);
grad.addColorStop(0, '#ffffff');
grad.addColorStop(0.2, '#9ca3af');
grad.addColorStop(0.4, '#1f2937');
grad.addColorStop(0.5, '#111827');
grad.addColorStop(0.65, '#cbd5e1');
grad.addColorStop(0.85, '#4b5563');
grad.addColorStop(1, '#111115');
c.fillStyle = grad;
c.fill();
c.restore();
} else if (layer === 'highlight') {
c.save();
c.scale(0.68, 0.68);
c.translate(blob.centerX * 0.47, blob.centerY * 0.47);
const hGrad = c.createLinearGradient(
blob.centerX - blob.baseRadius * 0.5,
blob.centerY - blob.baseRadius * 0.5,
blob.centerX + blob.baseRadius * 0.5,
blob.centerY + blob.baseRadius * 0.5
);
hGrad.addColorStop(0, 'rgba(255,255,255,0.95)');
hGrad.addColorStop(0.2, 'rgba(230,235,255,0.7)');
hGrad.addColorStop(0.45, 'rgba(10,12,18,0.9)');
hGrad.addColorStop(0.6, 'rgba(255,255,255,0.85)');
hGrad.addColorStop(0.8, 'rgba(150,155,170,0.4)');
hGrad.addColorStop(1, 'rgba(255,255,255,0.95)');
c.fillStyle = hGrad;
c.fill();
c.restore();
} else if (layer === 'stroke') {
c.strokeStyle = 'rgba(255, 255, 255, 0.85)';
c.lineWidth = 1.5;
c.stroke();
}
}
renderLoop();
})();
</script>
</div>
</body>
</html>React Component Wrapper (TSX)
import React from 'react';
export default function ViscousChromeFluidBackground() {
return (
<div
style={{ width: '100%', height: '100%', position: 'relative', overflow: 'hidden' }}
>
<style dangerouslySetInnerHTML={{ __html: `
.viscous-chrome-container {
width: 100%;
height: 100%;
background: #050505;
position: relative;
overflow: hidden;
}
#chrome-canvas {
width: 100%;
height: 100%;
display: block;
}
` }} />
{/* HTML Structure */}
<div
style={{ width: '100%', height: '100%' }}
dangerouslySetInnerHTML={{ __html: `
<div class="viscous-chrome-container" style="width: 100%; height: 100%; overflow: hidden; background: #050505;">
<canvas id="chrome-canvas" style="width: 100%; height: 100%; display: block; filter: contrast(1.2) brightness(1.1);"></canvas>
<script>
(function() {
const canvas = document.getElementById('chrome-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let width = 0;
let height = 0;
const dpr = 0.5;
let mouse = { x: -1000, y: -1000, active: false };
let lerpedMouse = { x: -1000, y: -1000 };
const blobs = [];
const numBlobs = 6;
const pointsPerBlob = 16;
function initSimulation() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = rect ? rect.width : window.innerWidth;
height = rect ? rect.height : window.innerHeight;
canvas.width = width * dpr;
canvas.height = height * dpr;
blobs.length = 0;
for (let b = 0; b < numBlobs; b++) {
const points = [];
const baseRadius = Math.min(width, height) * (0.15 + b * 0.05);
const centerX = width * (0.2 + Math.random() * 0.6);
const centerY = height * (0.2 + Math.random() * 0.6);
for (let i = 0; i < pointsPerBlob; i++) {
const angle = (i / pointsPerBlob) * Math.PI * 2;
const px = centerX + Math.cos(angle) * baseRadius;
const py = centerY + Math.sin(angle) * baseRadius;
points.push({
x: px * dpr,
y: py * dpr,
ox: px * dpr,
oy: py * dpr,
vx: 0,
vy: 0,
noiseOffsetX: Math.random() * 1000,
noiseOffsetY: Math.random() * 1000,
});
}
blobs.push({
points,
baseRadius: baseRadius * dpr,
centerX: centerX * dpr,
centerY: centerY * dpr,
speed: 0.2 + Math.random() * 0.3,
angleOffset: Math.random() * Math.PI * 2,
});
}
}
initSimulation();
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * dpr;
mouse.y = (e.clientY - rect.top) * dpr;
mouse.active = true;
});
window.addEventListener('mouseleave', () => {
mouse.active = false;
mouse.x = -1000;
mouse.y = -1000;
});
window.addEventListener('touchmove', (e) => {
if (e.touches && e.touches[0]) {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.touches[0].clientX - rect.left) * dpr;
mouse.y = (e.touches[0].clientY - rect.top) * dpr;
mouse.active = true;
}
});
window.addEventListener('touchend', () => {
mouse.active = false;
mouse.x = -1000;
mouse.y = -1000;
});
window.addEventListener('resize', () => {
initSimulation();
});
let time = 0;
function renderLoop() {
time += 0.003;
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (mouse.active) {
if (lerpedMouse.x === -1000) {
lerpedMouse.x = mouse.x;
lerpedMouse.y = mouse.y;
} else {
lerpedMouse.x += (mouse.x - lerpedMouse.x) * 0.15;
lerpedMouse.y += (mouse.y - lerpedMouse.y) * 0.15;
}
} else {
lerpedMouse.x = -1000;
lerpedMouse.y = -1000;
}
const repellerRadius = 160 * dpr;
const springK = 0.04;
const damping = 0.88;
blobs.forEach((blob) => {
const driftRadius = 30 * dpr;
const currentCenterX = blob.centerX + Math.cos(time * blob.speed + blob.angleOffset) * driftRadius;
const currentCenterY = blob.centerY + Math.sin(time * blob.speed * 1.3 + blob.angleOffset) * driftRadius;
blob.points.forEach((pt, pIdx) => {
const angle = (pIdx / pointsPerBlob) * Math.PI * 2;
const noiseFactor = 25 * dpr;
const slowDriftX = Math.cos(time + pt.noiseOffsetX) * noiseFactor;
const slowDriftY = Math.sin(time * 0.7 + pt.noiseOffsetY) * noiseFactor;
const targetX = currentCenterX + Math.cos(angle) * blob.baseRadius + slowDriftX;
const targetY = currentCenterY + Math.sin(angle) * blob.baseRadius + slowDriftY;
pt.ox = targetX;
pt.oy = targetY;
if (lerpedMouse.x !== -1000) {
const dx = pt.x - lerpedMouse.x;
const dy = pt.y - lerpedMouse.y;
const dist = Math.hypot(dx, dy);
if (dist < repellerRadius && dist > 0.1) {
const force = (repellerRadius - dist) / repellerRadius;
const repulsionPower = force * force * 22 * dpr;
const pushAngle = Math.atan2(dy, dx);
pt.vx += Math.cos(pushAngle) * repulsionPower;
pt.vy += Math.sin(pushAngle) * repulsionPower;
}
}
pt.vx += (pt.ox - pt.x) * springK;
pt.vy += (pt.oy - pt.y) * springK;
pt.vx *= damping;
pt.vy *= damping;
pt.x += pt.vx;
pt.y += pt.vy;
});
drawChromePath(blob.points, ctx, 'outer', blob);
drawChromePath(blob.points, ctx, 'middle', blob);
drawChromePath(blob.points, ctx, 'highlight', blob);
drawChromePath(blob.points, ctx, 'stroke', blob);
});
requestAnimationFrame(renderLoop);
}
function drawChromePath(points, c, layer, blob) {
if (points.length < 3) return;
c.beginPath();
let startX = (points[0].x + points[points.length - 1].x) / 2;
let startY = (points[0].y + points[points.length - 1].y) / 2;
c.moveTo(startX, startY);
for (let i = 0; i < points.length; i++) {
const pCurrent = points[i];
const pNext = points[(i + 1) % points.length];
const xc = (pCurrent.x + pNext.x) / 2;
const yc = (pCurrent.y + pNext.y) / 2;
c.quadraticCurveTo(pCurrent.x, pCurrent.y, xc, yc);
}
c.closePath();
const grad = c.createLinearGradient(
blob.centerX - blob.baseRadius,
blob.centerY - blob.baseRadius,
blob.centerX + blob.baseRadius,
blob.centerY + blob.baseRadius
);
if (layer === 'outer') {
grad.addColorStop(0, '#000000');
grad.addColorStop(0.3, '#101015');
grad.addColorStop(0.5, '#404452');
grad.addColorStop(0.55, '#121217');
grad.addColorStop(0.8, '#08080a');
grad.addColorStop(1, '#000000');
c.fillStyle = grad;
c.shadowColor = 'rgba(0,0,0,0.8)';
c.shadowBlur = 40;
c.fill();
c.shadowBlur = 0;
} else if (layer === 'middle') {
c.save();
c.scale(0.85, 0.85);
c.translate(blob.centerX * 0.176, blob.centerY * 0.176);
grad.addColorStop(0, '#ffffff');
grad.addColorStop(0.2, '#9ca3af');
grad.addColorStop(0.4, '#1f2937');
grad.addColorStop(0.5, '#111827');
grad.addColorStop(0.65, '#cbd5e1');
grad.addColorStop(0.85, '#4b5563');
grad.addColorStop(1, '#111115');
c.fillStyle = grad;
c.fill();
c.restore();
} else if (layer === 'highlight') {
c.save();
c.scale(0.68, 0.68);
c.translate(blob.centerX * 0.47, blob.centerY * 0.47);
const hGrad = c.createLinearGradient(
blob.centerX - blob.baseRadius * 0.5,
blob.centerY - blob.baseRadius * 0.5,
blob.centerX + blob.baseRadius * 0.5,
blob.centerY + blob.baseRadius * 0.5
);
hGrad.addColorStop(0, 'rgba(255,255,255,0.95)');
hGrad.addColorStop(0.2, 'rgba(230,235,255,0.7)');
hGrad.addColorStop(0.45, 'rgba(10,12,18,0.9)');
hGrad.addColorStop(0.6, 'rgba(255,255,255,0.85)');
hGrad.addColorStop(0.8, 'rgba(150,155,170,0.4)');
hGrad.addColorStop(1, 'rgba(255,255,255,0.95)');
c.fillStyle = hGrad;
c.fill();
c.restore();
} else if (layer === 'stroke') {
c.strokeStyle = 'rgba(255, 255, 255, 0.85)';
c.lineWidth = 1.5;
c.stroke();
}
}
renderLoop();
})();
</script>
</div>
` }}
/>
</div>
);
}Next.js App Router Component (use client)
'use client';
import React from 'react';
export default function ViscousChromeFluidBackground() {
return (
<div
className="w-full h-full relative overflow-hidden"
>
<style dangerouslySetInnerHTML={{ __html: `
.viscous-chrome-container {
width: 100%;
height: 100%;
background: #050505;
position: relative;
overflow: hidden;
}
#chrome-canvas {
width: 100%;
height: 100%;
display: block;
}
` }} />
{/* HTML Structure */}
<div
className="w-full h-full"
dangerouslySetInnerHTML={{ __html: `
<div class="viscous-chrome-container" style="width: 100%; height: 100%; overflow: hidden; background: #050505;">
<canvas id="chrome-canvas" style="width: 100%; height: 100%; display: block; filter: contrast(1.2) brightness(1.1);"></canvas>
<script>
(function() {
const canvas = document.getElementById('chrome-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let width = 0;
let height = 0;
const dpr = 0.5;
let mouse = { x: -1000, y: -1000, active: false };
let lerpedMouse = { x: -1000, y: -1000 };
const blobs = [];
const numBlobs = 6;
const pointsPerBlob = 16;
function initSimulation() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = rect ? rect.width : window.innerWidth;
height = rect ? rect.height : window.innerHeight;
canvas.width = width * dpr;
canvas.height = height * dpr;
blobs.length = 0;
for (let b = 0; b < numBlobs; b++) {
const points = [];
const baseRadius = Math.min(width, height) * (0.15 + b * 0.05);
const centerX = width * (0.2 + Math.random() * 0.6);
const centerY = height * (0.2 + Math.random() * 0.6);
for (let i = 0; i < pointsPerBlob; i++) {
const angle = (i / pointsPerBlob) * Math.PI * 2;
const px = centerX + Math.cos(angle) * baseRadius;
const py = centerY + Math.sin(angle) * baseRadius;
points.push({
x: px * dpr,
y: py * dpr,
ox: px * dpr,
oy: py * dpr,
vx: 0,
vy: 0,
noiseOffsetX: Math.random() * 1000,
noiseOffsetY: Math.random() * 1000,
});
}
blobs.push({
points,
baseRadius: baseRadius * dpr,
centerX: centerX * dpr,
centerY: centerY * dpr,
speed: 0.2 + Math.random() * 0.3,
angleOffset: Math.random() * Math.PI * 2,
});
}
}
initSimulation();
window.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * dpr;
mouse.y = (e.clientY - rect.top) * dpr;
mouse.active = true;
});
window.addEventListener('mouseleave', () => {
mouse.active = false;
mouse.x = -1000;
mouse.y = -1000;
});
window.addEventListener('touchmove', (e) => {
if (e.touches && e.touches[0]) {
const rect = canvas.getBoundingClientRect();
mouse.x = (e.touches[0].clientX - rect.left) * dpr;
mouse.y = (e.touches[0].clientY - rect.top) * dpr;
mouse.active = true;
}
});
window.addEventListener('touchend', () => {
mouse.active = false;
mouse.x = -1000;
mouse.y = -1000;
});
window.addEventListener('resize', () => {
initSimulation();
});
let time = 0;
function renderLoop() {
time += 0.003;
ctx.fillStyle = '#050505';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (mouse.active) {
if (lerpedMouse.x === -1000) {
lerpedMouse.x = mouse.x;
lerpedMouse.y = mouse.y;
} else {
lerpedMouse.x += (mouse.x - lerpedMouse.x) * 0.15;
lerpedMouse.y += (mouse.y - lerpedMouse.y) * 0.15;
}
} else {
lerpedMouse.x = -1000;
lerpedMouse.y = -1000;
}
const repellerRadius = 160 * dpr;
const springK = 0.04;
const damping = 0.88;
blobs.forEach((blob) => {
const driftRadius = 30 * dpr;
const currentCenterX = blob.centerX + Math.cos(time * blob.speed + blob.angleOffset) * driftRadius;
const currentCenterY = blob.centerY + Math.sin(time * blob.speed * 1.3 + blob.angleOffset) * driftRadius;
blob.points.forEach((pt, pIdx) => {
const angle = (pIdx / pointsPerBlob) * Math.PI * 2;
const noiseFactor = 25 * dpr;
const slowDriftX = Math.cos(time + pt.noiseOffsetX) * noiseFactor;
const slowDriftY = Math.sin(time * 0.7 + pt.noiseOffsetY) * noiseFactor;
const targetX = currentCenterX + Math.cos(angle) * blob.baseRadius + slowDriftX;
const targetY = currentCenterY + Math.sin(angle) * blob.baseRadius + slowDriftY;
pt.ox = targetX;
pt.oy = targetY;
if (lerpedMouse.x !== -1000) {
const dx = pt.x - lerpedMouse.x;
const dy = pt.y - lerpedMouse.y;
const dist = Math.hypot(dx, dy);
if (dist < repellerRadius && dist > 0.1) {
const force = (repellerRadius - dist) / repellerRadius;
const repulsionPower = force * force * 22 * dpr;
const pushAngle = Math.atan2(dy, dx);
pt.vx += Math.cos(pushAngle) * repulsionPower;
pt.vy += Math.sin(pushAngle) * repulsionPower;
}
}
pt.vx += (pt.ox - pt.x) * springK;
pt.vy += (pt.oy - pt.y) * springK;
pt.vx *= damping;
pt.vy *= damping;
pt.x += pt.vx;
pt.y += pt.vy;
});
drawChromePath(blob.points, ctx, 'outer', blob);
drawChromePath(blob.points, ctx, 'middle', blob);
drawChromePath(blob.points, ctx, 'highlight', blob);
drawChromePath(blob.points, ctx, 'stroke', blob);
});
requestAnimationFrame(renderLoop);
}
function drawChromePath(points, c, layer, blob) {
if (points.length < 3) return;
c.beginPath();
let startX = (points[0].x + points[points.length - 1].x) / 2;
let startY = (points[0].y + points[points.length - 1].y) / 2;
c.moveTo(startX, startY);
for (let i = 0; i < points.length; i++) {
const pCurrent = points[i];
const pNext = points[(i + 1) % points.length];
const xc = (pCurrent.x + pNext.x) / 2;
const yc = (pCurrent.y + pNext.y) / 2;
c.quadraticCurveTo(pCurrent.x, pCurrent.y, xc, yc);
}
c.closePath();
const grad = c.createLinearGradient(
blob.centerX - blob.baseRadius,
blob.centerY - blob.baseRadius,
blob.centerX + blob.baseRadius,
blob.centerY + blob.baseRadius
);
if (layer === 'outer') {
grad.addColorStop(0, '#000000');
grad.addColorStop(0.3, '#101015');
grad.addColorStop(0.5, '#404452');
grad.addColorStop(0.55, '#121217');
grad.addColorStop(0.8, '#08080a');
grad.addColorStop(1, '#000000');
c.fillStyle = grad;
c.shadowColor = 'rgba(0,0,0,0.8)';
c.shadowBlur = 40;
c.fill();
c.shadowBlur = 0;
} else if (layer === 'middle') {
c.save();
c.scale(0.85, 0.85);
c.translate(blob.centerX * 0.176, blob.centerY * 0.176);
grad.addColorStop(0, '#ffffff');
grad.addColorStop(0.2, '#9ca3af');
grad.addColorStop(0.4, '#1f2937');
grad.addColorStop(0.5, '#111827');
grad.addColorStop(0.65, '#cbd5e1');
grad.addColorStop(0.85, '#4b5563');
grad.addColorStop(1, '#111115');
c.fillStyle = grad;
c.fill();
c.restore();
} else if (layer === 'highlight') {
c.save();
c.scale(0.68, 0.68);
c.translate(blob.centerX * 0.47, blob.centerY * 0.47);
const hGrad = c.createLinearGradient(
blob.centerX - blob.baseRadius * 0.5,
blob.centerY - blob.baseRadius * 0.5,
blob.centerX + blob.baseRadius * 0.5,
blob.centerY + blob.baseRadius * 0.5
);
hGrad.addColorStop(0, 'rgba(255,255,255,0.95)');
hGrad.addColorStop(0.2, 'rgba(230,235,255,0.7)');
hGrad.addColorStop(0.45, 'rgba(10,12,18,0.9)');
hGrad.addColorStop(0.6, 'rgba(255,255,255,0.85)');
hGrad.addColorStop(0.8, 'rgba(150,155,170,0.4)');
hGrad.addColorStop(1, 'rgba(255,255,255,0.95)');
c.fillStyle = hGrad;
c.fill();
c.restore();
} else if (layer === 'stroke') {
c.strokeStyle = 'rgba(255, 255, 255, 0.85)';
c.lineWidth = 1.5;
c.stroke();
}
}
renderLoop();
})();
</script>
</div>
` }}
/>
</div>
);
}Raw CSS Stylesheet Snippet
.viscous-chrome-container {
width: 100%;
height: 100%;
background: #050505;
position: relative;
overflow: hidden;
}
#chrome-canvas {
width: 100%;
height: 100%;
display: block;
}