Найти в Дзене
IlyaDev

Создание эффекта падающих снежинок поверх контента с использованием React и Canvas.

Привет друг ! В этой статье попробуем создать эффект падающих снежинок поверх веб-контента, используя React и HTML5 Canvas. Этот эффект добавит праздничное настроение твоему сайту или приложению 🎅. С моими любыми комментариями в коде 🤭. Чего мы хотим добиться? тут 🥸. И так - поехали !🛼 Создаём компоненту CircleCanvas.tsx и добавим в него следующий код: interface Circle {
x: number;
y: number;
radius: number;
speedY: number;
speedX: number;
offsetX: number;
}
export type Nullable<T> = T | null; Этот интерфейс описывает параметры одной снежинки. Мы будем использовать их для управления движением и анимацией. Внутри нашего компонента создаем ссылку на canvas и функцию для автоматического изменения его размеров при изменении окна: // Инициализация и обновление размеров canvas const canvasRef = useRef<Nullable<HTMLCanvasElement>>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
Оглавление

Привет друг ! В этой статье попробуем создать эффект падающих снежинок поверх веб-контента, используя React и HTML5 Canvas. Этот эффект добавит праздничное настроение твоему сайту или приложению 🎅.

const currentMonth = new Date().getMonth(); {currentMonth === 4 && <CircleCanvas />}
const currentMonth = new Date().getMonth(); {currentMonth === 4 && <CircleCanvas />}

С моими любыми комментариями в коде 🤭.

Чего мы хотим добиться? тут 🥸.

И так - поехали !🛼

1. Создаём компоненту CircleCanvas

Создаём компоненту CircleCanvas.tsx и добавим в него следующий код:

2. Интерфейс снежинки

interface Circle {
x: number;
y: number;
radius: number;
speedY: number;
speedX: number;
offsetX: number;
}

export type Nullable<T> = T | null;

Этот интерфейс описывает параметры одной снежинки. Мы будем использовать их для управления движением и анимацией.

3. Настройка Canvas

Внутри нашего компонента создаем ссылку на canvas и функцию для автоматического изменения его размеров при изменении окна:

// Инициализация и обновление размеров canvas
const canvasRef = useRef<Nullable<HTMLCanvasElement>>(null);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
if (!context) return;

const resizeCanvas = () => {
canvas.width =
window.innerWidth;
canvas.height =
window.innerHeight;
};

resizeCanvas();
window.addEventListener('resize', resizeCanvas);

return () => {
window.removeEventListener('resize', resizeCanvas);
};
}, []);

4. Функция создания снежинки

Каждая снежинка создается со случайным положением, скоростью и размером:

const createCircle = (): Circle => {
return {
x:
Math.random() * window.innerWidth, // случайная позиция по ширине
y: -50, // начинаем выше экрана
radius:
Math.random() * 10 + 5, // радиус от 5 до 15
speedY:
Math.random() * 2 - 1, // скорость падения
speedX:
Math.random() * 2 - 1, // горизонтальная скорость (S-образное движение)
offsetX: 0, // смещение по X для S-образного движения
};
};

5. Анимация снежинок

Используем requestAnimationFrame для плавной анимации снежинок:

const animate = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
if (!context) return;

// Очищаем canvas
context.clearRect(0, 0, canvas.width, canvas.height);

// Обновляем и рисуем снежинки
circles.current.forEach((circle) => {
circle.y += circle.speedY;
circle.offsetX += circle.speedX;
circle.x +=
Math.sin(circle.offsetX / 50) * 2; // S-образное движение

// Рисуем
снежинку
context.beginPath();
context.arc(circle.x, circle.y, circle.radius, 0,
Math.PI * 2);
context.fillStyle = 'rgba(173,184,197,0.7)'; // Цвет
снежинки
context.fill();
context.closePath();
});

// Удаляем
снежинки, которые вышли за экран
circles.current = circles.current.filter(circle => circle.y <
window.innerHeight + 50);

// Добавляем новую
снежинку с некоторой вероятностью
if (
Math.random() < 0.02) {
circles.current.push(createCircle());
}

// Продолжаем анимацию
animationFrameId.current = requestAnimationFrame(animate);
};

6. Запуск анимации

Подключаем анимацию при монтировании компонента:

useEffect(() => {
animationFrameId.current = requestAnimationFrame(animate);

return () => {
if (animationFrameId.current) {
cancelAnimationFrame(animationFrameId.current);
}
};
}, []);

7. Рендер Canvas

Canvas располагается поверх контента, не мешая пользователю взаимодействовать с остальной страницей:

<canvas
ref={canvasRef}
style={{
position: 'fixed',
inset: 0,
zIndex: 100,
display: 'block',
pointerEvents: 'none',
}}
/>

8. Итоговый код

Теперь у вас есть готовый компонент CircleCanvas, который добавляет эффект снежинок:

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

interface Circle {
x: number;
y: number;
radius: number;
speedY: number;
speedX: number;
offsetX: number;
}

export type Nullable<T> = T | null;

export const CircleCanvas: React.FC = () => {
const canvasRef = useRef<Nullable<HTMLCanvasElement>>(null);
const circles = useRef<Circle[]>([]);
const animationFrameId = useRef<Nullable<number>>(null);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
if (!context) return;

const resizeCanvas = () => {
canvas.width =
window.innerWidth;
canvas.height =
window.innerHeight;
};

resizeCanvas();
window.addEventListener('resize', resizeCanvas);

return () => {
window.removeEventListener('resize', resizeCanvas);
};
}, []);

const createCircle = (): Circle => {
return {
x:
Math.random() * window.innerWidth,
y: -50,
radius:
Math.random() * 10 + 5,
speedY:
Math.random() * 2 - 1,
speedX:
Math.random() * 2 - 1,
offsetX: 0,
};
};

const animate = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const context = canvas.getContext('2d');
if (!context) return;

context.clearRect(0, 0, canvas.width, canvas.height);

circles.current.forEach((circle) => {
circle.y += circle.speedY;
circle.offsetX += circle.speedX;
circle.x +=
Math.sin(circle.offsetX / 50) * 2;

context.beginPath();
context.arc(circle.x, circle.y, circle.radius, 0,
Math.PI * 2);
context.fillStyle = 'rgba(173,184,197,0.7)';
context.fill();
context.closePath();
});

circles.current = circles.current.filter(circle => circle.y <
window.innerHeight + 50);

if (
Math.random() < 0.02) {
circles.current.push(createCircle());
}

animationFrameId.current = requestAnimationFrame(animate);
};

useEffect(() => {
animationFrameId.current = requestAnimationFrame(animate);

return () => {
if (animationFrameId.current) {
cancelAnimationFrame(animationFrameId.current);
}
};
}, []);

return (
<canvas
ref={canvasRef}
style={{
position: 'fixed',
inset: 0,
zIndex: 100,
display: 'block',
pointerEvents: 'none',
}}
/>
);
};

Теперь мы можем использовать CircleCanvas в любом месте нашего приложения! Да и вообще мы молодцы 🧸!

Спасибо за внимание! До Новых Встреч!🤗🤗🤗