The WOW Effect in 5 Minutes: Particles, Neon & a Magnetic Cursor — No Plugins Needed
Tired of static hero sections that feel lifeless? With a single HTML widget and a few lines of vanilla JavaScript, you can turn any Elementor container into a living, breathing canvas that reacts to every move your visitor makes — no heavy libraries, no page-speed penalties, and absolutely no plugins.
1. Cursor Constellation
Invisible nodes float in the background, but the moment you move your cursor it becomes the brightest star in the sky. Thin threads instantly connect the pointer to its nearest neighbors, rebuilding the constellation in real time as you glide across the screen. It feels like navigating a living star map.
2. Neon Comet
A single luminous point chases your cursor with a long, fading neon tail that lingers for seconds. The faster you move, the more dramatic the arc, leaving behind a streak of cyan or violet light that looks like a shooting star trapped inside your layout.
3. Meteor Rain
Particles fall at an angle from the top of the container, each trailing a short glowing tail. When the cursor enters the frame, the stream splits and bends as if a gust of wind just cut through a shower of light.
4. Pulse Rings
Every few seconds, gentle circular waves ripple outward from random points across the canvas. Your mouse acts like a stone dropped in water — the quicker you travel, the more rings you spawn, layering interference patterns across the dark background.
5. Bokeh Depth
Soft, out-of-focus orbs of varying size drift at different speeds, creating a cinematic depth-of-field effect. Moving the mouse shifts each layer independently, simulating a 3D parallax that feels like peering through a camera lens at city lights.
6. Wave Grid
Dots sit locked in a perfect grid until your cursor approaches and lifts them into a smooth sinusoidal hill. The mesh slowly settles back to flatness once you leave, behaving like fabric stretched in zero gravity and touched by a single finger.
7. Liquid Mesh
Triangles formed between neighboring points stretch, twist and change color as vertices are pulled toward the cursor. The geometric surface behaves like liquid latex, snapping back into shape with satisfying elasticity when the mouse retreats.
8. Fabric Grid
A network of dots connected by springs sags and dimples wherever the cursor presses. Neighboring nodes tug on each other through invisible threads, producing the tactile illusion of pushing your finger into soft cloth.
9. Interactive Ripple
The surface behaves like calm water: every mouse movement casts circular waves that physically push the surrounding particles. The dots act as buoys, bobbing and drifting in the wake of your cursor’s path.
10. Vortex Spiral
Particles are arranged in a graceful spiral galaxy that rotates lazily on its own. Bringing the cursor toward the center accelerates the spin and tightens the arms, while pulling away lets the galaxy exhale and slow back down.
11. Fireflies
Tiny golden orbs fade in and out at their own rhythm, drifting aimlessly through the dark. Your cursor becomes a lantern — nearby fireflies gently detach from their wandering paths and gather around the light, then disperse when you leave.
12. Atmospheric Fog
Large, blurred patches of color drift like smoke or low clouds. The cursor acts as a fan, carving a clear path through the haze and letting the fog slowly roll back in once the disturbance passes.
13. Pollen Drift
Microscopic particles drift on invisible air currents. The cursor generates aerodynamic flow — specks curve and stream around it rather than colliding, mimicking the way real pollen circumnavigates a moving object.
14. Digital Rain (Lite)
Vertical streaks of glowing dots fall from the top with varying speed and length, evoking the Matrix without the text. The cursor accelerates the drops in its vicinity, turning a gentle shower into a localized downpour of light.
15. Snow Blanket
Snowflakes fall and gradually accumulate at the bottom of the container, building a soft white ridge. Waving the mouse across the pile blows the snow away in real time, revealing the dark background underneath until fresh flakes settle again.
16. Plasma Connections
Straight lines are replaced by jagged, lightning-like zigzags that crackle between nearby points. As the cursor approaches, the bolts multiply and flicker faster, turning the background into a living plasma globe.
17. Cursor Echo
Each mouse movement sheds ghostly after-images that break away and float independently. These echoes live for a second or two, shrinking and dimming until they vanish — like sparks trailing from a firework on a black sky.
18. Orbital Dance
Every particle orbits its own invisible anchor point. The cursor warps the gravitational centers, causing planets to spiral inward and revolve around the pointer in hypnotic, synchronized loops before returning to their original axes.
19. Magnetic Repulse
Instead of attraction, the cursor emits a force field that repels every particle in its radius. The dots flee outward, leaving a perfect bubble of empty space that collapses with a gentle bounce-back once the mouse moves away.
20. Form Reveal
Particles begin locked in a recognizable silhouette — a logo, letter, or geometric shape. Hovering disrupts the formation, sending dots scattering in panic, after which they slowly creep back home and reassemble the hidden image.
Atmospheric Fog
<style>
.particles-here {
position: relative !important;
overflow: hidden !important;
}
.particles-here .ep-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.particles-here > *:not(.ep-canvas),
.particles-here > .e-con-inner > * {
position: relative;
z-index: 1;
}
</style>
<script>
(function() {
const containers = document.querySelectorAll('.particles-here');
if (!containers.length) return;
containers.forEach(container => {
const target = container.querySelector('.e-con-inner') || container;
const canvas = document.createElement('canvas');
canvas.className = 'ep-canvas';
target.insertBefore(canvas, target.firstChild);
const ctx = canvas.getContext('2d');
let W = 0, H = 0, blobs = [];
let mouse = { x: -9999, y: -9999 };
function resize() {
const rect = target.getBoundingClientRect();
W = rect.width;
H = rect.height;
const dpr = window.devicePixelRatio || 1;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
return W > 0 && H > 0;
}
class FogBlob {
constructor() {
this.reset();
}
reset() {
this.xBase = Math.random() * W;
this.yBase = Math.random() * H;
// Плавный дрейф
this.vx = (Math.random() - 0.5) * 0.6;
this.vy = (Math.random() - 0.5) * 0.5;
// Компактные облака: 60–160 px
this.radius = 60 + Math.random() * 100;
// Смещение от мыши
this.ox = 0;
this.oy = 0;
this.ovx = 0;
this.ovy = 0;
this.phase = Math.random() * Math.PI * 2;
// Цвет: чуть светлее фона #0b1018, холодный стальной
const hue = 210 + Math.random() * 20; // 210–230
const sat = 25 + Math.random() * 20; // 25–45%
const light = 38 + Math.random() * 14; // 38–52%
this.color = `hsla(${hue}, ${sat}%, ${light}%,`;
}
update(time) {
// Медленное «дыхание» размера
const breathe = Math.sin(time * 0.00018 + this.phase);
this.r = this.radius + breathe * 15;
// Плавный дрейф + лёгкое синусоидальное колебание
this.xBase += this.vx + Math.sin(time * 0.00012 + this.phase) * 0.08;
this.yBase += this.vy + Math.cos(time * 0.0001 + this.phase) * 0.06;
// Зацикливание
const m = this.r + 30;
if (this.xBase < -m) this.xBase = W + m;
if (this.xBase > W + m) this.xBase = -m;
if (this.yBase < -m) this.yBase = H + m;
if (this.yBase > H + m) this.yBase = -m;
// Мягкое отталкивание от мыши
const dx = (this.xBase + this.ox) - mouse.x;
const dy = (this.yBase + this.oy) - mouse.y;
const dist = Math.hypot(dx, dy);
const range = 200;
if (dist < range && dist > 1) {
const falloff = Math.pow(1 - dist / range, 2.2);
const angle = Math.atan2(dy, dx);
this.ovx += Math.cos(angle) * falloff * 1.2;
this.ovy += Math.sin(angle) * falloff * 1.2;
}
// Плавная инерция
this.ovx *= 0.96;
this.ovy *= 0.96;
this.ox += this.ovx;
this.oy += this.ovy;
// Упругий возврат
this.ox *= 0.97;
this.oy *= 0.97;
}
draw() {
const px = this.xBase + this.ox;
const py = this.yBase + this.oy;
// Умеренное размытие — мягкость без растворения
ctx.filter = 'blur(28px)';
const g = ctx.createRadialGradient(px, py, 0, px, py, this.r);
g.addColorStop(0, this.color + ' 0.40)');
g.addColorStop(0.45, this.color + ' 0.18)');
g.addColorStop(1, this.color + ' 0)');
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(px, py, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.filter = 'none';
}
}
function init() {
if (!resize()) return;
blobs = [];
// Больше облаков, но компактных
const count = Math.max(18, Math.floor((W * H) / 45000));
for (let i = 0; i < count; i++) blobs.push(new FogBlob());
}
function animate(time) {
if (!W || !H) { requestAnimationFrame(animate); return; }
ctx.clearRect(0, 0, W, H);
for (const b of blobs) {
b.update(time);
b.draw();
}
requestAnimationFrame(animate);
}
target.addEventListener('mousemove', e => {
const r = target.getBoundingClientRect();
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
});
target.addEventListener('mouseleave', () => {
mouse.x = -9999;
mouse.y = -9999;
});
window.addEventListener('resize', () => { resize(); init(); });
if (document.readyState === 'complete') {
init();
requestAnimationFrame(animate);
} else {
window.addEventListener('load', () => { init(); requestAnimationFrame(animate); });
}
});
})();
</script> Fabric Grid
<style>
.particles-here {
position: relative !important;
overflow: hidden !important;
}
.particles-here .ep-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.particles-here > *:not(.ep-canvas),
.particles-here > .e-con-inner > * {
position: relative;
z-index: 1;
}
</style>
<script>
(function() {
const containers = document.querySelectorAll('.particles-here');
if (!containers.length) return;
containers.forEach(container => {
const target = container.querySelector('.e-con-inner') || container;
const canvas = document.createElement('canvas');
canvas.className = 'ep-canvas';
target.insertBefore(canvas, target.firstChild);
const ctx = canvas.getContext('2d');
let W = 0, H = 0;
let points = [];
let links = [];
const mouse = { x: -9999, y: -9999 };
// Настройки физики
const CFG = {
spacing: 44, // шаг сетки
baseStiff: 0.028, // сила возврата к базе
neighborStiff: 0.018, // сила связи с соседом (нить)
damping: 0.90, // затухание инерции
mouseRadius: 140, // радиус вдавливания
mouseForce: 2.8, // сила отталкивания
subSteps: 3 // подшаги физики для стабильности
};
function resize() {
const rect = target.getBoundingClientRect();
W = rect.width;
H = rect.height;
const dpr = window.devicePixelRatio || 1;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
return W > 0 && H > 0;
}
function initGrid() {
points = [];
links = [];
const cols = Math.ceil(W / CFG.spacing) + 1;
const rows = Math.ceil(H / CFG.spacing) + 1;
const offX = (W - (cols - 1) * CFG.spacing) / 2;
const offY = (H - (rows - 1) * CFG.spacing) / 2;
// Создаём точки
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const bx = offX + x * CFG.spacing;
const by = offY + y * CFG.spacing;
points.push({
bx, by, x: bx, y: by, vx: 0, vy: 0,
gx: x, gy: y
});
}
}
// Создаём связи (нити) — только горизонталь и вертикаль
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const i = y * cols + x;
if (x < cols - 1) links.push([i, i + 1]); // вправо
if (y < rows - 1) links.push([i, i + cols]); // вниз
}
}
}
function updatePhysics() {
for (let s = 0; s < CFG.subSteps; s++) {
for (const p of points) {
let fx = 0, fy = 0;
// 1. Пружина к базовой позиции (возврат в исходную сетку)
fx += (p.bx - p.x) * CFG.baseStiff;
fy += (p.by - p.y) * CFG.baseStiff;
// 2. Связи с соседями (нити ткани)
// Ищем соседей через координаты сетки
const cols = Math.ceil(W / CFG.spacing) + 1;
const neighbors = [];
const i = points.indexOf(p);
const gx = p.gx, gy = p.gy;
if (gx > 0) neighbors.push(points[i - 1]);
if (gx < cols - 1) neighbors.push(points[i + 1]);
if (gy > 0) neighbors.push(points[i - cols]);
if (gy < Math.ceil(H / CFG.spacing)) neighbors.push(points[i + cols]);
for (const n of neighbors) {
fx += (n.x - p.x) * CFG.neighborStiff;
fy += (n.y - p.y) * CFG.neighborStiff;
}
// 3. Отталкивание от мыши (вдавливание)
const dx = p.x - mouse.x;
const dy = p.y - mouse.y;
const dist = Math.hypot(dx, dy);
if (dist < CFG.mouseRadius && dist > 0.5) {
const force = Math.pow(1 - dist / CFG.mouseRadius, 2);
const ndx = dx / dist;
const ndy = dy / dist;
fx += ndx * force * CFG.mouseForce;
fy += ndy * force * CFG.mouseForce;
}
// 4. Интеграция
p.vx += fx;
p.vy += fy;
p.vx *= CFG.damping;
p.vy *= CFG.damping;
p.x += p.vx;
p.y += p.vy;
}
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
// Нити
ctx.lineCap = 'round';
for (const [a, b] of links) {
const p1 = points[a], p2 = points[b];
const dist = Math.hypot(p1.x - p2.x, p1.y - p2.y);
const stretch = Math.abs(dist - CFG.spacing);
const maxStretch = CFG.spacing * 0.6;
const t = Math.min(1, stretch / maxStretch); // 0..1 насколько натянута
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
// Чем сильнее натянута — тем заметнее нить
ctx.strokeStyle = `rgba(100, 125, 160, ${0.06 + t * 0.14})`;
ctx.lineWidth = 0.6 + t * 0.8;
ctx.stroke();
}
// Точки (перекрёстки нитей)
for (const p of points) {
const distMouse = Math.hypot(p.x - mouse.x, p.y - mouse.y);
const glow = distMouse < CFG.mouseRadius ? 1 - distMouse / CFG.mouseRadius : 0;
ctx.beginPath();
ctx.arc(p.x, p.y, 1.2 + glow * 0.8, 0, Math.PI * 2);
ctx.fillStyle = `rgba(130, 155, 190, ${0.25 + glow * 0.4})`;
ctx.fill();
}
}
function animate() {
if (!W || !H) { requestAnimationFrame(animate); return; }
updatePhysics();
draw();
requestAnimationFrame(animate);
}
target.addEventListener('mousemove', e => {
const r = target.getBoundingClientRect();
mouse.x = e.clientX - r.left;
mouse.y = e.clientY - r.top;
});
target.addEventListener('mouseleave', () => {
mouse.x = -9999;
mouse.y = -9999;
});
window.addEventListener('resize', () => {
resize();
initGrid();
});
if (document.readyState === 'complete') {
resize();
initGrid();
requestAnimationFrame(animate);
} else {
window.addEventListener('load', () => {
resize();
initGrid();
requestAnimationFrame(animate);
});
}
});
})();
</script> Interactive Ripple
<style>
.particles-here {
position: relative !important;
overflow: hidden !important;
}
.particles-here .ep-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.particles-here > *:not(.ep-canvas),
.particles-here > .e-con-inner > * {
position: relative;
z-index: 1;
}
</style>
<script>
(function() {
const containers = document.querySelectorAll('.particles-here');
if (!containers.length) return;
containers.forEach(container => {
const target = container.querySelector('.e-con-inner') || container;
const canvas = document.createElement('canvas');
canvas.className = 'ep-canvas';
target.insertBefore(canvas, target.firstChild);
const ctx = canvas.getContext('2d');
let W = 0, H = 0, points = [], waves = [];
let lastWaveTime = 0;
const CFG = {
spacing: 40, // шаг сетки
waveAmp: 12, // амплитуда волны
waveDecay: 0.014, // затухание волны во времени
waveFreq: 0.22, // частота колец
waveSpeed: 0.14, // скорость распространения фазы
pointEase: 0.10, // плавность точек (меньше = вязче)
maxWaves: 12 // лимит одновременных волн
};
function resize() {
const rect = target.getBoundingClientRect();
W = rect.width;
H = rect.height;
const dpr = window.devicePixelRatio || 1;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
return W > 0 && H > 0;
}
function initGrid() {
points = [];
const cols = Math.ceil(W / CFG.spacing) + 1;
const rows = Math.ceil(H / CFG.spacing) + 1;
const offX = (W - (cols - 1) * CFG.spacing) / 2;
const offY = (H - (rows - 1) * CFG.spacing) / 2;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
points.push({
bx: offX + c * CFG.spacing,
by: offY + r * CFG.spacing,
x: 0, y: 0,
brightness: 0
});
}
}
points.rows = rows;
points.cols = cols;
}
function spawnWave(x, y) {
const now = performance.now();
if (now - lastWaveTime < 50) return;
lastWaveTime = now;
waves.push({
x, y,
age: 0,
amp: CFG.waveAmp * (0.9 + Math.random() * 0.3)
});
if (waves.length > CFG.maxWaves) waves.shift();
}
function update() {
// Удаляем старые волны
for (let i = waves.length - 1; i >= 0; i--) {
waves[i].age++;
if (waves[i].age > 280) waves.splice(i, 1);
}
const rows = points.rows;
const cols = points.cols;
// Обновляем каждую точку
for (let i = 0; i < points.length; i++) {
const p = points[i];
let targetX = p.bx;
let targetY = p.by;
let maxB = 0;
for (const w of waves) {
const dist = Math.hypot(p.bx - w.x, p.by - w.y);
const phase = dist * CFG.waveFreq - w.age * CFG.waveSpeed;
const envelope = Math.exp(-w.age * CFG.waveDecay) * Math.exp(-dist * 0.005);
if (envelope > 0.001) {
const displacement = Math.sin(phase) * w.amp * envelope;
targetY += displacement;
// Лёгкое радиальное смещение для объёма
const angle = Math.atan2(p.by - w.y, p.bx - w.x);
targetX += Math.cos(angle) * displacement * 0.25;
maxB = Math.max(maxB, Math.abs(displacement));
}
}
// Плавное следование цели
p.x += (targetX - p.x) * CFG.pointEase;
p.y += (targetY - p.y) * CFG.pointEase;
p.brightness = maxB;
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
const rows = points.rows;
const cols = points.cols;
// Сетка (нити)
ctx.lineCap = 'round';
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const i = r * cols + c;
const p = points[i];
if (!p) continue;
// Вправо
if (c < cols - 1) {
const n = points[i + 1];
const dist = Math.hypot(p.x - n.x, p.y - n.y);
const stretch = Math.abs(dist - CFG.spacing);
const alpha = 0.035 + Math.min(1, stretch / 8) * 0.11;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(n.x, n.y);
ctx.strokeStyle = `rgba(95, 130, 170, ${alpha})`;
ctx.lineWidth = 0.6;
ctx.stroke();
}
// Вниз
if (r < rows - 1) {
const n = points[i + cols];
const dist = Math.hypot(p.x - n.x, p.y - n.y);
const stretch = Math.abs(dist - CFG.spacing);
const alpha = 0.035 + Math.min(1, stretch / 8) * 0.11;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(n.x, n.y);
ctx.strokeStyle = `rgba(95, 130, 170, ${alpha})`;
ctx.lineWidth = 0.6;
ctx.stroke();
}
}
}
// Буйки (точки)
for (const p of points) {
const b = Math.min(1, p.brightness * 0.12);
const size = 1.0 + b * 1.8;
ctx.beginPath();
ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(130, 170, 210, ${0.22 + b * 0.55})`;
ctx.fill();
}
}
function animate() {
if (!W || !H) { requestAnimationFrame(animate); return; }
update();
draw();
requestAnimationFrame(animate);
}
target.addEventListener('mousemove', e => {
const r = target.getBoundingClientRect();
const mx = e.clientX - r.left;
const my = e.clientY - r.top;
spawnWave(mx, my);
});
target.addEventListener('mouseleave', () => {});
window.addEventListener('resize', () => {
resize();
initGrid();
});
if (document.readyState === 'complete') {
resize();
initGrid();
requestAnimationFrame(animate);
} else {
window.addEventListener('load', () => {
resize();
initGrid();
requestAnimationFrame(animate);
});
}
});
})();
</script> Form Reveal - Text, logo effecct
<style>
.particles-here {
position: relative !important;
overflow: hidden !important;
}
.particles-here .ep-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.particles-here > *:not(.ep-canvas),
.particles-here > .e-con-inner > * {
position: relative;
z-index: 1;
}
</style>
<script>
(function() {
const containers = document.querySelectorAll('.particles-here');
if (!containers.length) return;
const CONFIG = {
text: 'AISITELAB',
fontFamily: 'sans-serif',
colorBase: 'rgba(140, 195, 245, 0.85)',
colorActive: 'rgba(170, 230, 255, 1.0)',
particleSize: 1.6,
scatterForce: 4.5,
returnSpeed: 0.045,
friction: 0.91,
mouseRadius: 90,
pixelStep: 4
};
const PI2 = Math.PI * 2;
containers.forEach(container => {
const target = container.querySelector('.e-con-inner') || container;
const canvas = document.createElement('canvas');
canvas.className = 'ep-canvas';
target.insertBefore(canvas, target.firstChild);
const ctx = canvas.getContext('2d');
let W = 0, H = 0, particles = [];
let mouseX = -9999, mouseY = -9999;
function resize() {
const rect = target.getBoundingClientRect();
W = rect.width;
H = rect.height;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
return W > 0 && H > 0;
}
function getTextPoints() {
const off = document.createElement('canvas');
off.width = W;
off.height = H;
const octx = off.getContext('2d');
let fontSize = Math.min(W, H) * 0.46;
octx.font = `900 ${fontSize}px ${CONFIG.fontFamily}`;
const tw = octx.measureText(CONFIG.text).width;
if (tw > W * 0.9) fontSize *= (W * 0.9) / tw;
octx.font = `900 ${fontSize}px ${CONFIG.fontFamily}`;
octx.fillStyle = '#fff';
octx.textAlign = 'center';
octx.textBaseline = 'middle';
octx.fillText(CONFIG.text, W / 2, H / 2);
const img = octx.getImageData(0, 0, W, H);
const d = img.data;
const pts = [];
const step = CONFIG.pixelStep;
for (let y = 0; y < H; y += step) {
const row = y * W;
for (let x = 0; x < W; x += step) {
if (d[(row + x) * 4 + 3] > 100) pts.push({ x, y });
}
}
return pts;
}
function init() {
if (!resize()) return;
const targets = getTextPoints();
particles = [];
for (let i = 0; i < targets.length; i++) {
const t = targets[i];
particles.push({
tx: t.x, ty: t.y,
x: t.x + (Math.random() - 0.5) * 60,
y: t.y + (Math.random() - 0.5) * 60,
vx: 0, vy: 0
});
}
}
function animate() {
if (!W) { requestAnimationFrame(animate); return; }
ctx.clearRect(0, 0, W, H);
const mr = CONFIG.mouseRadius;
const ms = mr * mr;
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
// Возврат к букве
p.vx += (p.tx - p.x) * CONFIG.returnSpeed;
p.vy += (p.ty - p.y) * CONFIG.returnSpeed;
// Отталкивание от мыши (упрощённое)
const dx = p.x - mouseX;
const dy = p.y - mouseY;
const distSq = dx * dx + dy * dy;
let active = 0;
if (distSq < ms && distSq > 0.25) {
const dist = Math.sqrt(distSq);
const t = 1 - dist / mr;
active = t * t;
const f = active * CONFIG.scatterForce / dist;
p.vx += dx * f;
p.vy += dy * f;
}
p.vx *= CONFIG.friction;
p.vy *= CONFIG.friction;
p.x += p.vx;
p.y += p.vy;
// Рисование
ctx.beginPath();
ctx.arc(p.x, p.y, CONFIG.particleSize + active * 0.8, 0, PI2);
if (active > 0.1) {
ctx.fillStyle = CONFIG.colorActive;
ctx.shadowBlur = 10;
ctx.shadowColor = CONFIG.colorActive;
} else {
ctx.fillStyle = CONFIG.colorBase;
ctx.shadowBlur = 0;
}
ctx.fill();
ctx.shadowBlur = 0;
}
requestAnimationFrame(animate);
}
target.addEventListener('mousemove', e => {
const r = target.getBoundingClientRect();
mouseX = e.clientX - r.left;
mouseY = e.clientY - r.top;
});
target.addEventListener('mouseleave', () => {
mouseX = -9999;
mouseY = -9999;
});
window.addEventListener('resize', () => { resize(); init(); });
function start() {
resize();
init();
requestAnimationFrame(animate);
}
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(start);
} else {
setTimeout(start, 400);
}
});
})();
</script> Flashlight / Spotlight Reveal - cool hidden effect
<style>
.particles-here {
position: relative !important;
overflow: hidden !important;
background: #0b1018 !important;
}
.particles-here .elementor-heading-title,
.particles-here h1, .particles-here h2, .particles-here h3 {
color: #ffffff !important;
position: relative;
z-index: 1;
}
.particles-here .ep-spotlight {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: none;
}
</style>
<script>
(function() {
const containers = document.querySelectorAll('.particles-here');
if (!containers.length) return;
containers.forEach(container => {
// Удаляем старый canvas, если перезагрузка
const old = container.querySelector('.ep-spotlight');
if (old) old.remove();
const canvas = document.createElement('canvas');
canvas.className = 'ep-spotlight';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
let W = 0, H = 0;
const mouse = { x: -1000, y: -1000, active: false };
const light = { x: 0, y: 0, angle: 0, stretch: 0 };
let lastMx = 0, lastMy = 0;
function resize() {
const rect = container.getBoundingClientRect();
W = rect.width;
H = rect.height;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function updateLight() {
light.x += (mouse.x - light.x) * 0.12;
light.y += (mouse.y - light.y) * 0.12;
const dx = mouse.x - lastMx;
const dy = mouse.y - lastMy;
const speed = Math.hypot(dx, dy);
const targetAngle = Math.atan2(dy, dx);
let da = targetAngle - light.angle;
while (da > Math.PI) da -= Math.PI * 2;
while (da < -Math.PI) da += Math.PI * 2;
light.angle += da * 0.12;
const targetStretch = Math.min(1, speed * 0.06);
light.stretch += (targetStretch - light.stretch) * 0.1;
lastMx = mouse.x;
lastMy = mouse.y;
}
function draw() {
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = '#0b1018';
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = 'destination-out';
const baseR = 100;
const r = baseR + light.stretch * 50;
const rPerp = baseR - light.stretch * 30;
ctx.save();
ctx.translate(light.x, light.y);
ctx.rotate(light.angle);
ctx.scale(1, rPerp / r);
const grad = ctx.createRadialGradient(0, 0, 0, 0, 0, r);
grad.addColorStop(0, 'rgba(255,255,255,1)');
grad.addColorStop(0.35, 'rgba(255,255,255,0.85)');
grad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.globalCompositeOperation = 'source-over';
}
function animate(time) {
if (!W) { requestAnimationFrame(animate); return; }
if (!mouse.active) {
const cx = W / 2, cy = H / 2;
mouse.x = cx + Math.sin(time * 0.0005) * (W * 0.32);
mouse.y = cy + Math.cos(time * 0.00035) * (H * 0.22);
}
updateLight();
draw();
requestAnimationFrame(animate);
}
container.addEventListener('mousemove', e => {
const rect = container.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
mouse.active = true;
});
container.addEventListener('mouseleave', () => {
mouse.active = false;
});
window.addEventListener('resize', resize);
if (document.readyState === 'complete') {
resize();
requestAnimationFrame(animate);
} else {
window.addEventListener('load', () => { resize(); requestAnimationFrame(animate); });
}
});
})();
</script> Share post: