Создание хоккейного табло: HTML, CSS и JavaScript с нуля

Пошаговый практикум по созданию интерактивного прототипа хоккейного табло. Вы научитесь верстать структуру на HTML, стилизовать спортивный интерфейс на CSS и реализовать рабочие таймеры матча и штрафного времени на JavaScript. Минимум теории — максимум готового кода.

1. Основы HTML-структуры: разметка ключевых элементов табло

Основы HTML-структуры: разметка ключевых элементов табло

Представьте, что вы входите в ледовый дворец за 10 минут до начала матча. Ваш взгляд сразу падает на гигантское табло над площадкой: на нём уже горят названия команд, нули в графе счёта и таймер, ожидающий старта. Именно эту статичную, но информативно насыщенную «заготовку» мы создадим сегодня на HTML. Без неё не будет за что зацепиться ни стилям, ни скриптам.

Любое веб-табло — это набор контейнеров (или блоков), внутри которых лежат данные. Контейнеры помогают нам не только визуально группировать элементы, но и управлять ими через CSS и JavaScript как единым целым. Наша задача — распределить элементы табло по логическим группам, чтобы потом стилизовать и «оживить» их независимо.

Шапка табло: время матча и команды

Самый верхний блок — шапка (header). В ней мы разместим три ключевых блока: таймер матча по центру и названия команд по бокам. Используем семантический тег <header>, чтобы браузер и поисковики понимали важность этой области.

Обратите внимание на соглашение об именовании классов — методология BEM (Блок, Элемент, Модификатор). Класс team — это блок, team__name — его элемент, а team--home — модификатор, указывающий на хозяев площадки. Такой подход предотвращает конфликты имён и делает код самодокументируемым.

Основное поле: счёт и периоды

Центральная часть табло — это основной контейнер (main), где отображается счёт по периодам и общий результат. Разобьём его на две логические строки: детализация по периодам и итоговый счёт.

Здесь мы использовали тег <section> для выделения самостоятельного раздела с очками. Каждый показатель счёта обёрнут в <span> — строчный элемент, который не нарушает поток и идеально подходит для отображения чисел в таблице.

Подвал: штрафы и дополнительная информация

Нижняя часть табло — подвал (footer). В хоккее это зона для отображения штрафного времени, силы составов (например, 5 на 4) и прочих уведомлений. Создадим заготовку и для них.

Каждый штраф — это отдельный блок penalty с модификатором команды. Внутри — элементы для номера игрока, оставшегося времени и причины удаления. Такая структура позволит нам потом независимо стилизовать и управлять каждым штрафом через JavaScript.

Сборка всех фрагментов в единый документ

Теперь объединим все части в полноценный HTML-документ. Не забудем про doctype, кодировку и базовые мета-теги — они гарантируют корректное отображение в браузере.

Весь контент обёрнут в корневой контейнер div.scoreboard. Это главный контейнер, через который мы будем управлять всем табло: центрировать на странице, задавать фоновые стили и ограничивать ширину. Тег <script> подключён перед закрывающим </body> — это стандартная практика, чтобы браузер сначала загрузил и отобразил HTML, а потом исполнил JavaScript.

Проверка структуры: что мы получили

