Компас D-Lab · занятие 4

Столкновения частиц и управление моделью

Расширяем знания HTML и CSS, повторяем конструкции JavaScript и превращаем модель частиц в управляемую программу.

55 + 10 + 55 минут
Введение · 5 минут

От движущихся частиц к управляемой модели

К концу занятия модель будет обнаруживать сближение частиц, отмечать столкновения и реагировать на команды пользователя.

2

Основные результаты

  • повторить изученные средства HTML, CSS и JavaScript;
  • освоить новые элементы интерфейса;
  • перебрать все пары частиц;
  • определить столкновение по расстоянию между центрами;
  • назначить обработчики событий через addEventListener.

Красный цвет показывает, что частица соприкасается хотя бы с одной другой частицей.

Справочник · HTML

Структура и элементы страницы

HTML (HyperText Markup Language — язык гипертекстовой разметки) описывает структуру и содержание документа. Теги обозначают элементы страницы, а атрибуты уточняют их свойства и связи.

КонструкцияНазначениеПример
<!doctype html>Указывает современный режим обработки HTML.<!doctype html>
<html>Содержит весь HTML-документ.<html lang="ru">
<head>Содержит служебные сведения и подключения файлов.<title>Проект</title>
<body>Содержит видимую часть страницы.<body>...</body>
<h1>–<h3>Создают заголовки разных уровней.<h1>Модель частиц</h1>
<p>, <div>Создают абзац и универсальный контейнер.<p>Описание</p>
<button>Создаёт кнопку.<button id="start">Пуск</button>
<canvas>Создаёт область для программного рисования.<canvas width="600" height="300">
<link>Подключает внешний файл CSS.<link rel="stylesheet" href="style.css">
<script>Подключает или содержит JavaScript.<script src="script.js" defer></script>
id, classЗадают уникальное имя и имя группы элементов.id="scene" class="panel"
КомментарийОставляет пояснение, которое не отображается на странице.<!-- панель управления -->
Правило. Значение id должно быть уникальным на странице. Одно значение class можно назначить нескольким элементам.
Справочник · CSS

Оформление элементов

CSS (Cascading Style Sheets — каскадные таблицы стилей) описывает представление документа. Правило CSS состоит из селектора и блока объявлений. Каждое объявление содержит свойство и его значение.

Селекторы

ЗаписьЧто выбирает
buttonВсе элементы <button>.
.panelВсе элементы класса panel.
#sceneЭлемент с идентификатором scene.
button:hoverКнопку, на которую наведён указатель.

Общий вид правила

.panel {
  background-color: white;
  padding: 12px;
  border: 1px solid green;
}

.panel — селектор; padding — свойство; 12px — значение; вся строка padding: 12px; — объявление.

СвойствоНазначениеПример
colorЦвет текста.color: white;
background-colorЦвет фона.background-color: #123d32;
font-family, font-sizeГарнитура и размер шрифта.font-size: 18px;
text-alignВыравнивание текста.text-align: center;
width, heightШирина и высота элемента.width: 300px;
paddingВнутренний отступ.padding: 12px;
marginВнешний отступ.margin: 10px;
borderРамка элемента.border: 1px solid green;
border-radiusСкругление углов.border-radius: 8px;
Комментарий CSS: /* пояснение */. Комментарий помогает описать назначение правила и не влияет на оформление.
Справочник · JavaScript

Команды, данные и рисование

JavaScript — язык программирования, который задаёт поведение страницы: выполняет вычисления, изменяет элементы, обрабатывает события и управляет графикой Canvas.

Страница и функции
КонструкцияНазначение
document.getElementById("id")Находит элемент по идентификатору.
element.textContent = "..."Изменяет текст элемента.
element.style.backgroundColorИзменяет цвет фона через JavaScript.
function draw() { ... }Объявляет функцию.
draw()Вызывает функцию.
let, constОбъявляют изменяемую переменную и постоянную ссылку.
Массивы, объекты и циклы
КонструкцияНазначение
[10, 20, 30]Создаёт массив.
{x: 10, y: 20}Создаёт объект со свойствами.
array[0], p.xЧитают элемент массива и свойство объекта.
array.push(value)Добавляет значение в конец массива.
array.lengthВозвращает количество элементов.
for (...)Повторяет команды, пока условие истинно.
array.forEach(p => {...})Выполняет функцию для каждого элемента массива.
Движение и условия
КонструкцияНазначение
Math.random()Возвращает случайное число от 0 включительно до 1 не включительно.
setInterval(tick, 30)Повторяет вызов функции через заданный интервал.
p.x += p.vxУвеличивает координату на величину скорости.
if (условие) {...}Выполняет блок команд только при истинном условии.
< > <= >= ===Сравнивают значения.
a || bИстинно, если истинно хотя бы одно условие.
Canvas
КомандаНазначение
canvas.getContext("2d")Получает двумерный контекст рисования.
ctx.clearRect(...)Очищает прямоугольную область.
ctx.beginPath()Начинает новый контур.
ctx.arc(x, y, r, 0, Math.PI * 2)Добавляет окружность к текущему контуру.
ctx.fill()Закрашивает текущий контур.
Расширение HTML · 8 минут

