以前无人关联的大胆添加链接:文本片段

借助文本片段,您可以在网址片段中指定文本摘要。 当浏览器导航到包含此类文本片段的网址时,可以突出显示该片段和/或将其提请用户注意。

Chrome 80 是一个重大版本。其中包含许多备受期待的功能,例如 Web Worker 中的 ECMAScript 模块nullish 合并可选链接等。此次发布版本一如既往,是在 Chromium 博客上的博文中公布的。您可以在下面的屏幕截图中看到该博文的摘录。

Chromium 博文,其中包含带有 id 属性的元素的红色方框。

您可能想知道所有红色方框的含义。这些是运行以下代码段在 DevTools 中生成的结果。它会突出显示具有 id 属性的所有元素。

document.querySelectorAll('[id]').forEach((el) => {
  el.style.border = 'solid 2px red';
});

借助fragment 标识符,我可以将深层链接放置到用红色框突出显示的任何元素,然后在网页网址的哈希中使用该标识符。假设我想深层链接到侧边的在我们的产品论坛中向我们提供反馈框,可以通过手动创建网址 https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#HTML1 来实现。如您在开发者工具的“Elements”面板中看到的,相关元素具有值为 HTML1id 属性。

显示元素 id 的开发者工具。

如果我使用 JavaScript 的 URL() 构造函数解析此网址,就会显示不同的组件。请注意值为 #HTML1hash 属性。

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 的内容,该怎么办?假设我想链接到 Web Worker 中的 ECMAScript 模块标题。如您在下面的屏幕截图中所看到,相关 <h1> 没有 id 属性,这意味着我无法链接到此标题。这就是 Text Fragment 解决的问题。

开发者工具显示了没有 id 的标题。

文本 fragment

文本片段提案支持在网址哈希中指定文本摘要。在导航到包含此类文本片段的网址时,用户代理可以强调此片段和/或将其提请用户注意。

浏览器兼容性

浏览器支持

  • Chrome:89。
  • Edge:89。
  • Firefox:131.
  • Safari:18.2。

来源

出于安全考虑,此功能要求在 noopener 上下文中打开链接。因此,请务必在 <a> 锚点标记中添加 rel="noopener",或将 noopener 添加到窗口功能列表的 Window.open() 中。

start

最简单的形式的文本片段语法如下:井号 #,后跟 :~:text=,最后是 start,表示我要链接到的百分比编码文本。

#:~:text=start

例如,假设我想链接到宣布 Chrome 80 中功能的博文中的 ECMAScript Modules in Web Workers 标题,在这种情况下,网址将为:

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

文本片段会以这种方式突出显示。如果您在 Chrome 等支持的浏览器中点击链接,系统会突出显示相应文本片段,并将其滚动到视野中:

文本 fragment 滚动到视图中并突出显示。

startend

现在,如果我想链接到标题为 ECMAScript Modules in Web Workers 的整个部分(而不仅仅是标题),该怎么办?如果对该部分的全部文本进行百分比编码,生成的网址将会过长而无法使用。

幸运的是,我们有更好的方法。我可以使用 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)中点击后,整个部分会突出显示并滚动到视野中:

文本 fragment 滚动到视图中并突出显示。

现在,您可能想知道我选择 startend 的原因。实际上,稍短一些的网址 https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers.(左右各两个字词)也可以。将 startend 与之前的值进行比较。

如果我更进一步,现在只为 startend 使用一个字词,您会发现我遇到了问题。网址 https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript,Workers. 现在变得更短了,但突出显示的文本片段不再是原来想要的。突出显示会在 Workers. 字词首次出现时停止,这没错,但不是我要突出显示的内容。问题在于,当前一个字词的 startend 值无法唯一标识所需的部分:

未预期的文本片段滚动到视野中并突出显示。

prefix--suffix

startend 使用足够长的值是获取唯一链接的一种解决方案。不过,在某些情况下,这并不可行。顺便提一下,为什么我选择了 Chrome 80 发布博文作为示例?答案是,此版本中引入了文本 fragment:

博文正文:文本网址片段。用户或作者现在可以使用网址中提供的文本片段链接到网页的特定部分。页面加载后,浏览器会突出显示文本并滚动 fragment 到视野中。例如,以下网址会加载“猫”的维基百科页面,并滚动到“text”参数中列出的内容。
“文本片段”公告博文摘要。

请注意,在上面的屏幕截图中,“text”一词出现了四次。第四个出现的位置使用绿色代码字体。如果我想链接到这个特定字词,则会将 start 设置为 text。由于“text”这个词只有一个字,因此不能有 end。接下来该怎么做?网址 https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=text 会在标题中首次出现“Text”一词时匹配:

