Фрагменты текста позволяют указать фрагмент текста в фрагменте URL. При переходе по URL с таким фрагментом текста браузер может выделить его и привлечь к нему внимание пользователя.
Идентификаторы фрагментов
Chrome 80 – это крупный выпуск. В ней было много долгожданных функций, таких как модули ECMAScript в веб-воркерах, оператор нулевого слияния, оператор опциональной последовательности и т. д. Как обычно, о выпуске было объявлено в записи в блоге Chromium. Ниже приведен скриншот с отрывком из этой записи.
id.Вы, вероятно, спрашиваете себя, что означают все эти красные рамки. Они являются результатом выполнения следующего фрагмента кода в DevTools. Выделяет все элементы с атрибутом id.
document.querySelectorAll('[id]').forEach((el) => {
el.style.border = 'solid 2px red';
});
Я могу создать ссылку на контент для любого элемента, выделенного красной рамкой, благодаря идентификатору фрагмента, который я затем использую в хеше URL страницы. Предположим, я хочу сослаться на блок Оставьте отзыв на форуме в боковой панели. Я могу сделать это, вручную создав URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1.
Как видно на панели "Элементы" в Инструментах разработчика, у элемента есть атрибут id со значением HTML1.
id элемента.Если проанализировать этот URL с помощью конструктора URL() в JavaScript, можно увидеть разные компоненты.
Обратите внимание на свойство hash со значением #HTML1.
new URL('https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1');
/* Creates a new `URL` object
URL {
hash: "#HTML1"
host: "blog.chromium.org"
hostname: "blog.chromium.org"
href: "https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1"
origin: "https://blog.chromium.org"
password: ""
pathname: "/2019/12/chrome-80-content-indexing-es-modules.html"
port: ""
protocol: "https:"
search: ""
searchParams: URLSearchParams {}
username: ""
}
*/
Однако тот факт, что мне пришлось открыть Инструменты разработчика, чтобы найти id элемента, говорит о том, что автор записи в блоге вряд ли хотел, чтобы на этот раздел страницы ссылались.
Что делать, если я хочу сослаться на что-то без id? Допустим, я хочу сослаться на заголовок ECMAScript Modules in Web Workers. Как видно на скриншоте ниже, у элемента <h1> нет атрибута id, а значит, я не могу сослаться на этот заголовок. Эту проблему решают фрагменты текста.
id.Фрагменты текста
Предложение Фрагменты текста добавляет поддержку указания фрагмента текста в хеше URL. При переходе по URL с таким фрагментом текста агент пользователя может выделить его и привлечь к нему внимание пользователя.
Совместимость браузеров
Из соображений безопасности ссылки должны открываться в контексте noopener.
Поэтому не забудьте добавить rel="noopener" в разметку привязки <a> или noopener в список функций окна Window.open().
start
В простейшем случае синтаксис фрагментов текста выглядит следующим образом: символ решетки #, за которым следует :~:text= и, наконец, start, который представляет собой процентно-закодированный текст, на который я хочу сослаться.
#:~:text=start
Например, если вы хотите сослаться на заголовок ECMAScript Modules in Web Workers в записи блога о функциях Chrome 80, URL будет выглядеть так:
Фрагмент текста выделен вот так. Если вы нажмете на ссылку в поддерживаемом браузере, например Chrome, фрагмент текста будет выделен и показан на экране:
start и end
А что, если я хочу сослаться на весь раздел под названием ECMAScript Modules in Web Workers, а не только на его заголовок? Если закодировать весь текст раздела, URL получится слишком длинным.
К счастью, есть более эффективный способ. Вместо всего текста я могу выделить определенную часть текста с помощью синтаксиса start,end. Я указываю несколько слов в начале и в конце фрагмента текста, закодированных в процентах и разделенных запятой.
Вот как это выглядит:
Для start у меня есть ECMAScript%20Modules%20in%20Web%20Workers, затем запятая, а затем ES%20Modules%20in%20Web%20Workers. как end. Если вы нажмете на ссылку в поддерживаемом браузере, например Chrome, то весь раздел будет выделен и прокручен в область просмотра:
Возможно, вы задаетесь вопросом, почему я выбрал именно start и end. На самом деле, немного более короткий URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers., в котором всего по два слова с каждой стороны, тоже подошел бы. Сравните start и end с предыдущими значениями.
Если я пойду ещё дальше и буду использовать одно слово для обозначения и start, и end, то у меня возникнут проблемы. URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript,Workers. стал ещё короче, но выделенный фрагмент текста уже не тот, что был выбран изначально. Выделение останавливается на первом вхождении слова Workers., что правильно, но не соответствует моему намерению. Проблема в том, что выбранный раздел не идентифицируется однозначно текущими значениями start и end, состоящими из одного слова:
prefix- и -suffix
Чтобы получить уникальную ссылку, используйте достаточно длинные значения для start и end.
Однако в некоторых случаях это невозможно. Кстати, почему я выбрал в качестве примера запись в блоге о выпуске Chrome 80? Ответ заключается в том, что в этом выпуске
были представлены текстовые фрагменты:
Обратите внимание, что на скриншоте выше слово "text" встречается четыре раза. Четвёртое вхождение написано зелёным шрифтом кода. Если бы я хотел сослаться на это конкретное слово, я бы установил start на text. Поскольку слово "текст" состоит из одного слова, end не может быть. Что делать? URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=text соответствует первому вхождению слова "Text" в заголовке:
К счастью, есть решение. В таких случаях я могу указать prefix- и -suffix. Перед словом "text", выделенным зеленым цветом, стоит слово "the", а после него – "parameter". Ни в одном из трех других случаев употребления слова "текст" нет такого же окружения. Теперь я могу изменить предыдущий URL, добавив в него prefix- и -suffix. Как и другие параметры, они должны быть закодированы в процентах и могут содержать более одного слова.
https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=the-,text,-parameter.
Чтобы синтаксический анализатор мог четко идентифицировать prefix- и -suffix, их необходимо отделить от start и необязательного end с помощью тире -.
Полный синтаксис
Ниже приведен полный синтаксис фрагментов текста. Квадратные скобки указывают на необязательный параметр.
Значения всех параметров должны быть закодированы в процентах. Это особенно важно для тире -, амперсанда & и запятой ,, чтобы они не интерпретировались как часть синтаксиса текстовой директивы.
#:~:text=[prefix-,]start[,end][,-suffix]
Каждый из параметров prefix-, start, end и -suffix может соответствовать только тексту в пределах одного блочного элемента, но полные диапазоны start,end могут охватывать несколько блоков. Например, селектор :~:text=The quick,lazy dog не сработает в следующем случае, потому что начальная строка "The quick" не находится в одном непрерывном элементе блочного уровня:
<div>
The
<div></div>
quick brown fox
</div>
<div>jumped over the lazy dog</div>
Однако в следующем примере совпадение будет найдено:
<div>The quick brown fox</div>
<div>jumped over the lazy dog</div>
Как создать URL фрагмента текста с помощью расширения браузера
Создавать URL-адреса текстовых фрагментов вручную утомительно, особенно когда нужно убедиться, что они уникальны. Если вы хотите узнать больше, ознакомьтесь с инструкциями по созданию URL с фрагментами текста. Мы предлагаем расширение для браузера с открытым исходным кодом Link to Text Fragment, которое позволяет создавать ссылки на любой текст. Для этого нужно выделить его и выбрать в контекстном меню пункт "Копировать ссылку на выделенный текст". Это расширение доступно для следующих браузеров:
- Link to Text Fragment для Google Chrome
- Link to Text Fragment для Microsoft Edge
- Link to Text Fragment для Mozilla Firefox
- Ссылка на текстовый фрагмент для Apple Safari
Расширение для браузера
Несколько фрагментов текста в одном URL
Обратите внимание, что в одном URL может быть несколько фрагментов текста. Фрагменты текста нужно разделять амперсандом (&). Вот пример ссылки с тремя фрагментами текста:
https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=Text%20URL%20Fragments&text=text,-parameter&text=:~:text=On%20islands,%20birds%20can%20contribute%20as%20much%20as%2060%25%20of%20a%20cat's%20diet.
Смешивание элементов и фрагментов текста
Традиционные фрагменты элементов можно комбинировать с текстовыми фрагментами. Вы можете использовать оба варианта в одном URL, например чтобы обеспечить резервный вариант на случай, если исходный текст на странице изменится и фрагмент текста перестанет соответствовать. URL https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1:~:text=Give%20us%20feedback%20in%20our%20Product%20Forums., который ведет на раздел Оставьте отзыв на форумах, содержит как фрагмент элемента (HTML1), так и фрагмент текста (text=Give%20us%20feedback%20in%20our%20Product%20Forums.):
Директива фрагмента
В синтаксисе есть один элемент, который мы ещё не рассмотрели, – директива фрагмента :~:. Чтобы избежать проблем с совместимостью с существующими фрагментами элементов URL, в спецификации текстовых фрагментов введена директива фрагмента. Директива фрагмента – это часть фрагмента URL, ограниченная последовательностью символов :~:. Он зарезервирован для инструкций агента пользователя, таких как text=, и удаляется из URL во время загрузки, чтобы скрипты автора не могли напрямую взаимодействовать с ним. Инструкции для агента пользователя также называются директивами. В данном случае text= называется текстовой директивой.
Распознавание объектов
Чтобы определить поддержку, проверьте свойство fragmentDirective, доступное только для чтения, в document. Директива фрагмента – это механизм, позволяющий URL передавать инструкции браузеру, а не документу. Это позволяет избежать прямого взаимодействия со скриптом автора, чтобы в будущем можно было добавлять инструкции агента пользователя, не опасаясь критических изменений в существующем контенте. Например, в будущем мы можем добавить подсказки для перевода.
if ('fragmentDirective' in document) {
// Text Fragments is supported.
}
Обнаружение функций в основном предназначено для случаев, когда ссылки генерируются динамически (например, поисковыми системами), чтобы избежать показа ссылок на фрагменты текста браузерам, которые их не поддерживают.
Оформление фрагментов текста
По умолчанию браузеры оформляют фрагменты текста так же, как и mark (обычно это черный текст на желтом фоне, системные цвета CSS для mark). Таблица стилей агента пользователя содержит CSS, который выглядит следующим образом:
:root::target-text {
color: MarkText;
background: Mark;
}
Как видите, браузер предоставляет псевдоселектор ::target-text, который можно использовать для настройки выделения. Например, вы можете сделать так, чтобы фрагменты текста были черными на красном фоне. Как всегда, обязательно
проверьте цветовой контраст,
чтобы переопределяющий стиль не вызывал проблем с доступностью, и убедитесь, что выделение действительно
визуально выделяется на фоне остального контента.
:root::target-text {
color: black;
background-color: red;
}
Полифил
Функцию фрагментов текста можно в некоторой степени реализовать с помощью полифила. Для браузеров, в которых нет встроенной поддержки фрагментов текста, мы предоставляем полифил, который используется расширением. В этом случае функция реализуется с помощью JavaScript.
Автоматическое создание ссылок на фрагменты текста
Полифил содержит файл fragment-generation-utils.js, который можно импортировать и использовать для создания ссылок на фрагменты текста. Это показано в примере кода:
const { generateFragment } = await import('https://unpkg.com/text-fragments-polyfill/dist/fragment-generation-utils.js');
const result = generateFragment(window.getSelection());
if (result.status === 0) {
let url = `${location.origin}${location.pathname}${location.search}`;
const fragment = result.fragment;
const prefix = fragment.prefix ?
`${encodeURIComponent(fragment.prefix)}-,` :
'';
const suffix = fragment.suffix ?
`,-${encodeURIComponent(fragment.suffix)}` :
'';
const start = encodeURIComponent(fragment.textStart);
const end = fragment.textEnd ?
`,${encodeURIComponent(fragment.textEnd)}` :
'';
url += `#:~:text=${prefix}${start}${end}${suffix}`;
console.log(url);
}
Получение фрагментов текста для аналитики
Многие сайты используют фрагменты для маршрутизации, поэтому браузеры удаляют фрагменты текста, чтобы не нарушать работу этих страниц. Существует признанная необходимость предоставлять страницам ссылки на фрагменты текста, например для аналитики, но предложенное решение пока не реализовано. В качестве временного решения вы можете использовать приведенный ниже код.
new URL(performance.getEntries().find(({ type }) => type === 'navigate').name).hash;
Безопасность
Директивы фрагментов текста вызываются только при полной навигации (не на той же странице), которая является результатом активации пользователя.
Кроме того, если источник перехода отличается от целевой страницы, переход должен выполняться в контексте noopener, чтобы целевая страница была достаточно изолирована. Директивы фрагментов текста применяются только к основному фрейму. Это означает, что поиск текста не выполняется внутри окон iframe, а навигация по ним не вызывает фрагмент текста.
Конфиденциальность
Важно, чтобы реализации спецификации Text Fragments не раскрывали, был ли найден фрагмент текста на странице. Если фрагменты элементов полностью контролируются автором исходной страницы, то фрагменты текста может создавать кто угодно. Помните, как в моем примере не было возможности сослаться на заголовок ECMAScript Modules in Web Workers, поскольку у <h1> не было id, но как любой, включая меня, мог просто сослаться на любое место, тщательно создав текстовый фрагмент?
Представьте, что я управляю вредоносной рекламной сетью evil-ads.example.com. Представьте, что в одном из моих рекламных фреймов iframe я динамически создал скрытый перекрестный фрейм iframe для dating.example.com с URL фрагмента текста dating.example.com#:~:text=Log%20Out, когда пользователь взаимодействует с объявлением. Если текст "Выйти" найден, я знаю, что жертва вошла в dating.example.com, что я могу использовать для профилирования пользователей. Поскольку простая реализация фрагментов текста может решить, что успешное совпадение должно вызвать переключение фокуса, на evil-ads.example.com я мог бы прослушивать событие blur и, таким образом, знать, когда произошло совпадение. В Chrome мы реализовали фрагменты текста таким образом, что такой сценарий невозможен.
Другой вариант атаки – использование сетевого трафика на основе позиции прокрутки. Предположим, у меня был доступ к журналам сетевого трафика моей жертвы, например, в качестве администратора интрасети компании. Теперь представьте, что существует длинный документ отдела кадров Порядок действий, если вы страдаете от..., а затем список состояний, таких как выгорание, тревога и т. д. Я мог бы разместить пиксель отслеживания рядом с каждым элементом в списке. Если я затем определю, что загрузка документа по времени совпадает с загрузкой пикселя отслеживания рядом, например, с элементом выгорание, то я, как администратор интрасети, смогу определить, что сотрудник перешел по ссылке на фрагмент текста с :~:text=burn%20out, который сотрудник мог считать конфиденциальным и никому не видимым. Поскольку этот пример несколько надуман и для его реализации требуются очень специфические условия, команда безопасности Chrome оценила риск реализации прокрутки при навигации как управляемый.
Другие агенты пользователя могут показывать элемент интерфейса для прокрутки вручную.
Для сайтов, которые хотят отказаться от этой функции, Chromium поддерживает значение заголовка Document Policy, которое они могут отправить, чтобы агенты пользователя не обрабатывали URL-адреса фрагментов текста.
Document-Policy: force-load-at-top
Как отключить фрагменты текста
Проще всего отключить эту функцию с помощью расширения, которое может внедрять заголовки HTTP-ответов, например ModHeader (не является продуктом Google). Оно позволяет вставить заголовок ответа (не запроса) следующим образом:
Document-Policy: force-load-at-top
Другой, более сложный способ – использовать корпоративную настройку ScrollToTextFragmentEnabled.
Чтобы сделать это в macOS, вставьте в терминал следующую команду:
defaults write com.google.Chrome ScrollToTextFragmentEnabled -bool false
В Windows следуйте инструкциям в Справочном центре Google Chrome Enterprise.
Фрагменты текста в веб-поиске
По некоторым запросам поисковая система Google предоставляет краткий ответ или сводку с фрагментом контента с соответствующего сайта. Выделенные описания чаще всего появляются, когда запрос сформулирован в виде вопроса. Нажав на выделенное описание, пользователь переходит непосредственно к тексту выделенного описания на исходной веб-странице. Это возможно благодаря автоматически созданным URL фрагментов текста.
Заключение
URL фрагментов текста – это мощная функция, позволяющая ссылаться на произвольный текст на веб-страницах. Научное сообщество может использовать его для предоставления высокоточных ссылок на цитаты или справочные материалы. Поисковые системы могут использовать его для создания ссылок на контент, которые ведут к текстовым результатам на страницах. Социальные сети могут использовать его, чтобы пользователи могли делиться определенными фрагментами веб-страницы, а не недоступными скриншотами. Надеюсь, вы начнете использовать URL фрагментов текста и найдете их такими же полезными, как и я. Обязательно установите расширение для браузера Link to Text Fragment.
Статьи по теме
- Черновик спецификации
- Проверка TAG
- Запись о статусе платформы Chrome
- Ошибка отслеживания в Chrome
- Intent to Ship thread
- Цепочка WebKit-Dev
- Обсуждение стандартов Mozilla
Благодарности
Фрагменты текста были реализованы и описаны Ником Беррисом (Nick Burris) и Дэвидом Боканом (David Bokan) при участии Гранта Вана (Grant Wang). Благодарим Джо Медли (Joe Medley) за тщательную проверку этого документа.