Элементы управления и смысловые разделы

Новые элементы сразу используются в панели управления моделью. Они уточняют назначение частей документа и делают интерфейс понятнее.

<section>

Объединяет тематически связанную часть документа. Раздел обычно имеет собственный заголовок.

<section>
  <h2>Управление</h2>
  ...
</section>

<label>

Создаёт подпись элемента управления. Атрибут for содержит id связанного элемента.

<label for="speed">Скорость</label>

<input type="range">

Создаёт ползунок. Атрибуты min, max, step и value задают допустимые значения.

<input id="speed" type="range"
       min="1" max="5" step="1" value="2">

<output>

Предназначен для вывода результата вычисления или текущего значения элемента управления.

<output id="speedValue"
        for="speed">2</output>
3
Связь элементов. Значения for="speed" и id="speed" должны совпадать. При выборе подписи браузер передаёт фокус связанному элементу управления.
Расширение CSS · 8 минут

Компоновка и состояния интерфейса

display: flex

Превращает элемент в flex-контейнер. Его непосредственные дочерние элементы располагаются вдоль основной оси.

gap

Задаёт промежуток между элементами flex-контейнера без отдельных внешних отступов у каждого элемента.

:hover и transition

Псевдокласс :hover выбирает элемент при наведении указателя, а transition задаёт плавность изменения указанных свойств.

Пользовательские свойства

Запись --accent: blue; создаёт пользовательское свойство, а var(--accent) получает его значение.

.controls {
  display: flex;
  gap: 10px;
  align-items: center;
}
.chip {
  --accent: #165d8d;
  border-color: var(--accent);
  transition: 0.2s;
}
.chip:hover {
  color: white;
  background: var(--accent);
}
Наведите указатель Flex-контейнер Промежуток gap
Актуализация · задания 1–3

HTML и CSS

от распознавания к записи

Задания используют изученные темы, но имеют новые формулировки и примеры.

1. Какая запись правильно связывает подпись с ползунком?
Выберите один ответ.
2. Как будет выглядеть контейнер с правилом display: flex; gap: 12px; при стандартном направлении?
Выберите один ответ.
3. Запишите атрибут подписи, который связывает её с элементом id="speed".
Актуализация · задания 4–6

Массивы и циклы

анализ кода
4. Что обозначает выражение particles[2].vx?
Выберите один ответ.
5. В массиве четыре частицы. Сколько различных пар можно составить, если порядок частиц в паре не учитывается?
6. Дополните начало внутреннего цикла так, чтобы частица не сравнивалась с собой и каждая пара проверялась один раз:
for (let j = ______; j < particles.length; j++)
Актуализация · задания 7–9

Скорость и условия

применение
7. Координата частицы равна x = 70, а скорость vx = -2. Чему станет равна координата после команды x += vx?
8. Какое условие соответствует соприкосновению или пересечению двух кругов?
Выберите один ответ.
9. Запишите условие, которое становится истинным, когда правая точка частицы вышла за правую границу Canvas.
Практика · 8 минут

Проверьте основу модели

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

function tick() {
  particles.forEach(p => {
    p.x += p.vx;
    p.y += p.vy;

    if (p.x < p.r ||
        p.x > canvas.width - p.r) {
      p.vx = -p.vx;
    }

    if (p.y < p.r ||
        p.y > canvas.height - p.r) {
      p.vy = -p.vy;
    }
  });
  draw();
}

Проверка

Если основа ещё не работает, сначала исправьте её. Новый код обнаружения столкновений добавляется только после этой проверки.
Новая тема · 10 минут

Как перебрать все пары частиц

Один цикл выбирает первую частицу пары. Второй цикл выбирает вторую частицу из элементов, расположенных правее в массиве. Поэтому частица не сравнивается с собой, а одна и та же пара не проверяется дважды.

for (let i = 0;
     i < particles.length;
     i++) {

  for (let j = i + 1;
       j < particles.length;
       j++) {

    const a = particles[i];
    const b = particles[j];
    // здесь проверяем пару a и b
  }
}

Пример для четырёх частиц

Индексы частиц: 0, 1, 2, 3.

Проверяемые пары:

(0,1), (0,2), (0,3), (1,2), (1,3), (2,3).

Всего получается шесть различных пар.

Почему j = i + 1? Значение j = i сравнило бы частицу с самой собой. Значение меньше i повторило бы уже проверенную пару в обратном порядке.
Столкновение · 15 минут

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

Два круга соприкасаются или пересекаются, если расстояние между их центрами не превышает сумму радиусов.