在“Text”首次出现时匹配的文本 fragment。

幸运的是,这个问题有解决方法。在这种情况下,我可以指定 prefix​--suffix。绿色代码字体“text”前面的字词是“the”,后面的字词是“parameter”。另外三个出现“text”一词的位置,周围的字词都不相同。有了这些知识,我可以调整之前的网址,并添加 prefix--suffix。与其他参数一样,这些参数也需要进行百分比编码,并且可以包含多个字词。https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=the-,text,-parameter。 为了让解析器能够明确识别 prefix--suffix,需要使用短划线 - 将它们与 start 和可选的 end 分隔开来。

在“text”出现的所需位置匹配的文本 fragment。

完整语法

文本 fragment 的完整语法如下所示。(方括号表示可选参数。) 所有参数的值都需要采用百分比编码。这对于短划线 -、和号 & 和英文逗号 , 字符尤为重要,以免它们被解读为文本指令语法的一部分。

#:~:text=[prefix-,]start[,end][,-suffix]

prefix-startend-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>

使用浏览器扩展程序创建文本 fragment 网址

手动创建文本片段网址非常繁琐,尤其是在确保其唯一性方面。如果您真的想这样做,规范中提供了一些提示,并列出了生成文本片段网址的确切步骤。我们提供了一个名为链接到文本片段的开源浏览器扩展程序,您可以通过选择文本,然后点击上下文菜单中的“复制链接到所选文本”,将链接添加到任何文本。此扩展程序适用于以下浏览器:

为所选文本创建链接 Fragment 浏览器扩展程序。

一个网址中包含多个文本片段

请注意,一个网址中可以显示多个文本片段。特定文本片段需要用“&”字符 & 分隔。以下是一个包含三个文本片段的链接示例: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

一个网址中包含三个文本片段。

混合使用元素和文本 fragment

传统元素 fragment 可以与文本 fragment 组合使用。将这两者放在同一网址中完全没问题,例如,在网页上的原始文本发生变化导致文本片段不再匹配时,提供有意义的回退选项。指向产品论坛部分向我们提供反馈的网址 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.):

同时与元素 fragment 和文本 fragment 建立关联。

fragment 指令

我尚未介绍语法中的一项元素:fragment 指令 :~:。为了避免与现有网址元素片段(如上所示)存在兼容性问题,文本片段规范引入了片段指令。片段指令是网址片段的一部分,由代码序列 :~: 分隔。此属性专用于用户代理指令(例如 text=),并会在加载期间从网址中剥离,以便作者脚本无法直接与其互动。用户代理说明也称为指令。因此,在具体示例中,text= 被称为文本指令

功能检测

如需检测支持情况,请在 document 上测试只读 fragmentDirective 属性。片段指令是一种机制,可让网址指定指向浏览器(而非文档)的说明。这是为了避免与作者脚本直接交互,以便在添加未来的用户代理说明时,不必担心对现有内容引入破坏性更改。未来可能会添加的功能示例之一就是翻译提示。

if ('fragmentDirective' in document) {
  // Text Fragments is supported.
}

功能检测主要适用于动态生成链接(例如由搜索引擎生成)的情况,以避免向不支持文本片段链接的浏览器提供此类链接。

设置文本片段的样式

默认情况下,浏览器为文本片段设置样式的方式与为 mark 设置样式的方式相同(通常是黑色文本在黄色背景上,即 mark 的 CSS 系统颜色)。用户代理样式表包含如下所示的 CSS:

:root::target-text {
  color: MarkText;
  background: Mark;
}

如您所见,浏览器会公开一个伪选择器 ::target-text,您可以使用该选择器自定义应用的突出显示效果。例如,您可以将文本 fragment 设计为红色背景上的黑色文本。一如既往,请务必检查颜色对比度,确保替换样式不会导致无障碍问题,并确保突出显示的内容在视觉上确实与其他内容区分开来。

:root::target-text {
  color: black;
  background-color: red;
}

可多填充

文本 fragment 功能在某种程度上可以进行多填充。对于不提供文本片段内置支持的浏览器(这些功能是使用 JavaScript 实现的),我们提供了一个 polyfill,该 polyfill 由扩展程序在内部使用。

polyfill 包含一个文件 fragment-generation-utils.js,您可以将其导入并使用它来生成文本 fragment 链接。如以下代码示例所述:

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);
}

出于分析目的获取文本片段

