Порядок занятия
Занятие состоит из двух частей. Сначала — четыре блока повторения материала занятий 1 и 2, оформленные как контрольные вопросы. Задание, решённое без затруднений, не требует задержки: переходите к следующему блоку сразу. Далее следует новая тема — набор частиц на Canvas: от одной точки с известными координатами до множества частиц, которые перемещаются и отражаются от границ поля.
Структура занятия
1. Четыре блока повторения: HTML, подключение JavaScript, CSS, Canvas.
2. Новая тема: массив частиц, движение, отражение от границ.
3. Перенос результата в собственный проект.
Итоговая точка на сегодня у разных учеников будет разной — это заложено в структуре занятия, а не является отклонением от неё.
Структура документа и атрибуты
Пять заданий на повторение материала занятия 1. Задания построены не на определениях, а на разборе конкретного кода — это ближе к тому, с чем вы уже работали в тренажёре.
<p>Первый абзац<p>Второй абзац</p>?<img src="cat.png"> используется без парного закрывающего тега?id="box". Чем это опасно?<img src="cat.png" ___="кот на подоконнике">. Какой атрибут пропущен, если нужно описать изображение текстом?<body>. Что при этом произойдёт?Подсказка
Задание 1: каждый парный тег закрывается до начала следующего элемента того же уровня, иначе структура документа искажается. Задание 2: исключение из этого правила — теги без содержимого, такие как <img>: у них нет пары. Задание 3: идентификатор id служит для однозначного обращения к элементу и не должен повторяться на странице — иначе команда, ищущая элемент по этому id, работает непредсказуемо. Задание 4: значение атрибута, описывающего изображение словами, записывается в alt. Задание 5: содержимое <head> не отображается на самой странице — это служебная часть документа, а не текст для читателя.
Теория блока: структура HTML-документа
HTML (HyperText Markup Language — язык гипертекстовой разметки) описывает, из каких частей состоит страница и как они вложены друг в друга. Полный минимальный документ выглядит так:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Моя страница</title>
</head>
<body>
<h1>Заголовок</h1>
<p>Текст абзаца.</p>
<img src="cat.png" alt="кот на подоконнике">
</body>
</html>
Построчный разбор:
<!DOCTYPE html>— первая строка любого HTML-файла: сообщает браузеру, что документ написан по современным правилам HTML.<html>…</html>— корневой элемент, внутри которого находится весь документ целиком.<head>…</head>— служебная часть. Ничего из неё не отображается на самой странице. Здесь находится заголовок вкладки браузера (<title>) и подключения внешних файлов — CSS и JavaScript.<body>…</body>— видимая часть: всё, что находится здесь, показывается пользователю.<h1>— крупный заголовок раздела; более мелкие заголовки —<h2>,<h3>и так далее по убыванию значимости.<p>(paragraph — абзац) — обычный текстовый блок.
Парные и одиночные теги. Большинство тегов работают в паре: у открывающего <p> есть закрывающий </p> с косой чертой, а между ними — содержимое элемента:
<p>Это содержимое абзаца.</p>
Небольшая группа тегов не имеет содержимого и потому используется без закрывающей пары — это одиночные (void) теги: <img> (изображение), <br> (перенос строки), <input> (поле ввода). Если пометить пару такому тегу — например, написать <img></img>, — лишний закрывающий тег не даст ошибки, но не будет иметь смысла и обычно не пишется.
Вложенность. Элементы можно вкладывать друг в друга, но открытые теги нужно закрывать в обратном порядке — как скобки в математике:
<!-- верно: p открыт, затем strong, затем strong закрыт, затем p -->
<p>Обычный текст и <strong>жирный текст</strong>.</p>
<!-- неверно: теги закрыты в неправильном порядке -->
<p>Обычный текст и <strong>жирный текст</p></strong>
Похожая по духу ошибка — незакрытый тег перед началом следующего элемента того же уровня: <p>Первый<p>Второй</p>. Браузер способен исправить некоторые такие ошибки самостоятельно, но полагаться на это не стоит: разметка должна оставаться понятной и без автоматических исправлений.
Атрибуты — дополнительные характеристики тега, которые записываются внутри открывающего тега в виде имя="значение", через пробел, если атрибутов несколько:
<img src="cat.png" alt="кот на подоконнике" id="mainPhoto">
src(source — источник) — путь к файлу изображения.alt(alternative — альтернативный) — текстовое описание изображения на случай, если файл не загрузится, а также для программ чтения с экрана.id— уникальное имя элемента. По этому имени к элементу впоследствии обращаются CSS и JavaScript. В отличие от класса (с которым мы познакомимся в следующем блоке), id не должен повторяться на странице: если два элемента получат одинаковый id, команда поиска по этому id найдёт только один из них, а какой именно — заранее не очевидно.
Наконец, в HTML можно оставлять комментарии — заметки для человека, читающего код, которые браузер не отображает: <!-- это комментарий -->.
Селектор, свойство, значение
Четыре задания на разбор CSS-правил. Материал соответствует занятию 2.
#title { color white; } пропущен один символ, из-за которого правило не сработает. Запишите объявление верно..card { padding: 10px; } и элемент <div id="card"> (без класса card). Применится ли к нему это правило?highlight. К скольким из них применится правило .highlight { color: red; }?Подсказка
Задание 1: CSS-объявление состоит из свойства и значения, разделённых двоеточием, с точкой с запятой в конце: color: white;. Задание 2: точка перед именем в селекторе означает обращение по классу, а решётка — обращение по идентификатору; это разные атрибуты, и они не заменяют друг друга. Задание 3: внешний файл стилей подключается тегом <link rel="stylesheet" href="…">, а не тегом <script>. Задание 4: в отличие от id, класс может использоваться на любом числе элементов, и правило применится ко всем из них одновременно.
Теория блока: CSS — язык оформления
HTML описывает содержимое страницы: где заголовок, где абзац, где картинка. CSS (Cascading Style Sheets — каскадные таблицы стилей) описывает внешний вид этого содержимого: цвет, размер, отступы, шрифт. Одно и то же HTML-содержимое может выглядеть совершенно по-разному в зависимости от того, какой CSS к нему подключён.
Правило CSS состоит из двух частей: селектора и блока объявлений в фигурных скобках.
селектор {
свойство: значение;
свойство: значение;
}
Рассмотрим на конкретном примере:
#title {
color: white;
background-color: #123d32;
padding: 12px;
text-align: center;
}
#title— селектор: к какому элементу применяется оформление. Решётка означает «элемент с id="title"».color: white;— свойствоcolorотвечает за цвет текста; значениеwhite— белый.background-color: #123d32;— свойство цвета фона; значение записано в шестнадцатеричном формате (можно и словом, напримерgreen).padding: 12px;— внутренний отступ между границей элемента и его содержимым, 12 пикселей со всех сторон.text-align: center;— выравнивание текста по центру.
Три основных вида селектора различаются тем, скольких элементов они касаются:
p { … }— по имени тега: правило применится ко всем элементам<p>на странице..card { … }— по классу (точка перед именем): правило применится ко всем элементам с атрибутомclass="card", сколько бы их ни было.#header { … }— по идентификатору (решётка перед именем): правило применится к одному элементу с атрибутомid="header", поскольку id уникален.
Класс и id — разные атрибуты HTML, и селекторы для них пишутся по-разному: <div class="card"> подхватывается селектором .card, а <div id="card"> — селектором #card. Совпадение названия не делает их взаимозаменяемыми.
Подключение CSS к странице возможно тремя способами:
<!-- 1. Прямо в атрибуте, только для одного элемента -->
<p style="color: red;">Текст</p>
<!-- 2. Внутри самого документа, для всей страницы -->
<head>
<style>
p { color: red; }
</style>
</head>
<!-- 3. Во внешнем файле — самый удобный способ для проекта -->
<head>
<link rel="stylesheet" href="style.css">
</head>
Третий способ используется в собственном проекте: правила оформления собираются в отдельном файле style.css и подключаются тегом <link> в <head>. Это позволяет менять оформление сразу всей страницы, не трогая разметку в HTML-файле. Комментарий в CSS-файле записывается как /* текст комментария */ и браузером не учитывается.
Обработчик события и первые команды
Пять заданий на разбор кода. Материал соответствует второй половине занятия 1.
<button onclik="start()">Старт</button>. В чём причина?document.getElementById("title").textContent = "Готово";?document.body.style.___ = "orange"; — чтобы изменить цвет фона страницы.onclick="start()" после имени функции стоят круглые скобки?Подсказка
Задание 1: атрибут, связывающий клик с функцией, пишется как onclick, без опечаток. Задание 2: команда элемент.textContent = "…" заменяет видимый текст внутри найденного элемента, а не его id и не его оформление. Задание 3: свойство, отвечающее за цвет фона в JavaScript, называется backgroundColor — без пробела, с заглавной буквой C. Задание 4: круглые скобки после имени функции означают вызов — команду немедленно выполнить её содержимое. Задание 5: команды внутри функции выполняются последовательно, одна за другой, в порядке записи сверху вниз.
Теория блока: JavaScript — язык поведения страницы
Если HTML описывает содержимое, а CSS — оформление, то JavaScript отвечает за поведение: что происходит на странице в ответ на действия пользователя или течение времени. Код JavaScript размещается либо в отдельном файле, подключённом тегом <script src="script.js"></script>, либо прямо внутри тега <script>…</script>.
Событие и обработчик. Программа не выполняется вся целиком в момент загрузки страницы — часть команд ждёт наступления события: щелчка мышью, нажатия клавиши, отправки формы. Самый простой способ связать событие с действием — HTML-атрибут onclick:
<button onclick="start()">Старт</button>
<script>
function start() {
document.body.style.backgroundColor = "orange";
}
</script>
Построчный разбор:
onclick="start()"— атрибут кнопки: при щелчке вызвать функцию с именемstart.function start() { … }— объявление функции: создаёт именованную группу команд. Само объявление ничего не выполняет — команды внутри сработают только при вызове.start()— вызов функции. Круглые скобки после имени означают «выполнить эту функцию сейчас».document.body.style.backgroundColor = "orange";— команда внутри функции: найти видимую часть страницы (document.body), обратиться к её оформлению (.style) и установить свойство цвета фона (.backgroundColor) в значение"orange".
Поиск элемента по id. Чтобы изменить не всю страницу, а один конкретный элемент, его сначала нужно найти:
document.getElementById("title").textContent = "Готово";
document— вся текущая страница, точка входа для любых команд JavaScript, работающих с содержимым.getElementById("title")— метод, который ищет на странице единственный элемент с указанным id и возвращает его..textContent— свойство найденного элемента: его видимый текст.= "Готово";— оператор присваивания записывает новое значение в это свойство; точка с запятой отмечает конец команды.
Порядок выполнения. Если внутри функции записано несколько команд, они выполняются строго последовательно, одна за другой, в порядке записи сверху вниз — так же, как читаются строки текста:
function start() {
document.body.style.backgroundColor = "orange"; // выполнится первой
document.getElementById("title").textContent = "Готово"; // выполнится второй
}
Комментарий — текст после // до конца строки — предназначен для человека, читающего код, и браузером не выполняется.
Отрисовка и состояние
Щелчок по полю изменяет переменную x и вызывает повторную отрисовку. Само по себе значение не меняется — его меняет код, реагирующий на действие.
ctx.clearRect(0, 0, canvas.width, canvas.height);. Что произойдёт при повторной отрисовке движущегося объекта?ctx.arc(150, 80, 20, 0, Math.PI * 2) третий параметр задаёт радиус круга. Чему он равен?ctx.beginPath(). Для чего это нужно?Подсказка
Задание 1: Canvas не хранит прежние кадры — без очистки поля старое изображение остаётся под новым, и движение выглядит как след. Задание 2: у метода ctx.arc(x, y, радиус, начальный угол, конечный угол) третий параметр — радиус; в примере это число 20. Задание 3: команда beginPath() отделяет одну фигуру от другой, чтобы их контуры не соединялись между собой при заливке.
Теория блока: рисование на Canvas
Элемент <canvas> — область страницы для программного рисования: не изображение, а чистое полотно, на котором JavaScript рисует линии и фигуры командами.
<canvas id="scene" width="560" height="200"></canvas>
<script>
const canvas = document.getElementById("scene");
const ctx = canvas.getContext("2d");
</script>
widthиheightу самого тега<canvas>задают размер полотна в пикселях рисования — это не то же самое, что размер на экране через CSS.canvas.getContext("2d")возвращает объект контекста — своего рода «инструмент рисования» для этого полотна. Все дальнейшие команды рисования вызываются у контекста (ctx), а не у самогоcanvas.
Система координат. Точка (0, 0) находится в левом верхнем углу поля. Координата x растёт вправо, а координата y растёт вниз — это отличается от привычной оси y на уроках математики, где вверх означает увеличение.
Рисование круга. Полный пример:
ctx.beginPath();
ctx.fillStyle = "green";
ctx.arc(150, 80, 20, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath()— начать новый, независимый контур. Без этой команды новая фигура может «слипнуться» с предыдущей.ctx.fillStyle = "green";— устанавливает цвет, которым будет заполнена следующая фигура.ctx.arc(x, y, радиус, начальный угол, конечный угол)— описывает контур окружности с центром в точке(x, y). Углы задаются в радианах;0иMath.PI * 2означают полный оборот, то есть целую окружность.ctx.fill()— заливает описанный контур цветом изfillStyle. Вместо заливки можно вызватьctx.stroke()— тогда будет нарисован только контур, без заливки, цветом изstrokeStyle.
Прямоугольники рисуются проще, без beginPath/fill — одной командой:
ctx.fillStyle = "orange";
ctx.fillRect(x, y, ширина, высота);
Очистка поля. Canvas не хранит историю кадров — всё, что нарисовано, остаётся на полотне навсегда, пока не будет явно стёрто:
ctx.clearRect(0, 0, canvas.width, canvas.height);
clearRect(x, y, ширина, высота) делает прозрачным прямоугольную область, начиная с точки (x, y). Указывая координаты (0, 0) и полные canvas.width, canvas.height, мы очищаем всё полотно целиком. Именно поэтому в любой Canvas-анимации перед отрисовкой нового кадра поле сначала полностью очищается — иначе прежние положения движущегося объекта останутся видны, образуя след.
Система частиц: координаты, массив, движение
Сегодня мы собираем на Canvas систему из множества однотипных объектов — частиц. Каждая частица описывается набором свойств (координаты, размер, скорость), а сам набор частиц хранится в массиве.
Положение
Координаты x и y задают точку на поле — тема, уже отработанная в блоке повторения по Canvas.
Хранение
Массив хранит сразу много частиц; цикл проходит по ним по очереди.
Движение
Скорость и условие на границе определяют, как частица перемещается со временем.
Одна частица: переменные и координаты
Коротко: объявление переменных — let и const
Переменная — именованное хранилище для значения. Прежде чем использовать переменную, её нужно объявить — указать, что такое имя появляется в программе. Для этого перед именем ставится одно из двух ключевых слов:
let x = 280;— объявляет переменнуюxи сразу записывает в неё значение 280. Словоletиспользуется, когда значение переменной в дальнейшем будет меняться (например, координата движущейся частицы).const ctx = canvas.getContext("2d");— объявляет переменную, значение которой в дальнейшем меняться не будет. Словоconst(от constant — постоянный) защищает от случайной повторной записи в эту переменную: попытка присвоить ей новое значение вызовет ошибку.
Выбор между let и const — это сообщение читающему код человеку (и самому себе в будущем) о намерении: «это значение постоянно» или «это значение будет меняться». В коде ниже canvas и ctx объявлены через const, потому что они находятся один раз и далее не переназначаются, а x, y, r — через let, потому что координаты частицы в следующих ступенях начнут меняться.
Первый шаг — неподвижная частица на Canvas, построенная теми же средствами, что и в блоке 4.
const canvas = document.getElementById("solidCanvas");
const ctx = canvas.getContext("2d");
let x = 280;
let y = 100;
let r = 10;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();x изменили с 280 на 60. На сколько пикселей сместился центр частицы?r с центром в точке (x, y).Массив как хранилище нескольких значений
Коротко: массив, объект, цикл for и Math.random()
Массив — упорядоченный набор значений, записанный в одной переменной. Значения перечисляются через запятую в квадратных скобках: let numbers = [10, 20, 30];. Обращение к отдельному значению происходит по номеру — индексу, который начинается с нуля: numbers[0] равно 10, numbers[1] равно 20. Добавить новое значение в конец массива позволяет метод .push(): numbers.push(40);. Свойство .length хранит число элементов в массиве: для numbers после добавления четвёртого значения numbers.length равно 4.
Объект — набор значений, у каждого из которых есть собственное имя (ключ), а не порядковый номер, как в массиве. Запись {x: 10, y: 20, r: 6} создаёт объект с тремя свойствами: x, y и r. Чтобы получить значение конкретного свойства, к переменной, хранящей объект, дописывают точку и имя свойства — точно так же, как мы уже делали в document.body.style:
let p = { x: 10, y: 20, r: 6 };
p.x; // 10 — читаем свойство
p.x = 15; // записываем в свойство новое значение
Массив и объект часто используются вместе: массив хранит много однотипных «карточек»-объектов, а каждый объект описывает свойства одной из них. Именно так будут устроены частицы в этом занятии — массив particles, где каждый элемент представляет собой объект с полями x, y, r.
Чтобы не обращаться к каждому элементу массива по отдельности, используется цикл — конструкция, повторяющая одно и то же действие заданное число раз. Классическая запись:
for (let i = 0; i < numbers.length; i++) {
// команды, использующие numbers[i]
}
Заголовок цикла состоит из трёх частей, разделённых точкой с запятой:
let i = 0— выполняется один раз перед первым повтором: объявляет переменную-счётчик и присваивает ей начальное значение.i < numbers.length— условие, проверяемое перед каждым повтором. Пока оно истинно, цикл продолжается; как только становится ложным — цикл останавливается.i++— выполняется после каждого повтора: увеличиваетiна 1. Записьi++— сокращение дляi = i + 1.
В результате i по очереди принимает значения 0, 1, 2 и так далее до numbers.length - 1, то есть перебирает номер каждого элемента массива. Метод массива .forEach() решает ту же задачу более короткой записью: numbers.forEach(n => { ... }) — команда внутри выполнится для каждого значения n массива по очереди, без явного счётчика. Здесь n => { ... } — функция без имени, записанная короче обычной: значок => («стрелка») заменяет слово function, а n перед стрелкой — параметр, в который на каждом шаге подставляется очередной элемент массива.
Команда Math.random() возвращает случайное дробное число от 0 (включительно) до 1 (не включительно). Чтобы получить случайное число в пределах ширины поля, значение умножают на нужную величину: Math.random() * canvas.width.
Отдельная переменная на каждую частицу неудобна при их значительном числе. Массив хранит список частиц, а цикл for последовательно обращается к каждому элементу этого списка.
let particles = [];
for (let i = 0; i < 30; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: 6
});
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fill();
}
}
draw();draw убрать цикл for и оставить только одно обращение particles[0]?Расположение частиц по узлам регулярной сетки вместо случайных координат — тоже допустимый вариант этой ступени в собственном проекте.
Движение: обновление состояния через равные промежутки времени
Коротко: setInterval
Команда setInterval(функция, интервал) запускает переданную функцию не один раз, а многократно, повторяя вызов через указанный интервал времени в миллисекундах (1000 мс = 1 секунда). Запись setInterval(tick, 30) означает: выполнять функцию tick каждые 30 миллисекунд, то есть примерно 33 раза в секунду. Если на каждом таком повторе координата объекта немного меняется и сцена перерисовывается заново, глаз воспринимает последовательность отдельных положений как непрерывное движение — на этом принципе основана любая компьютерная анимация.
Изменение координаты записывается через составное присваивание: p.x += p.vx; — сокращение для p.x = p.x + p.vx;, то есть «прибавить к текущему значению p.x величину p.vx и сохранить результат обратно в p.x». Такая запись короче и привычна для любых повторяющихся накоплений значения, не только для координат.
Каждой частице присваивается скорость — компоненты vx и vy. На каждом шаге координата увеличивается на величину скорости, после чего сцена перерисовывается. Повтор действия через равные промежутки времени обеспечивает команда setInterval.
particles.forEach(p => {
p.vx = (Math.random() - 0.5) * 1.5;
p.vy = (Math.random() - 0.5) * 1.5;
});
function tick() {
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
});
draw();
}
setInterval(tick, 30);tick?setInterval(tick, 30) изменили на 300. Как изменится видимая скорость частиц?Отражение от границы: конструкция if
Коротко: условие if и операторы сравнения
Конструкция if (условие) { команды } выполняет команды в фигурных скобках только тогда, когда условие истинно. Условие — это выражение, принимающее одно из двух логических значений: true (истина) или false (ложь). Значения сравниваются операторами <, >, <=, >= (меньше, больше, меньше или равно, больше или равно) и === (равно). Несколько условий можно объединить оператором || («или»): выражение a || b истинно, если истинно хотя бы одно из условий a и b.
Важно не путать одиночный знак = и тройной ===: одиночный = — это присваивание, он записывает значение в переменную (мы уже использовали его в let x = 280;). Тройной === — это сравнение, он проверяет, равны ли два значения, и сам по себе ничего никуда не записывает. Существует ещё двойной знак ==, тоже сравнение, но менее строгое: перед проверкой он пытается привести значения к одному типу, из-за чего 0 == false и 5 == "5" дают true, хотя слева и справа разные типы данных. Такое поведение сложно удержать в голове, поэтому в этом курсе для сравнения всегда используется только ===.
Чтобы частица не покидала пределы Canvas, перед перемещением проверяется её положение относительно границы. При достижении края направление движения по соответствующей оси меняется на противоположное.
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();
}p.vx = -p.vx;?p.x < p.r убрали, оставив только проверку правой границы. Что произойдёт с частицей у левого края?vx и vy на коэффициент. Это небольшой самостоятельный шаг в сторону темы событий, которая будет разобрана подробно на одном из следующих занятий.Столкновение частиц друг с другом
Реализованная модель предусматривает отражение частиц от стенок сосуда, но не их взаимодействие между собой: в текущем виде частицы проходят одна сквозь другую. Определение столкновения двух частиц — задача для самостоятельной работы или начала следующего занятия. Общий принцип: расстояние между центрами двух частиц сравнивается с суммой их радиусов.
// Общий принцип — не обязательно реализовывать сегодня
function distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
// если distance(a, b) меньше суммы a.r и b.r — частицы столкнулисьРезультат занятия
Итоговая точка у разных учеников может отличаться: часть группы дойдёт до неподвижной частицы, часть — до движения, часть — до отражения от границ. Существенным критерием является не глубина продвижения, а способность объяснить назначение каждой написанной строки кода.
Проверка перед завершением
Файлы сохранены и понятно названы; страница открывается в браузере без ошибок; каждая написанная сегодня строка кода может быть объяснена.
Следующее занятие
Работа продолжится с достигнутой точки: движение, отражение от границ и, для продвинувшихся дальше, столкновение частиц друг с другом.