Интерактивная система частиц с физикой на JavaScript
Размер текста: A+ A-

Интерактивная система частиц с физикой на JavaScript

Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]

Интерактивная система частиц на чистом JavaScript – это впечатляющая визуализация с физическим движением, гравитацией и динамическими соединениями.

Реализация использует современные веб-технологии для создания immersive опыта с реальной физикой частиц, отталкиванием и притяжением.

Полностью настраиваемая система демонстрирует возможности Canvas API и объектно-ориентированного программирования в браузере: Ссылка на демонстрацию работы кода.

Поставленная задача:

  1. Создать интерактивную систему частиц с физикой и гравитацией

  2. Частицы должны взаимодействовать друг с другом и с курсором мыши

  3. Должна быть возможность настраивать различные параметры системы:

    • Количество частиц

    • Силу гравитации

    • Силу отталкивания

    • Расстояние соединения между частицами

    • Цветовую схему

  4. Должны быть реализованы специальные эффекты:

    • Взрыв (разбрасывает частицы)

    • Притяжение (собирает частицы в центре)

    • Сброс системы

  5. Визуализация должна включать:

    • Плавное движение частиц

    • Соединения между близкими частицами

    • Пульсацию размера частиц

    • Эффект размытия при движении

    • Счетчик ФПС

Это лишь депонстрация работы кода и возможностей JS в 2025 году.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Интерактивная система частиц</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
}
canvas {
display: block;
}
.controls {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 10px;
backdrop-filter: blur(5px);
max-width: 300px;
}
h2 {
margin-top: 0;
color: #4fc3f7;
}
.control-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-size: 14px;
}
input[type="range"] {
width: 100%;
}
button {
background: #4fc3f7;
border: none;
color: white;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
margin-right: 10px;
margin-bottom: 10px;
transition: background 0.3s;
}
button:hover {
background: #29b6f6;
}
.info {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px 15px;
border-radius: 10px;
font-size: 14px;
backdrop-filter: blur(5px);
}
.color-picker {
display: flex;
gap: 5px;
margin-top: 5px;
}
.color-option {
width: 25px;
height: 25px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
}
.color-option.active {
border-color: white;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div class="controls">
<h2>Управление частицами</h2>
<div class="control-group">
<label for="particleCount">Количество частиц: <span id="countValue">200</span></label>
<input type="range" id="particleCount" min="50" max="1000" value="200">
</div>
<div class="control-group">
<label for="gravity">Гравитация: <span id="gravityValue">0.5</span></label>
<input type="range" id="gravity" min="0" max="2" step="0.1" value="0.5">
</div>
<div class="control-group">
<label for="repulsion">Отталкивание: <span id="repulsionValue">1.0</span></label>
<input type="range" id="repulsion" min="0" max="3" step="0.1" value="1.0">
</div>
<div class="control-group">
<label for="connectionDistance">Расстояние соединения: <span id="distanceValue">100</span></label>
<input type="range" id="connectionDistance" min="10" max="200" value="100">
</div>
<div class="control-group">
<label>Цветовая схема:</label>
<div class="color-picker">
<div class="color-option active" style="background: linear-gradient(45deg, #ff0080, #00ffcc);" data-scheme="rainbow"></div>
<div class="color-option" style="background: linear-gradient(45deg, #ff6b6b, #ffd93d);" data-scheme="fire"></div>
<div class="color-option" style="background: linear-gradient(45deg, #5ee7df, #b490ca);" data-scheme="ocean"></div>
<div class="color-option" style="background: linear-gradient(45deg, #667eea, #764ba2);" data-scheme="purple"></div>
</div>
</div>
<div class="control-group">
<button id="resetBtn">Сбросить</button>
<button id="explodeBtn">Взрыв</button>
<button id="attractBtn">Притяжение</button>
</div>
</div>
<div class="info">
Частиц: <span id="particleCountInfo">0</span> | FPS: <span id="fpsInfo">0</span>
</div>
<script>
// Получаем элементы DOM
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const particleCountInput = document.getElementById('particleCount');
const gravityInput = document.getElementById('gravity');
const repulsionInput = document.getElementById('repulsion');
const connectionDistanceInput = document.getElementById('connectionDistance');
const resetBtn = document.getElementById('resetBtn');
const explodeBtn = document.getElementById('explodeBtn');
const attractBtn = document.getElementById('attractBtn');
const colorOptions = document.querySelectorAll('.color-option');
const countValue = document.getElementById('countValue');
const gravityValue = document.getElementById('gravityValue');
const repulsionValue = document.getElementById('repulsionValue');
const distanceValue = document.getElementById('distanceValue');
const particleCountInfo = document.getElementById('particleCountInfo');
const fpsInfo = document.getElementById('fpsInfo');
// Устанавливаем размеры canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Переменные для отслеживания FPS
let frameCount = 0;
let lastTime = performance.now();
let fps = 0;
// Настройки системы частиц
let config = {
particleCount: 200,
gravity: 0.5,
repulsion: 1.0,
connectionDistance: 100,
colorScheme: 'rainbow',
mouseX: 0,
mouseY: 0,
mouseRadius: 150,
isMousePressed: false
};
// Массив частиц
let particles = [];
// Класс частицы
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
this.radius = Math.random() * 3 + 1;
this.baseRadius = this.radius;
this.color = this.getColor();
this.life = 1;
this.decay = Math.random() * 0.005 + 0.002;
this.connections = [];
}
getColor() {
switch(config.colorScheme) {
case 'rainbow':
return `hsl(${Math.random() * 360}, 70%, 60%)`;
case 'fire':
const hue = Math.random() * 30 + 10;
return `hsl(${hue}, 100%, ${Math.random() * 30 + 50}%)`;
case 'ocean':
const blueHue = Math.random() * 60 + 180;
return `hsl(${blueHue}, 70%, ${Math.random() * 30 + 50}%)`;
case 'purple':
const purpleHue = Math.random() * 60 + 270;
return `hsl(${purpleHue}, 70%, ${Math.random() * 30 + 50}%)`;
default:
return `hsl(${Math.random() * 360}, 70%, 60%)`;
}
}
update() {
// Применяем гравитацию к центру
const dx = canvas.width / 2 - this.x;
const dy = canvas.height / 2 - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
this.vx += (dx / distance) * config.gravity * 0.01;
this.vy += (dy / distance) * config.gravity * 0.01;
}
// Взаимодействие с мышью
const mouseDx = config.mouseX - this.x;
const mouseDy = config.mouseY - this.y;
const mouseDistance = Math.sqrt(mouseDx * mouseDx + mouseDy * mouseDy);
if (mouseDistance < config.mouseRadius) {
if (config.isMousePressed) {
// Притяжение к курсору
this.vx += (mouseDx / mouseDistance) * 0.5;
this.vy += (mouseDy / mouseDistance) * 0.5;
} else {
// Отталкивание от курсора
this.vx -= (mouseDx / mouseDistance) * config.repulsion * 0.1;
this.vy -= (mouseDy / mouseDistance) * config.repulsion * 0.1;
}
}
// Обновляем позицию
this.x += this.vx;
this.y += this.vy;
// Замедление
this.vx *= 0.99;
this.vy *= 0.99;
// Отскок от границ
if (this.x < 0 || this.x > canvas.width) {
this.vx *= -0.8;
this.x = this.x < 0 ? 0 : canvas.width;
}
if (this.y < 0 || this.y > canvas.height) {
this.vy *= -0.8;
this.y = this.y < 0 ? 0 : canvas.height;
}
// Обновляем жизненный цикл
this.life -= this.decay;
if (this.life <= 0) {
this.reset();
}
// Пульсация радиуса
this.radius = this.baseRadius + Math.sin(Date.now() * 0.005) * 0.5;
}
reset() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
this.life = 1;
this.color = this.getColor();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
// Инициализация системы частиц
function initParticles() {
particles = [];
for (let i = 0; i < config.particleCount; i++) {
particles.push(new Particle(
Math.random() * canvas.width,
Math.random() * canvas.height
));
}
}
// Рисование соединений между частицами
function drawConnections() {
for (let i = 0; i < particles.length; i++) {
particles[i].connections = [];
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.connectionDistance) {
particles[i].connections.push({
particle: particles[j],
distance: distance
});
const opacity = 1 - (distance / config.connectionDistance);
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.2})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}
}
}
// Основной цикл анимации
function animate() {
// Очистка canvas с эффектом размытия
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Обновление и отрисовка частиц
particles.forEach(particle => {
particle.update();
particle.draw();
});
// Рисование соединений
drawConnections();
// Обновление информации о FPS
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
frameCount = 0;
lastTime = currentTime;
fpsInfo.textContent = fps;
particleCountInfo.textContent = particles.length;
}
requestAnimationFrame(animate);
}
// Обработчики событий
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
canvas.addEventListener('mousemove', (e) => {
config.mouseX = e.clientX;
config.mouseY = e.clientY;
});
canvas.addEventListener('mousedown', () => {
config.isMousePressed = true;
});
canvas.addEventListener('mouseup', () => {
config.isMousePressed = false;
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
config.mouseX = e.touches[0].clientX;
config.mouseY = e.touches[0].clientY;
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
config.isMousePressed = true;
config.mouseX = e.touches[0].clientX;
config.mouseY = e.touches[0].clientY;
});
canvas.addEventListener('touchend', () => {
config.isMousePressed = false;
});
// Обработчики элементов управления
particleCountInput.addEventListener('input', () => {
const newCount = parseInt(particleCountInput.value);
config.particleCount = newCount;
countValue.textContent = newCount;
if (newCount > particles.length) {
// Добавляем новые частицы
for (let i = particles.length; i < newCount; i++) {
particles.push(new Particle(
Math.random() * canvas.width,
Math.random() * canvas.height
));
}
} else if (newCount < particles.length) {
// Удаляем лишние частицы
particles.length = newCount;
}
});
gravityInput.addEventListener('input', () => {
config.gravity = parseFloat(gravityInput.value);
gravityValue.textContent = config.gravity;
});
repulsionInput.addEventListener('input', () => {
config.repulsion = parseFloat(repulsionInput.value);
repulsionValue.textContent = config.repulsion;
});
connectionDistanceInput.addEventListener('input', () => {
config.connectionDistance = parseInt(connectionDistanceInput.value);
distanceValue.textContent = config.connectionDistance;
});
resetBtn.addEventListener('click', () => {
initParticles();
});
explodeBtn.addEventListener('click', () => {
particles.forEach(particle => {
const angle = Math.atan2(particle.y - canvas.height/2, particle.x - canvas.width/2);
const force = 5 + Math.random() * 5;
particle.vx = Math.cos(angle) * force;
particle.vy = Math.sin(angle) * force;
});
});
attractBtn.addEventListener('click', () => {
particles.forEach(particle => {
const dx = canvas.width/2 - particle.x;
const dy = canvas.height/2 - particle.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
particle.vx += (dx / distance) * 0.5;
particle.vy += (dy / distance) * 0.5;
}
});
});
colorOptions.forEach(option => {
option.addEventListener('click', () => {
colorOptions.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
config.colorScheme = option.getAttribute('data-scheme');
// Обновляем цвета всех частиц
particles.forEach(particle => {
particle.color = particle.getColor();
});
});
});
// Запуск системы
initParticles();
animate();
</script>
</body>
</html>
Нажмите, чтобы оценить наш труд:
[Всего: 1 Средняя: 5]
Ethan Carter

Я, Итан Картер – американский разработчик и технический автор с более чем 20-летним опытом в системном и прикладном программировании. Мой основной профиль — низкоуровневая разработка на Assembler: 22 года практики, включая глубокую работу с оптимизацией кода, архитектурой процессоров и производительностью критичных по скорости решений. Я защитил PhD dissertation по Assembler, а также более 18 лет работаю с ASP.NET, создавая корпоративные веб-системы, API и масштабируемые backend-решения.

Дополнительно я имею 9 лет опыта в C++ и C#, а также 7 лет практики программирования микроконтроллеров на Assembler. Благодаря моему сочетанию академической подготовки и прикладного инженерного опыта я могу писать статьи на стыке архитектуры ПО, низкоуровневой оптимизации и современной разработки, делая сложные технические темы понятными для профессиональной аудитории.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.

О нас | Контакты


Прокрутить вверх