Алгоритм

  1. Найдите разности координат: dx и dy.
  2. Вычислите расстояние по теореме Пифагора.
  3. Сравните расстояние с суммой радиусов.
function distance(a, b) {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  return Math.sqrt(dx * dx + dy * dy);
}

function isColliding(a, b) {
  return distance(a, b) <= a.r + b.r;
}
РасстояниеСумма радиусовВывод
3020Между кругами есть промежуток.
2020Круги соприкасаются.
1620Круги пересекаются.
Math.sqrt(value) вычисляет квадратный корень. Выражение dx * dx + dy * dy соответствует сумме квадратов катетов.
Практика · 15 минут

Отметьте столкнувшиеся частицы

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

function findCollisions() {
  particles.forEach(p => {
    p.hit = false;
  });

  for (let i = 0; i < particles.length; i++) {
    for (let j = i + 1;
         j < particles.length; j++) {
      const a = particles[i];
      const b = particles[j];

      if (isColliding(a, b)) {
        a.hit = true;
        b.hit = true;
      }
    }
  }
}
function drawParticle(p) {
  ctx.beginPath();
  ctx.arc(p.x, p.y, p.r,
          0, Math.PI * 2);

  if (p.hit) {
    ctx.fillStyle = "#c8413a";
  } else {
    ctx.fillStyle = "#1d5a49";
  }

  ctx.fill();
}
Порядок одного шага: изменить координаты → проверить границы → найти столкновения → очистить Canvas → нарисовать новое состояние.
Дополнительная задача: упрощённое изменение движения

Можно поменять направления движения обеих частиц:

if (isColliding(a, b)) {
  a.vx = -a.vx;
  a.vy = -a.vy;
  b.vx = -b.vx;
  b.vy = -b.vy;
}

Это учебное упрощение, а не точная физическая модель столкновения. Оно особенно заметно при касательных столкновениях.

JavaScript · события · 10 минут

Обработчики событий через addEventListener

Событие сообщает программе, что произошло определённое действие: пользователь нажал кнопку, переместил ползунок или изменил значение поля. Метод addEventListener назначает функцию, которая будет вызвана при наступлении выбранного события.

Щелчок по кнопке

const pauseButton =
  document.getElementById("pause");

pauseButton.addEventListener(
  "click",
  function () {
    running = !running;
  }
);

Изменение ползунка

const speedInput =
  document.getElementById("speed");

speedInput.addEventListener(
  "input",
  function () {
    speed = Number(speedInput.value);
  }
);
Часть записиСмысл
pauseButtonЭлемент, за событиями которого наблюдает программа.
"click"Тип события.
function () {...}Обработчик — функция, выполняемая после события.
"input"Событие изменения значения во время перемещения ползунка.
Преимущество. В HTML остаётся описание элементов, а программная реакция записывается в JavaScript. Одному элементу можно назначить несколько обработчиков разных событий.
Практика · 10 минут

Добавьте управление моделью

HTML

<section class="controls">
  <button id="pause">Пауза</button>

  <label for="speed">Скорость</label>
  <input id="speed" type="range"
         min="1" max="5" value="2">
  <output id="speedValue"
          for="speed">2</output>
</section>

CSS

.controls {
  display: flex;
  gap: 10px;
  align-items: center;
}

button {
  transition: background-color 0.2s;
}

button:hover {
  background-color: #1d5a49;
}
let running = true;
let speed = 2;

pauseButton.addEventListener("click", function () {
  running = !running;
  pauseButton.textContent = running ? "Пауза" : "Пуск";
});

speedInput.addEventListener("input", function () {
  speed = Number(speedInput.value);
  speedValue.textContent = speed;
});

function tick() {
  if (!running) return;
  // изменение координат с учётом speed
}
Оператор ! означает логическое отрицание. Если running равно false, выражение !running истинно, и команда return завершает текущий вызов функции.
Самостоятельная работа · 8 минут

Выберите одно расширение модели

Сначала сохраните работающую базовую версию проекта. Затем создайте копию файла и добавьте одно расширение.

Базовые варианты

  • изменение количества частиц;
  • случайный цвет каждой частицы;
  • кнопка полного сброса модели;
  • переключатель показа столкновений.

Повышенная сложность

  • разные радиусы частиц;
  • счётчик столкновений;
  • упрощённое изменение скорости при столкновении;
  • собственный вариант управления.
Использование ИИ. Разрешается запросить план одной функции, объяснение ошибки или небольшой фрагмент кода. Полученное решение необходимо проверить, приспособить к проекту и уметь объяснить по строкам.
Итоги · 5 минут

Проверьте результат занятия

Проект

Объяснение

Обязательный результат: работающая модель с обнаружением столкновений и хотя бы одним элементом управления. Дополнительные эффекты не заменяют понимание основного кода.