Смело ссылайтесь там, где раньше никто не ссылался: фрагменты текста.

Текстовые фрагменты позволяют указать фрагмент текста во фрагменте URL-адреса. При переходе по URL-адресу с таким фрагментом текста браузер может подчеркнуть и/или обратить на него внимание пользователя.

Идентификаторы фрагментов

Chrome 80 стал большим релизом. Он содержал ряд долгожданных функций, таких как модули ECMAScript в Web Workers , нулевое объединение , необязательное связывание и многое другое. О выпуске, как обычно, было объявлено в сообщении в блоге Chromium. Вы можете увидеть отрывок из сообщения в блоге на скриншоте ниже.

Сообщение в блоге 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 в заголовке Web Workers . Как вы можете видеть на скриншоте ниже, рассматриваемый <h1> не имеет атрибута id , а это означает, что я не могу ссылаться на этот заголовок. Эту проблему решают текстовые фрагменты.

Инструменты разработчика показывают заголовок без id .

Текстовые фрагменты

Предложение «Фрагменты текста» добавляет поддержку указания фрагмента текста в хеше URL-адреса. При переходе по URL-адресу с таким фрагментом текста пользовательский агент может подчеркнуть и/или привлечь к нему внимание пользователя.

Совместимость с браузером

Поддержка браузера

  • Хром: 89.
  • Край: 89.
  • Фаерфокс: 131.
  • Сафари: 18.2.

Источник

По соображениям безопасности эта функция требует, чтобы ссылки открывались в контексте noopener . Поэтому обязательно включите rel="noopener" в разметку привязки <a> или добавьте noopener в список функциональных возможностей окна Window.open() .

start

В своей простейшей форме синтаксис текстовых фрагментов выглядит следующим образом: за символом решетки # следует :~:text= и, наконец, start , который представляет текст в процентном кодировании, на который я хочу создать ссылку.

#:~:text=start

Например, предположим, что я хочу дать ссылку на заголовок «Модули ECMAScript в Web Workers» в сообщении блога, анонсирующем функции Chrome 80 , URL-адрес в этом случае будет таким:

https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html #:~:text=ECMAScript%20Modules%20in%20Web%20Workers

Фрагмент текста выделен так . Если вы нажмете ссылку в поддерживающем браузере, например Chrome, фрагмент текста будет выделен и прокрутится в поле зрения:

Фрагмент текста прокручивается в поле зрения и выделяется.

start и end

А что, если я хочу создать ссылку на весь раздел под названием «Модули ECMAScript в Web Workers» , а не только на его заголовок? Процентное кодирование всего текста раздела сделало бы URL-адрес невыполнимо длинным.

К счастью, есть лучший способ. Вместо всего текста я могу создать нужный текст, используя синтаксис start,end . Поэтому я указываю пару слов с процентной кодировкой в ​​начале нужного текста, и пару слов с процентной кодировкой в ​​конце нужного текста, через запятую , .

Это выглядит так:

https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html #:~:text=ECMAScript%20Modules%20in%20Web%20Workers,ES%20Modules%20in%20Web%20Workers. .

Для 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? Ответ в том, что в этом выпуске были представлены фрагменты текста:

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

Обратите внимание, как на скриншоте выше слово «текст» появляется четыре раза. Четвертое вхождение написано зеленым кодовым шрифтом. Если бы я хотел создать ссылку на это конкретное слово, я бы установил start на text . Поскольку слово «текст» — это всего лишь одно слово, у него не может быть end . Что теперь? URL-адрес https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html #:~:text=text соответствует первому вхождению слова «Текст» уже в заголовок:

Сопоставление текстового фрагмента при первом появлении «Текста».

К счастью, есть решение. В подобных случаях я могу указать prefix​- и -suffix . Слово перед зеленым кодовым шрифтом «текст» — это «the», а слово после — «параметр». Ни одно из трех других вхождений слова «текст» не имеет одинаковых окружающих слов. Вооружившись этими знаниями, я могу настроить предыдущий 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 не будет найдена в следующем примере, поскольку начальная строка «Быстрая» не появляется в одном непрерывном элементе уровня блока:

<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-адресов текстовых фрагментов . Мы предоставляем расширение для браузера с открытым исходным кодом под названием «Ссылка на фрагмент текста» , которое позволяет создавать ссылку на любой текст, выделив его, а затем нажав «Копировать ссылку на выделенный текст» в контекстном меню. Это расширение доступно для следующих браузеров:

Ссылка на расширение браузера Text Fragment .

Несколько фрагментов текста в одном 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-адресе, например, чтобы обеспечить значимый запасной вариант на случай, если исходный текст на странице изменится, и фрагмент текста больше не будет совпадать. 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 не будет осуществляться, а навигация по iframe не будет вызывать фрагмент текста.

Конфиденциальность

Важно, чтобы реализации спецификации Text Fragments не сообщали о том, был ли текстовый фрагмент найден на странице или нет. Хотя фрагменты элементов полностью находятся под контролем автора исходной страницы, текстовые фрагменты может создавать кто угодно. Помните, как в моем примере выше не было возможности ссылаться на модули ECMAScript в заголовке 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-адресам текстовых фрагментов.

Страница результатов поисковой системы Google, на которой показан избранный фрагмент. В строке состояния отображается URL-адрес текстовых фрагментов.
После перехода соответствующий раздел страницы прокручивается в поле зрения.

Заключение

URL-адрес текстовых фрагментов — это мощная функция для создания ссылок на произвольный текст на веб-страницах. Научное сообщество может использовать его для предоставления высокоточного цитирования или справочных ссылок. Поисковые системы могут использовать его для создания глубоких ссылок на текстовые результаты на страницах. Сайты социальных сетей могут использовать его, чтобы позволить пользователям делиться конкретными фрагментами веб-страницы, а не недоступными скриншотами. Я надеюсь, что вы начнете использовать URL-адреса текстовых фрагментов и найдете их такими же полезными, как и я. Обязательно установите браузерное расширение Link to Text Fragment .

Благодарности

Текстовые фрагменты были реализованы и определены Ником Беррисом и Дэвидом Боканом при участии Гранта Ванга . Спасибо Джо Медли за тщательный обзор этой статьи. Изображение героя Грега Ракози на Unsplash .