Инспектор элементов: смотрим дерево вживую
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Вкладка Elements — это не просмотр исходника, а окно в живой DOM: то же самое дерево, по которому ходит ваш querySelector. Научиться читать и править его мышью быстрее, чем обвешивать код строчками console.log.
Откройте инструменты разработчика (F12, в macOS — Cmd+Option+I) и перейдите на вкладку Elements (в Firefox она называется «Инспектор»). Похоже на HTML-файл с отступами, но это результат разбора: браузер уже прочитал текст документа, построил из него дерево узлов и показывает вам именно дерево. Наведите мышь на строку — на странице подсветится область элемента; внизу панели появятся «хлебные крошки» вроде html > body > ul > li, то есть путь от корня.
В разметке этого примера нет ни одного закрывающего </li> — и всё равно в дереве три полноценных элемента. Парсер закрыл пункты сам, как велит спецификация. Инспектор покажет ровно это: три аккуратные строки с </li> на конце, которых вы не писали. Первый повод запомнить: панель показывает итог, а не то, что вы набрали в редакторе.
Текстовые узлы. Переводы строк и отступы между тегами — тоже узлы, о них статья про дерево документа. Поэтому childNodes почти всегда длиннее children: во втором только элементы.
Комментарии. Это полноценные узлы дерева; некоторые фреймворки оставляют их как невидимые метки-разделители.
Псевдоэлементы. Строки ::before и ::after инспектор рисует отдельно, хотя в разметке их нет и достать их из JavaScript нельзя — они целиком порождены CSS.
#shadow-root. Под узлом может прятаться собственное внутреннее дерево. Так устроены веб-компоненты, а ещё <input type="range"> и <video> — у них шейдоу закрытый, браузер показывает его только по особой настройке.
Кликните по строке в Elements — узел становится выбранным (справа от него появляется пометка == $0). Теперь переключитесь в Console и наберите $0: это ссылка на тот самый элемент. Не нужно сочинять селектор — ткнули мышью и продолжили кодом. Ещё удобнее наоборот: кнопка со стрелкой в углу панели (Ctrl+Shift+C) позволяет выбрать элемент прямо на странице.
| Что набрать в консоли | Что это даёт |
|---|---|
| $0 | элемент, выбранный в Elements прямо сейчас |
| $1 … $4 | выбранные раньше, от свежего к старому |
| $("h2") | то же, что document.querySelector |
| $$(".item") | то же, что querySelectorAll, но сразу массив |
| inspect(el) | перепрыгнуть на этот узел во вкладке Elements |
| copy(el.outerHTML) | положить разметку узла в буфер обмена |
$0, $$, inspect, copy существуют только внутри консоли DevTools. В файле скрипта их нет — код с $0 упадёт с ReferenceError. А если страница объявила свою переменную $ (классика — jQuery), она перебивает помощника: $$ при этом обычно продолжает работать.
Разница между $$(".note") и querySelectorAll не косметическая: второй возвращает NodeList, а у него нет привычных методов массива.
По тексту узла можно дважды кликнуть и переписать его. Правый клик → Edit as HTML отдаёт на правку весь фрагмент целиком, Delete удаляет узел, а мышью строку дерева можно перетащить в другое место. Всё это — обычные операции над DOM, ровно те же, что делает код.
Правки в Elements живут ровно до F5: вы редактируете дерево в памяти вкладки, а не файл на диске. Подобрали удачный отступ или цвет — переносите в CSS сразу, иначе перезагрузка съест находку.
Правая часть панели — блок Styles. Там видно, какое правило откуда пришло и что чем перекрыто (перечёркнутое объявление проиграло). Галочка слева выключает объявление, не удаляя его; кнопка :hov принудительно включает состояния :hover, :focus, :active — иначе стиль исчезает ровно в тот момент, когда вы уводите мышь к панели. Новые объявления, набранные в блоке element.style, попадают в атрибут style элемента: это то же самое, что запись в el.style из кода.
Нажмите Ctrl+F (Cmd+F), находясь внутри Elements — откроется строка поиска по дереву, а не по странице. Она принимает три вида запроса: обычный текст, CSS-селектор и XPath, а рядом показывает счётчик: номер текущего совпадения и сколько их всего (в английском интерфейсе — 1 of 5); Enter перебирает совпадения по очереди. Браузер сам догадывается, что вы ввели: .btn он поймёт как селектор, а слово «Купить» — как текст.
Отсюда растёт хорошая привычка: сначала вбить будущий селектор в поиск по дереву — счётчик мгновенно скажет, сколько узлов под него попадёт, — и только потом писать его в коде. Обратный путь тоже есть: правый клик по узлу → Copy → Copy selector. Но полученное обычно хрупко, потому что привязано к позиции: div:nth-child(3) > span развалится при первой же перестановке блоков. Такой селектор годится как черновик, а в код лучше отправить id, data--атрибут или осмысленный класс.
Самое частое недоумение: в файле одно, в инспекторе другое. Так и задумано. С сервера приходит текст; браузер разбирает его, чинит ошибки разметки и достраивает обязательные узлы, которых вы не писали. Хрестоматийный случай — таблица: между <table> и <tr> парсер молча вставляет <tbody>, и селектор table > tr из-за этого не находит ничего.
Второй источник расхождений — код. Приложение, которое рисует интерфейс в браузере, присылает почти пустой документ: в исходнике вы увидите <div id="app"></div>, а в Elements — сотни узлов. Добавьте сюда расширения браузера, которые тоже дописывают свои элементы в чужие страницы, — и вопрос «откуда взялся этот div» перестаёт быть загадкой.
| Куда смотрим | Что видим |
|---|---|
| view-source: (Ctrl+U) или Network → документ → Response | байты, которые действительно прислал сервер |
| Вкладка Elements | DOM прямо сейчас: после парсера, скриптов и расширений |
| document.documentElement.outerHTML | то же текущее дерево, сериализованное обратно в строку |
- Вкладка Elements показывает не HTML-файл, а текущее состояние DOM: с достроенными узлами, результатами работы скриптов и вставками расширений.
- Выбранный в панели узел доступен в консоли как $0; рядом живут $1…$4, $(), $$(), inspect() и copy() — они существуют только внутри DevTools.
- Правка текста, Edit as HTML, перетаскивание узла и добавление стилей — обычные операции над DOM, которые исчезают при перезагрузке.
- Ctrl+F внутри Elements ищет по тексту, CSS-селектору и XPath: счётчик совпадений позволяет проверить селектор до того, как он попадёт в код.
- Настоящий ответ сервера смотрят в view-source: или в Network → Response; расхождение с деревом — норма, а не признак поломки.
1/3В файле было написано <table><tr><td>1</td></tr></table>, а инспектор показывает внутри table ещё и tbody. Почему?
2/3Что такое $0 в консоли инструментов разработчика?
3/3Вы через Edit as HTML заменили заголовок на странице интернет-магазина и нажали F5. Что произойдёт?
Проверьте селектор в дереве, прежде чем писать его в код
В корзине три пункта, один из них помечен классом sold. Наивный селектор ловит все три. Подберите такой, который вернёт только непроданные, и убедитесь двумя способами: через Ctrl+F во вкладке Elements (счётчик должен показать 2 совпадения) и через querySelectorAll в консоли.
Годится псевдокласс отрицания: он отсекает пункты с классом sold.
Тот же текст #cart .item:not(.sold) можно вставить в поиск по дереву: браузер поймёт его как CSS-селектор, подсветит два узла и покажет счётчик 1 of 2. Это и есть смысл упражнения — счётчик отвечает на вопрос «сколько элементов попадёт» до того, как селектор уедет в код.
Повторите правку из инспектора кодом
Всё, что вы делаете мышью в Elements, выражается парой строк на JavaScript. Возьмите значок-черновик и сделайте с ним то же, что сделали бы вручную: смените текст на «опубликовано» и допишите ему в атрибут style зелёный цвет. Затем выведите outerHTML и сравните с тем, что показывала бы панель.
Двойной клик по тексту — это textContent, строчка в блоке element.style — это el.style.
Обратите внимание: цвет ушёл в атрибут style самого элемента — поэтому в панели он и появляется в блоке element.style, поверх всех правил из файлов CSS. И, как и правка мышью, эта строка живёт до перезагрузки.