Как работают вместе HTML, CSS и JavaScript
Вы уже умеете писать код на JavaScript — но где он живёт? Как браузер загружает страницу, при чём тут DOM, и почему важно, куда вы ставите тег <script>. Разберём всё по порядку.
🏗️ Три технологии — три роли
Каждый сайт в интернете построен на трёх технологиях. Они работают вместе, но у каждой — своя чёткая роль. Попробуйте представить строительство дома:
HTML — это скелет страницы. Он говорит браузеру: «здесь заголовок, здесь абзац, здесь картинка, здесь кнопка». Это просто разметка — что и где находится. Без HTML нет страницы вообще.
CSS — это внешний вид. Какой цвет у кнопки? Какой шрифт? Какие отступы? Как элементы расположены на странице? CSS превращает голую разметку в красивый интерфейс.
JS — это мозг страницы. Нажали кнопку — появилось меню. Ввели текст — проверилась форма. Страница обновилась без перезагрузки. Всё, что «делает» страница в ответ на действия пользователя — это JavaScript.
💬 Аналогия с домом: HTML — это стены и комнаты. CSS — это обои, мебель и освещение. JavaScript — это выключатели, дверные звонки и лифты. Без стен дом не существует. Без обоев — некрасив. Без лифта — неудобен.
Посмотрим на конкретный пример. Вот кнопка в HTML:
<!-- HTML: просто кнопка — она существует, но ничего не делает -->
<button>Нажми меня</button>
Добавляем CSS — кнопка стала красивой:
/* CSS: кнопка теперь выглядит красиво */
button {
background: #3b82f6;
color: white;
padding: 10px 20px;
border-radius: 8px;
border: none;
font-size: 1rem;
cursor: pointer;
}
Добавляем JavaScript — кнопка ожила:
// JavaScript: кнопка теперь что-то ДЕЛАЕТ
document.querySelector('button').addEventListener('click', function() {
alert('Привет! Кнопка работает!');
});
Без JavaScript кнопка просто существует и выглядит. С JavaScript — она реагирует. Именно это делает страницу интерактивной.
🔄 Как браузер загружает страницу — шаг за шагом
Когда вы вводите адрес сайта и жмёте Enter, браузер делает много работы прежде чем вы увидите страницу. Понять этот процесс — значит понять, почему важно правильно подключать JavaScript.
Браузер скачивает HTML-файл с сервера. Это сырой текст — набор тегов и содержимого.
Браузер разбирает (парсит) HTML и строит из него дерево объектов — DOM. Теперь каждый тег — это не просто текст, а живой объект в памяти, с которым может работать JavaScript.
Параллельно браузер скачивает и разбирает CSS-файлы. Получается CSSOM — дерево стилей, которое знает, как должен выглядеть каждый элемент.
DOM + CSSOM = Render Tree. Это финальный список всего, что видно на странице, с учётом стилей. Элементы с display: none сюда не попадают.
Layout (раскладка): браузер вычисляет, где именно на экране находится каждый элемент — координаты, размеры. Paint (отрисовка): пиксели наконец появляются на экране. Страница отображена!
💡 Ключевой момент: JavaScript может прервать этот процесс. Когда браузер встречает тег <script> без специальных атрибутов, он останавливается, скачивает и выполняет скрипт, и только потом продолжает строить DOM. Это называется «блокирующий» скрипт.
📎 Как подключить JavaScript к HTML
Есть несколько способов добавить JavaScript на страницу. Разберём каждый.
Способ 1: Встроенный скрипт
Прямо в HTML, внутри тега <script>:
<!DOCTYPE html>
<html>
<head>
<title>Мой сайт</title>
</head>
<body>
<h1>Привет!</h1>
<!-- Встроенный JS прямо в HTML -->
<script>
console.log('Страница загружена!');
</script>
</body>
</html>
Подходит для маленьких скриптов, но неудобен — весь код смешивается с HTML. Для реальных проектов так не делают.
Способ 2: Внешний файл
JavaScript пишется в отдельном файле script.js, а в HTML добавляется ссылка:
<!-- Подключение внешнего JS-файла -->
<script src="script.js"></script>
Это правильный подход. Код отдельно — удобно редактировать, кешируется браузером, можно подключить к нескольким страницам.
Куда ставить тег <script>?
Вот где начинается важный выбор. У вас есть три варианта:
<head>
<script src="script.js"></script> <!-- блокирует загрузку! -->
</head>
Браузер встречает <script>, останавливает парсинг HTML, скачивает и выполняет скрипт. DOM ещё не построен — JavaScript не может найти элементы страницы. Пользователь видит пустую страницу во время загрузки скрипта.
<body>
<h1>Контент</h1>
<!-- ... весь HTML ... -->
<script src="script.js"></script> <!-- в конце тела -->
</body>
DOM уже построен к моменту выполнения скрипта, элементы доступны. Но браузер начинает скачивать скрипт только после разбора всего HTML — это медленнее чем defer.
<head>
<script src="script.js" defer></script> <!-- правильно! -->
</head>
defer говорит браузеру: «Скачивай файл параллельно с разбором HTML, но выполни его только после того, как DOM полностью построен». Идеальный вариант: скрипт скачивается рано, DOM не блокируется, порядок выполнения сохраняется.
⚖️ defer vs async — в чём разница
У тега <script> есть два «ускоряющих» атрибута. Новички часто путают их.
- Скрипт скачивается параллельно с HTML
- Выполняется после полного построения DOM
- Несколько
defer-скриптов выполняются по порядку - Подходит для большинства скриптов
<script src="app.js" defer></script>
- Скрипт скачивается параллельно с HTML
- Выполняется сразу как только скачан (может прервать парсинг)
- Несколько
async-скриптов выполняются в случайном порядке - Подходит для независимых скриптов (аналитика, реклама)
<script src="analytics.js" async></script>
✅ Простое правило: для ваших собственных скриптов используйте defer в <head>. Он гарантирует, что DOM готов, и сохраняет порядок выполнения. async — для сторонних независимых скриптов, которые не взаимодействуют с вашим кодом.
Вот итоговая шпаргалка по загрузке скриптов:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мой проект</title>
<!-- Стили — всегда в head, они не блокируют парсинг HTML -->
<link rel="stylesheet" href="styles.css">
<!-- Свой JS — defer в head. Скачается параллельно, выполнится после DOM -->
<script src="app.js" defer></script>
<!-- Сторонний независимый скрипт (Google Analytics и т.п.) — async -->
<script src="analytics.js" async></script>
</head>
<body>
<h1>Привет, мир!</h1>
<button id="myBtn">Нажми меня</button>
</body>
</html>
🛠️ Всё вместе: реальный пример
Представим маленький проект: страница с кнопкой, которая меняет заголовок при клике. Вот как всё три технологии работают вместе:
index.html — структура страницы:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Моя страница</title>
<link rel="stylesheet" href="style.css">
<script src="app.js" defer></script>
</head>
<body>
<h1 id="title">Добро пожаловать!</h1>
<button id="changeBtn">Изменить заголовок</button>
</body>
</html>
style.css — внешний вид:
body {
font-family: sans-serif;
padding: 40px;
}
h1 {
color: #1e40af;
}
button {
padding: 10px 24px;
background: #3b82f6;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
}
app.js — поведение:
// Этот код выполнится ПОСЛЕ построения DOM (благодаря defer)
const title = document.getElementById('title');
const btn = document.getElementById('changeBtn');
btn.addEventListener('click', function() {
title.textContent = 'Заголовок изменился! 🎉';
});
🔑 Важно: если бы мы не использовали defer, а поставили <script> в <head> без атрибутов — JavaScript попытался бы найти #title и #changeBtn до того, как браузер разобрал тело страницы. Элементов ещё нет — код падает с ошибкой. defer решает именно эту проблему.
⚠️ Типичные ошибки новичков
// В head без defer — элемент ещё не создан!
const btn = document.getElementById('myBtn'); // null
btn.addEventListener('click', ...); // TypeError: Cannot read properties of null
Решение: добавьте defer к тегу <script>.
<script src="App.js" defer></script> <!-- файл называется app.js -->
<!-- На Linux/Mac — регистр важен! Скрипт не загрузится -->
Решение: проверяйте точное имя файла, особенно регистр букв.
<!-- Устаревший антипаттерн — смешивать HTML и JS -->
<button onclick="alert('клик')">Кнопка</button>
Решение: используйте addEventListener в отдельном JS-файле. Это чище, безопаснее и проще в поддержке.
✅ Итоги урока
- HTML — структура (что есть на странице), CSS — стиль (как выглядит), JS — поведение (что делает)
- Браузер строит DOM из HTML, CSSOM из CSS, объединяет в Render Tree и рисует пиксели
- Тег
<script>без атрибутов блокирует построение DOM — это медленно и опасно defer: скрипт скачивается параллельно, выполняется после DOM — лучший вариант для своих скриптовasync: выполняется сразу после скачивания, порядок не гарантирован — для независимых сторонних скриптов- Правильная структура: CSS в
<head>, свои скрипты в<head defer>
В следующем уроке мы разберём DOM изнутри — как JavaScript «видит» страницу и как искать нужные элементы. 🌳
HTML, CSS и JavaScript
7 вопросов