Адреса: относительный путь, якорь и ловушка file://
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Страница редко живёт одна: ссылки ведут на другие страницы, картинки и стили лежат в соседних файлах. В разметке их адреса записывают коротко — photo.png, ../photo.png, /photo.png, #comments, — а полный адрес браузер достраивает сам, от адреса страницы. Правил достройки немного, но одна косая черта меняет результат: картинка, которая видна по одному адресу, пропадает по другому, а файл, открытый с диска двойным щелчком, остаётся без стилей — и ни одной ошибки в консоли, страница просто выглядит голой. Разберём правила и проверим каждое на адресах, которые браузер считает так же, как мы.
Полный адрес страницы складывается из трёх частей. Возьмём https://shop.ru/blog/post/. https: — схема, то есть способ получить файл: здесь по сети, защищённым соединением. shop.ru — сайт, к которому обращается браузер. Остальное, начиная с косой черты, — путь внутри сайта: /blog/post/. Путь похож на папки на диске и читается так же — слева направо, от общего к частному.
Чаще всего адреса стоят в разметке в двух атрибутах. href — у ссылки <a> и у тега <link>, которым к странице подключают файл стилей: <link rel="stylesheet" href="style.css">, где rel="stylesheet" говорит, что по адресу лежат стили. src — у тегов, которые встраивают файл в страницу: у картинки <img> и у скрипта <script>. Полный адрес туда писать можно, но для файлов своего сайта так почти не делают: адрес выходит длинным и продолжает вести на старый домен, когда сайт переезжает. Пишут короткий, относительный адрес: полный браузер достроит сам, отсчитав от адреса страницы, на которой стоит тег.
Достройку проверяет функция resolve: ей передают адрес из атрибута и адрес страницы, от которой считать, а она возвращает полный адрес — по тем же правилам стандарта URL, по которым браузер достраивает href и src. Если полного адреса из этих двух строк не получается вовсе, ответом будет «не адрес» и первая строка: так выглядит, например, попытка достроить что-то от служебного адреса страницы примера.
Начнём с адреса, который уже полный. Ссылка на shop.ru стоит на странице новостей https://news.ru/today/. Запишите прогноз в поле под кодом: вернёт ли браузер ровно то, что в кавычках, или что-то поправит?
Адрес страницы новостей не понадобился: полному адресу отсчёт не нужен. Но одну косую черту браузер всё же дописал. Путь всегда начинается с неё, и у главной страницы сайта путь — одна косая черта. Это корень сайта. Запомните его: с диска корень окажется совсем в другом месте, и на этом расхождении построена половина ошибок начинающих.
Та же достройка решает, где браузер возьмёт файл стилей и скрипт, поэтому от неё зависит не только картинка. Классический симптом — «у меня на компьютере всё работало, а на хостинге страница голая»: пути не менялись, изменилось место, от которого они отсчитываются. Две задачи в конце статьи разбирают ровно эти случаи — сайт в папке хостинга и страницу, открытую двойным щелчком.
Короткий адрес записывают четырьмя способами, и каждый отсчитывает от своего места. Главное из этих мест — папка страницы: путь её адреса до последней косой черты включительно. У https://shop.ru/blog/post/ папка — /blog/post/.
| Запись | Как её читает браузер |
|---|---|
photo.png | файл в папке страницы |
./photo.png | то же самое: ./ значит «эта папка» |
../photo.png | подняться из папки страницы на одну папку выше и взять файл там; ../../ — на две |
/photo.png | начать от корня сайта, путь страницы не учитывать |
#comments | эта же страница, место с id="comments" |
Проверим все четыре записи на одной и той же странице https://shop.ru/blog/post/. Строк четыре, и прогнозов тоже четыре: запишите полный адрес для каждой до запуска, иначе после ответа будет казаться, что вы его знали.
Первые две строки считают от папки страницы: photo.png встал прямо в /blog/post/, а ../ сначала поднялся на ступеньку, в /blog/. Косая черта в начале третьей строки отбросила весь путь страницы, и файл ищется от корня. Якорь дописался к адресу страницы целиком — у него своё правило, о нём отдельный раздел.
Лишние ../ ошибкой не считаются: выше корня подняться некуда, и ../../../photo.png с этой страницы даст тот же https://shop.ru/photo.png.
Слово «папка» здесь — удобное сравнение, а не обещание. Сервер может хранить страницы как угодно: в файлах, в базе данных или собирать на лету из шаблона. Браузер его об этом не спрашивает и достраивает адрес по одному только тексту, который видит в адресной строке. Отсюда и следующая ловушка.
Один и тот же пост часто открывается двумя адресами: https://shop.ru/blog/post/ и https://shop.ru/blog/post. Для человека это одна страница, и поисковик, скорее всего, покажет её по обоим. Предскажите: одинаковыми ли получатся полные адреса картинки photo.png на этих двух страницах?
Не одинаковыми. Браузер не знает, что post — пост, а не файл: он видит только текст адреса. Правило такое: всё, что стоит после последней косой черты, считается именем файла, и при достройке это имя отбрасывается. У первого адреса после последней косой черты пусто, и папка — /blog/post/. У второго имя файла — post, и папка — /blog/. По тому же правилу у страницы https://shop.ru/catalog/tea.html папка — /catalog/: имя tea.html в отсчёт не входит.
Всё после последней косой черты — имя файла. При достройке оно отбрасывается, даже если это не файл, а пост.
Отсюда ошибка, которую долго не замечают. Пока посетители приходят по адресу с косой чертой, картинки на месте. Кто-то поставит ссылку без неё — и у пришедших по этой ссылке картинки пропадут: браузер попросит у сервера /blog/photo.png, а такого файла нет. Поэтому сайт выбирает одну форму адреса, а вторую сервер перенаправляет на неё. Файлы, общие для многих страниц, адресуют от корня — /img/photo.png: такому адресу косая черта на конце страницы безразлична.
Запись с решёткой, #comments, — адрес не файла, а места на странице: элемента с атрибутом id="comments". id — имя элемента, и на странице оно должно быть единственным. По ссылке <a href="#comments"> браузер не загружает страницу заново. Он прокручивает её к элементу с этим именем и дописывает #comments в адресную строку, а кнопка «назад» возвращает прежнее место на той же странице.
Часть адреса от решётки до конца называют фрагментом, а ссылку на него — якорем. Достраивается фрагмент по своему правилу, не такому, как у файлов. Предскажите две строки: что станет с именем post у страницы без косой черты и что станет со старым фрагментом #comments во второй строке?
Имя post осталось на месте. Якорь не отбрасывает ничего, кроме старого фрагмента: он берёт адрес страницы целиком и заменяет только хвост от решётки. Поэтому ссылки на места страницы работают при любой косой черте на конце — в отличие от путей к файлам, которым эта черта решает всё. Чтобы сослаться на место другой страницы, фрагмент дописывают к её адресу: /blog/post/#comments.
У resolve адрес страницы стоит второй строкой потому, что у страницы, на которой выполняются примеры учебника, обычного адреса нет. Её не скачивают с сервера: она собрана прямо на странице учебника, и вместо адреса у неё служебное about:srcdoc. Что получится, если отсчитывать от него? Предскажите обе строки.
У about:srcdoc нет пути с косыми чертами, и отсчитывать папку не от чего: относительный путь не складывается в адрес. А фрагмент складывается, ведь он только заменяет хвост. Поэтому в примерах учебника ссылки на якоря работают, а картинка с коротким src не загрузится: браузер даже не узнает, какой файл просить.
Якорем делятся местом в длинной странице: ссылка вида /blog/post/#comments открывает пост сразу прокрученным к отзывам. Учебник устроен так же — у каждого раздела статьи свой id, и оглавление ведёт не на страницу, а на раздел. Тот же id использует ссылка «к содержимому», которая появляется при нажатии Tab: она ведёт на #content, чтобы человек с клавиатуры не табил через всю шапку.
Ссылка на якорь остаётся переходом: адрес в строке меняется, и кнопка «назад» его отменяет. Этим ссылка и отличается от кнопки, которая действует на месте и адрес не трогает. Когда нужна ссылка, а когда кнопка, разбирает статья этой главы про настоящие кнопки, ссылки и подписи.
Файл, открытый с диска двойным щелчком, тоже получает адрес — со схемой file: и путём к файлу на диске. Например, file:///Users/anna/site/index.html: страница index.html в папке site у пользователя anna на Mac. Если вы уже создали первый файл на диске, щёлкните в адресную строку — адрес будет такого же вида. Три косые черты подряд — не опечатка: после двух косых черт в адресе стоит имя сайта, у файла на своём диске его нет, и сразу начинается путь со своей косой чертой.
На сервере корень — начало сайта. На диске сайта нет вовсе, и корнем оказывается начало всего диска. Анна подключила файл стилей двумя способами — с косой чертой в начале и без. Прогноз: куда поведёт каждый адрес?
Адрес с косой чертой в начале ушёл в корень диска, где никакого style.css нет, и страница открылась без стилей. Сообщения об ошибке на ней не появится: она просто будет выглядеть голой, и искать причину придётся в адресной строке. Относительный адрес остался в папке сайта, рядом со страницей, и сработал. В Windows путь к файлу начинается с буквы диска, file:///C:/Users/…, но и там косая черта в начале уводит из папки сайта — в корень диска C.
С диска /style.css — корень диска, а не папка сайта. Если страницу открывают двойным щелчком, пути пишут без косой черты в начале.
Ловушку снимает локальный сервер — программа на вашем компьютере, которая отдаёт папку сайта по адресу, начинающемуся с http://localhost. Корнем тогда становится папка сайта, и /style.css находится. Например, если на компьютере установлен Python, сервер запускается одной командой в терминале, открытом в папке сайта: python3 -m http.server. Пока терминал открыт, сервер работает: наберите в браузере адрес http://localhost:8000/, и откроется index.html из этой папки.
Локальный сервер пригодится и позже, причём по другой причине. Chrome и Firefox с обычными настройками не запускают с диска скрипты-модули из файлов, даже если все пути в них верные: модули разрешено загружать только по сети. Что такое модули и почему им нужен сервер, объясняет часть I — статья про модули в браузере.
Всё вместе — в одной таблице. «С сервера» означает сайт, отданный сервером, включая локальный; «с диска» — страницу, открытую двойным щелчком.
| Запись | С сервера | С диска | Когда писать |
|---|---|---|---|
https://… | работает | работает, если есть интернет | файл на другом сайте |
/img/photo.png | работает на любой странице, если сайт лежит в корне домена | ведёт в корень диска | общие файлы сайта, который открывают только с сервера |
img/photo.png, ../img/photo.png | работает, пока у страницы та же папка; косая черта на конце папку меняет | работает | файлы рядом со страницей и всё, что открывают с диска |
#comments | работает | работает | место на той же странице |
Страницы пролога лежат в одной папке и открываются двойным щелчком, поэтому в них пишут относительные пути: style.css, img/photo.png. Адрес от корня удобен большому сайту на сервере, где одна и та же картинка подключается со страниц из разных папок, — но только там, где корень сайта совпадает с корнем домена. Как только сайт выкладывают в папку, тот же адрес ломается: это вторая задача ниже.
По тем же правилам браузер находит и скрипт. В части I JavaScript подключают к странице тегом <script src="app.js">, и app.js в нём — такой же относительный путь от папки страницы. Как это устроено, показывает статья про первую программу и подключение скрипта файлом.
- Полный адрес — схема, сайт и путь:
https://shop.ru/blog/post/. Короткий адрес вhrefиsrcбраузер достраивает до полного от адреса страницы. - Отсчёт идёт от папки страницы — пути до последней косой черты.
photo.png— файл в этой папке,../— на папку выше, косая черта в начале — от корня сайта. - Всё после последней косой черты браузер считает именем файла и отбрасывает. Поэтому у
/blog/postи/blog/post/разные папки и разные адреса одной и той же картинки. - Якорь
#idзаменяет только фрагмент — хвост адреса после решётки. Страница не перезагружается: браузер прокручивает её к элементу с этимid, а «назад» возвращает прежнее место. - С диска адрес страницы начинается с
file:///, а корень — начало диска, и/style.cssтам не найдётся. Страницам, которые открывают двойным щелчком, — относительные пути; адресу от корня нужен сервер, хотя бы локальный. - У страницы, на которой выполняются примеры учебника, вместо адреса
about:srcdoc: короткий путь от него не достраивается, а якорь достраивается. Поэтомуresolveполучает адрес страницы второй строкой.
1/4Страница открыта по адресу https://shop.ru/blog/post — без косой черты на конце. На ней стоит <img src="../cat.png">. Какой файл попросит браузер?
2/4Страница открыта с диска двойным щелчком, файл style.css лежит рядом с ней, а подключён так: <link rel="stylesheet" href="/style.css">. Что будет?
3/4Что происходит при щелчке по ссылке <a href="#comments">?
4/4Картинка logo.png лежит в папке img рядом со страницей. Какой адрес сработает и на сервере, и с диска?
Картинка из соседней папки
Страница чая в магазине лежит по адресу https://shop.ru/catalog/tea.html, а картинки сайта — в папке img в корне: https://shop.ru/img/green.png. На странице написали <img src="img/green.png">, и картинки нет. Предскажите, какой файл попросил браузер, и запустите пример.
Теперь исправьте адрес в первых кавычках так, чтобы вывод стал https://shop.ru/img/green.png. Способов два — найдите оба и решите, какой из них переживёт открытие магазина с диска.
Папка страницы — /catalog/: имя файла tea.html отбрасывается. Поэтому img/green.png превратился в https://shop.ru/catalog/img/green.png, а такой папки на сайте нет.
Первый способ — подняться на папку выше, второй — начать от корня:
На сервере обе строки дают https://shop.ru/img/green.png. Разница видна с диска. Если магазин лежит в папке /Users/anna/shop/, первый адрес найдёт file:///Users/anna/shop/img/green.png, а второй уйдёт в file:///img/green.png. С диска сработает только относительный путь ../img/green.png.
Сайт в папке хостинга
Анна выложила сайт на хостинг, и он открылся не в корне домена, а в папке. Главная — https://anna.github.io/site/, страница «Обо мне» — https://anna.github.io/site/about/, файл стилей — https://anna.github.io/site/style.css. На обеих страницах стили подключены как /style.css: Анна помнила, что адресу от корня косая черта на конце безразлична. Предскажите обе строки.
Перепишите адреса в первых кавычках обеих строк так, чтобы обе напечатали https://anna.github.io/site/style.css, а стили находились бы и тогда, когда Анна открывает сайт с диска.
Корень — начало домена, https://anna.github.io/, а не папка сайта, поэтому обе страницы просят https://anna.github.io/style.css. Это та же ловушка, что с диском: косая черта в начале ведёт в корень, а сайт лежит в папке.
Нужны относительные пути: на главной style.css, на странице «Обо мне» — ../style.css.
Адрес /site/style.css тоже дал бы нужную строку на сервере, но с диска превратился бы в file:///site/style.css. Относительный путь работает в обоих местах: с диска ../style.css из файла file:///Users/anna/site/about/index.html даёт file:///Users/anna/site/style.css.
Стили и якорь на своём файле
Этот опыт — вне песочницы. Возьмите папку с index.html из задачи про первый файл на диске и положите рядом файл style.css с одной строкой body { background: lightyellow; } — это правило красит фон страницы светло-жёлтым, а как устроены правила, разбирает вторая глава пролога. В <head> страницы подключите файл: <link rel="stylesheet" href="/style.css">. Откройте страницу двойным щелчком и посмотрите на фон. Потом уберите косую черту в начале адреса, сохраните файл и обновите страницу.
Вторая часть — якорь. Добавьте в <body> ссылку <a href="#comments">К отзывам</a>, под ней — много абзацев текста, а в самом конце — заголовок <h2 id="comments">Отзывы</h2>. Щёлкните по ссылке и посмотрите на адресную строку, затем нажмите «назад».
С /style.css фон остаётся белым: браузер искал file:///style.css в корне диска, а там файла нет. Без косой черты адрес стал путём к style.css в папке страницы, и фон пожелтел.
По ссылке страница не перезагрузилась, а прокрутилась к заголовку, и в конце адреса появилось #comments. «Назад» убрал фрагмент и вернул страницу в начало. Если прокрутки не видно, проверьте две вещи: текста под ссылкой должно хватать, чтобы заголовок мог подняться к верху окна, а id должен совпадать с тем, что стоит после решётки, буква в букву.