Откройте созданный HTML-файл в браузере. Пока вы увидите просто текст без оформления — это нормально. Главное, чтобы:

  • Названия команд были на своих местах (слева и справа от таймера)
  • Таблица счёта содержала все периоды и итоговую колонку
  • Штрафные блоки отображались внизу
  • Такая семантическая разметка — фундамент. Каждый элемент находится в логическом контейнере, имеет понятный класс и готов к стилизации. В следующей статье мы превратим этот скелет в визуально привлекательное табло с помощью CSS, добавив спортивные цвета, шрифты и адаптивность под разные экраны.

    2. Стилизация табло на CSS: спортивный дизайн и адаптивность

    Стилизация табло на CSS: спортивный дизайн и адаптивность

    Голый HTML-скелет табло напоминает шахматную доску без фигур — структура понятна, но эмоций нет. Именно CSS превращает разметку в визуальный объект, который можно узнать по стилю: тёмный фон как ведро со льдом, яркие акценты как командные цвета, чёткие шрифты как на профессиональных аренах. Сегодня мы придадим нашему табло спортивный характер и научим его подстраиваться под размер экрана.

    Базовые стили: сброс и подготовка

    Любой проект начинается с сброса стилей браузера. Разные браузеры имеют свои отступы и размеры по умолчанию — наша задача унифицировать начальные условия.

    Селектор * применяет правила ко всем элементам. box-sizing: border-box — ключевое свойство, которое заставляет ширину и высоту элемента включать в себя padding и border. Без него расчёты макета превращаются в головную боль.

    Стилизация корневого контейнера табло

    Контейнер .scoreboard станет нашим «холстом». Зададим ему фиксированную ширину, скруглённые угла и тень — это создаст эффект физического объекта, висящего над страницей.

    Градиент linear-gradient создаёт глубину, а красная рамка border — фирменный акцент, напоминающий хоккейную разметку. Свойство overflow: hidden гарантирует, что внутренние элементы не вылезут за скруглённые углы.

    Шапка табло: контраст и читаемость

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

    Используем флексбокс (display: flex) для расположения трёх блоков в строку. justify-content: space-between раздвигает команды по краям, оставляя таймер по центру. Моноширинный шрифт Courier New для времени обеспечивает ровную ширину цифр — они не будут «прыгать» при обновлении.

    Основное поле: таблица счёта

    Таблица счёта должна выглядеть как статистический лист. Используем CSS Grid для создания сетки с колонками под периоды.

    Сетка grid-template-columns: 2fr repeat(4, 1fr) создаёт пять колонок: первая (название команды) в два раза шире остальных. repeat(4, 1fr) — сокращённая запись для четырёх равных колонок. Такой подход делает таблицу адаптивной: колонки автоматически пересчитывают ширину.

    Подвал со штрафами: визуальные предупреждения

    Штрафы — это «тревожная» зона табло. Выделим её красным фоном и крупными цифрами, чтобы информация об удалении сразу бросалась в глаза.

    Каждый блок штрафа имеет красную левую границу — визуальный маркер серьёзности. flex: 1 заставляет блоки растягиваться на доступное пространство, но max-width ограничивает их, чтобы на широких экранах они не расплывались.

    Адаптивность: табло на мобильных устройствах

    Настоящее табло должно работать не только на мониторе, но и на телефоне. Используем медиазапросы для изменения стилей при узком экране.

    При ширине экрана менее 600 пикселей шапка перестраивается в колонку, шрифты уменьшаются, а блоки штрафов выстраиваются вертикально. Это мобильный приоритет — сначала проектируем для маленьких экранов, затем расширяем для больших.

    Финальные штрихи: анимации и состояния

    Добавим тонкие CSS-переходы для интерактивности. Например, плавное изменение цвета при наведении на блок штрафа.

    Свойство transition делает изменения плавными. При наведении (:hover) блок слегка увеличивается и получает красную тень — это создаёт эффект «живого» табло, реагирующего на действия.

    Теперь наше табло обрело визуальную идентичность: спортивные цвета, чёткая типографика, адаптивная вёрстка. Но оно пока статично — как распечатанная фотография. В следующей статье мы «вдохнём» в него жизнь с помощью JavaScript, научив таймер матча обратному отсчёту.

    3. JavaScript-таймер матча: обратный отсчёт и управление временем

    JavaScript-таймер матча: обратный отсчёт и управление временем

    Статичное табло — это муляж. Настоящее табло живёт: секунды бегут, периоды меняются, время останавливается по свистку арбитра. Сегодня мы превратим наш HTML-скелет в интерактивный объект, где JavaScript управляет временем матча. Вы научитесь создавать обратный отсчёт, обновлять DOM и реализовывать базовые контроллеры: старт, пауза, сброс.

    Фундамент: как работает обратный отсчёт в браузере

    Обратный отсчёт — это периодическое обновление значения на экране. Браузер предоставляет функцию setInterval, которая выполняет код через равные промежутки времени. Её синтаксис: setInterval(функция, интервал_в_миллисекундах).

    Функция использует document.querySelector для поиска элемента с классом .timer__time — это тот самый <span>, который мы создали в HTML. textContent безопаснее, чем innerHTML, когда нужно вставить просто текст.

    Кнопки управления: старт, пауза, сброс

    Таймер без управления — как машина без руля. Добавим в HTML три кнопки, а в JavaScript — обработчики событий.

    addEventListener('click', ...) вешает на кнопку функцию, которая сработает при клике. Важно: перед запуском нового интервала проверяем !gameTimer.isRunning — иначе при повторном нажатии «Старт» создадутся несколько параллельных таймеров, и время будет ускоряться.

    Стили для кнопок управления

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

    Свойство cursor: pointer меняет курсор на «руку» при наведении — визуальная подсказка, что элемент кликабельный.

    Отладка: вывод состояния в консоль

    Пока мы не реализовали смену периодов, полезно видеть состояние таймера в консоли разработчика (F12 в браузере). Добавим логирование в функцию updateTimer:

    Теперь при каждом обновлении таймера в консоли будет появляться строка с текущим периодом, временем и статусом. Это поможет отследить ошибки, когда мы добавим более сложную логику.

    Что мы получили: рабочий прототип таймера

    На этом этапе у нас есть:

  • Таймер, считающий обратно от 20:00 до 00:00
  • Кнопки для запуска, паузы и сброса
  • Форматирование времени в привычный формат MM:SS
  • Базовая обработка окончания периода
  • Но настоящий хоккейный таймер сложнее: он должен останавливаться по свистку, менять периоды, учитывать штрафное время. В следующей статье мы реализуем отдельный таймер для штрафов, который будет работать параллельно с основным и отображаться в подвале табло.

    4. Штрафное время: отдельный таймер и отображение на табло

    Штрафное время: отдельный таймер и отображение на табло

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

    Архитектура: массив штрафов и их состояния

    В отличие от единственного таймера матча, штрафов может быть несколько. Для этого используем массив объектов, где каждый объект — отдельный штраф со своими параметрами.

    Использование Date.now() в качестве ID гарантирует уникальность — метод возвращает миллисекунды, прошедшие с 1 января 1970 года. Два штрафа, добавленные даже в одну секунду, получат разные ID.

    Отрисовка штрафа в HTML

    Функция renderPenalty должна создать HTML-элемент для нового штрафа и добавить его в контейнер .penalty-box. Используем DOM-методы для динамического создания элементов.

    Селектор [data-penalty-id="{penaltyId}"]); if (element) { // Добавляем анимацию исчезновения element.style.opacity = '0'; element.style.transform = 'translateX(100px)'; setTimeout(() => element.remove(), 300); } } javascript // В обработчике кнопки "Старт" startBtn.addEventListener('click', () => { if (!gameTimer.isRunning) { gameTimer.isRunning = true; gameTimer.intervalId = setInterval(updateTimer, 1000); // Запускаем все приостановленные штрафы penalties.forEach(penalty => { if (!penalty.isRunning && penalty.currentTime > 0) { startPenaltyTimer(penalty); } }); } });

    // В обработчике кнопки "Пауза" pauseBtn.addEventListener('click', () => { if (gameTimer.isRunning) { clearInterval(gameTimer.intervalId); gameTimer.isRunning = false; // Приостанавливаем все штрафы penalties.forEach(penalty => { if (penalty.isRunning) { clearInterval(penalty.intervalId); penalty.isRunning = false; } }); } }); css .penalty--warning .penalty__time { animation: pulse 1s infinite; color: #ff0000; }

    @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } } javascript // Внутри setInterval таймера штрафа if (penalty.currentTime <= 30) { const penaltyElement = document.querySelector([data-penalty-id="${penalty.id}"]); if (penaltyElement && !penaltyElement.classList.contains('penalty--warning')) { penaltyElement.classList.add('penalty--warning'); } } ``

    Анимация pulse` создаёт эффект мигания — это универсальный сигнал «внимание» в интерфейсах.

    Тестирование сценариев

    Проверим работу системы:

  • Добавим два штрафа для разных команд
  • Запустим основной таймер — оба штрафа должны тикать
  • Поставим на паузу — оба должны остановиться
  • Удалим один штраф кнопкой «✕» — второй должен продолжить работу
  • Дождёмся окончания штрафа — элемент должен исчезнуть с анимацией
  • Теперь наше табло стало по-настоящему сложным: основной таймер, несколько параллельных штрафов, управление состоянием. В следующей статье мы соберём все части воедино, добавим недостающие элементы и превратим прототип в готовое к использованию табло.

    5. Финальная сборка: объединение HTML, CSS и JavaScript в работающий прототип

    Финальная сборка: объединение HTML, CSS и JavaScript в работающий прототип

    У вас есть HTML-скелет, CSS-стили и JavaScript-логика — три отдельных файла, которые пока живут своей жизнью. Сегодня мы соединим их в единое целое, добавим недостающие функции и превратим набор компонентов в полноценное хоккейное табло. Это этап, где код обретает завершённость: вы сможете открыть его в браузере и управлять матчем от первого до третьего периода.

    Структура проекта: три файла и их связи

    Профессиональные веб-проекты разделяют код на файлы по ответственности. Создадим папку проекта с такой структурой:

    В HTML мы уже подключили CSS и JS через теги <link> и <script>. Убедимся, что пути указаны верно — файлы должны лежать в одной папке.

    Доработка HTML: добавление недостающих элементов

    Наш HTML нуждается в нескольких дополнениях: кнопках управления таймером, форме добавления штрафа и области для уведомлений.

    Форма <form> с полями ввода позволит добавлять штрафы через удобный интерфейс, а не через консоль. Контейнер #notifications будет показывать сообщения: «Период окончен», «Штраф добавлен» и т.д.

    Финальная версия CSS: стили для новых элементов

    Добавим стили для панели управления и уведомлений.

    Сетка grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)) делает форму адаптивной: поля автоматически перестраиваются в зависимости от ширины экрана.

    Объединение JavaScript: главный модуль

    Создадим единый файл script.js, который объединит всю логику. Начнем с строгого режима и инициализации.

    Метод reduce суммирует все элементы массива — это функциональный подход вместо цикла for. Теперь при нажатии «+1» счёт обновляется мгновенно.

    Финальная сборка и тестирование

    Откройте index.html в браузере. Протестируйте все сценарии:

  • Запустите таймер — убедитесь, что секунды уменьшаются
  • Поставьте на паузу — время должно остановиться
  • Добавьте штраф через форму — должен появиться красный блок с обратным отсчётом
  • Забейте гол — счёт в таблице должен обновиться
  • Дождитесь конца периода — период должен автоматически смениться
  • Проверьте на телефоне — интерфейс должен адаптироваться
  • Возможные улучшения

    Прототип готов, но его можно развивать:

  • Сохранение состояния в localStorage, чтобы при перезагрузке страницы счёт и время сохранялись
  • Звуковые сигналы при окончании периода или штрафа
  • Таймауты по 30 секунд для каждой команды
  • Статистика бросков и удалений
  • Но даже в текущем виде это полнофункциональное табло, которое можно использовать для домашних матчей или тренировок. Вы прошли путь от пустого файла до работающего приложения, освоив HTML-структурирование, CSS-стилизацию и JavaScript-логику. Каждый компонент можно извлечь и использовать в других проектах: таймер — для любых обратных отсчётов, система уведомлений — для любых интерфейсов, а подход с управлением состоянием — для любых сложных приложений.