许多网站都使用 fragment 进行路由,因此浏览器会剥离文本 fragment,以免破坏这些网页。我们已确认需要公开文本 fragment 与网页的链接(例如出于分析目的),但建议的解决方案尚未实现。目前,您可以使用以下代码提取所需信息,以作为一种权宜解决方法。

new URL(performance.getEntries().find(({ type }) => type === 'navigate').name).hash;

安全

只有在用户激活所产生的完整(非同一页面)导航时,才会调用文本 fragment 指令。此外,如果导航起源与目的地不同,则需要在 noopener 上下文中进行导航,以便知道目的网页已充分隔离。文本 fragment 指令仅应用于主帧。这意味着,系统不会在 iframe 中搜索文本,并且 iframe 导航不会调用文本 fragment。

隐私权

请务必确保文本 fragment 规范的实现不会泄露网页上是否找到了文本 fragment。虽然元素 fragment 完全由原始网页作者控制,但任何人都可以创建文本 fragment。还记得上面的示例中,由于 <h1> 没有 id,因此无法链接到 ECMAScript Modules in Web Workers 标题,但包括我在内的任何人都可以通过精心构建文本片段来链接到任何位置吗?

假设我运营了一个邪恶的广告网络 evil-ads.example.com。假设您在某个广告 iframe 中,在用户与广告互动后,动态创建了一个使用文本 fragment 网址 dating.example.com#:~:text=Log%20Out 的隐藏跨源 iframe dating.example.com。如果找到“退出”文本,我就会知道受害者目前已登录 dating.example.com,我可以使用该信息来分析用户。由于简单的 TextFragments 实现可能会决定成功匹配应导致焦点切换,因此在 evil-ads.example.com 上,我可以监听 blur 事件,从而知道何时发生匹配。在 Chrome 中,我们实现文本 fragment 的方式可以防止上述情况发生。

另一种攻击可能基于滚动位置来利用网络流量。假设我有权访问受害者的网络流量日志,例如作为公司内网的管理员。现在假设有一篇长篇人力资源文档,名为“如果您患有…该怎么办”,其中列出了倦怠焦虑等一系列情况。我可以在列表中的每个项目旁边放置一个跟踪像素。然后,如果我确定文档的加载时间与“倦怠”项旁边的跟踪像素的加载时间一致,那么作为 Intranet 管理员,我就可以确定某位员工点击了包含 :~:text=burn%20out 的文本片段链接,而该员工可能认为该链接包含机密信息,不对任何人显示。由于此示例从一开始就有些牵强附会,并且利用此漏洞需要满足非常具体的前提条件,因此 Chrome 安全团队评估后认为,实现导航滚动功能的风险可控。其他用户代理可能会决定改为显示手动滚动界面元素。

对于希望停用此功能的网站,Chromium 支持文档政策标头值,网站可以发送此值,以便用户代理不会处理文本片段网址。

Document-Policy: force-load-at-top

停用文本 fragment

如需停用此功能,最简单的方法是使用可注入 HTTP 响应标头的扩展程序(例如 ModHeader,这不是 Google 产品),以插入响应(而非请求)标头,如下所示:

Document-Policy: force-load-at-top

另一种更复杂的停用方式是使用企业设置 ScrollToTextFragmentEnabled。如需在 macOS 上执行此操作,请将以下命令粘贴到终端中。

defaults write com.google.Chrome ScrollToTextFragmentEnabled -bool false

在 Windows 上,请按照 Google Chrome 企业版帮助支持网站上的文档操作。

对于某些搜索,Google 搜索引擎会选取相关网站的内容片段来提供快速解答或摘要。当您以提问的形式进行搜索时,系统最有可能显示这些精选摘要。点击精选摘要会直接将用户转至源网页上的精选摘要文本。这得益于自动制作的文字片段网址。

显示精选摘要的 Google 搜索引擎结果页。状态栏会显示文本 fragment 网址。
点击后,页面的相关部分会滚动到视野中。

总结

文本片段网址是一项强大的功能,可用于链接到网页上的任意文本。学术界可以使用它提供高度准确的引用或参考链接。搜索引擎可以使用它深层链接到网页上的文字搜索结果。社交网站可以使用它来让用户分享网页的特定段落,而不是无法访问的屏幕截图。希望您开始使用文本 fragment 网址,并像我一样发现它们非常实用。请务必安装链接到文本 fragment 浏览器扩展程序。

致谢

文本 fragment 由 Nick BurrisDavid Bokan 实现和指定,并由 Grant Wang 提供贡献。感谢 Joe Medley 对本文进行全面审核。主打图片:Unsplash 用户 Greg Rakozy 提供。