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

通过文本片段,您可以在网址片段中指定文本片段。 当用户导航到包含此类文本片段的网址时,浏览器可以突出显示该文本片段,以引起用户的注意。

Fragment 标识符

Chrome 80 是一个重要版本。它包含许多备受期待的功能,例如 Web Worker 中的 ECMAScript 模块、空值合并、可选链等。与往常一样,此次发布是通过 Chromium 博客上的博文宣布的。您可以在以下屏幕截图中看到博文的摘录。

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

您可能想知道所有这些红色框代表什么。它们是在开发者工具中运行以下代码段的结果。它会突出显示具有 id 属性的所有元素。

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

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

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

如果我使用 JavaScript 的 URL() 构造函数解析此网址,则会显示不同的组成部分。 请注意值为 #HTML1 的 hash 属性。

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 属性,这意味着我无法链接到此标题。这就是文本片段要解决的问题。

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

文本片段

文本片段提案增加了对在网址哈希中指定文本片段的支持。当用户导航到包含此类文本片段的网址时,用户代理可以突出显示该文本片段,以引起用户的注意。

浏览器兼容性

Browser Support

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

Source

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

start

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

#:~:text=start

例如,假设我想链接到宣布 Chrome 80 功能的博文中的 Web Worker 中的 ECMAScript 模块标题,则网址如下:

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

文本片段会像这样突出显示。 如果您在 Chrome 等支持此功能的浏览器中点击该链接,文本片段会突出显示并滚动到视图中:

滚动到视图中并突出显示的文本片段。

start和end

现在,如果我想链接到标题为 Web Worker 中的 ECMAScript 模块的整个部分,而不仅仅是其标题,该怎么办?对该部分的整个文本进行百分比编码会导致生成的网址过长,无法使用。

幸运的是,还有更好的方法。我可以使用 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。实际上,稍微短一点的网址 https://blog.chromium.org/2019/12/chrome-80-content-indexing-es-modules.html#:~:text=ECMAScript%20Modules,Web%20Workers.(每侧只有两个字词)也可以。将 start 和 end 与之前的值进行比较。

如果我更进一步,现在只用一个词来表示 start 和 end,您可以看到我遇到了麻烦。网址 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 版本博文作为示例?答案是,此版本引入了文本片段:

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

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

文本片段匹配“Text”的首次出现。

好在,现在有了解决方案。在这种情况下,我可以指定 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=[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>

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

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

Link to Text 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。

一个网址中的三个文本片段。

混合元素和文本片段

传统元素片段可以与文本片段结合使用。在同一网址中同时包含这两种链接是完全可以的,例如,在网页上的原始文本发生更改导致文本片段不再匹配时,提供有意义的后备链接。链接到在我们的产品论坛中向我们提供反馈部分的网址 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 指令

语法中还有一个元素我尚未解释:片段指令 :~:。为避免与现有网址元素片段(如上所示)出现兼容性问题,文本片段规范引入了片段指令。片段指令是网址片段的一部分,由代码序列 :~: 分隔。它专供用户代理指令(例如 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,您可以使用它来自定义应用的高亮显示。例如,您可以将文本片段设计为红底黑字。一如既往,请务必检查色彩对比度,确保替换样式不会导致无障碍问题,并确保突出显示的内容在视觉上确实与其余内容区分开来。

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

Polyfill

文本片段功能在一定程度上可以进行 polyfill。我们提供了一个 polyfill,供扩展程序在内部使用,适用于不提供对文本片段的内置支持的浏览器,其中功能是用 JavaScript 实现的。

polyfill 包含一个 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);
}

获取文本片段,以便执行数据分析

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

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

安全

文本片段指令仅在由用户激活导致的完整(非同页)导航中调用。此外,如果导航的来源与目标不同,则导航必须在 noopener 上下文中进行,以便目标页面能够充分隔离。文本片段指令仅应用于主框架。这意味着,系统不会在 iframe 内搜索文本,并且 iframe 导航不会调用文本片段。

隐私权

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

假设我运营着一个恶意广告联盟 evil-ads.example.com。再假设,在我的一个广告 iframe 中,我动态创建了一个隐藏的跨域 iframe,以便在用户与广告互动后,使用文本片段网址 dating.example.com#:~:text=Log%20Out 访问 dating.example.com。如果找到“退出”文本,我就知道受害者已登录 dating.example.com,我可以利用这一点来分析用户画像。由于简单的文本片段实现可能会决定成功匹配应导致焦点切换,因此在 evil-ads.example.com 上,我可以监听 blur 事件,从而知道何时发生匹配。在 Chrome 中,我们以一种不会发生此类情况的方式实现了文本片段。

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

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

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 企业版帮助支持网站上的文档进行操作。

对于某些搜索,Google 搜索引擎会提供一个简短回答或摘要,其中包含相关网站的内容摘要。当搜索内容采用问题形式时,最有可能显示这些精选摘要。点击精选摘要后,用户会直接转到来源网页上的精选摘要文本。这得益于自动创建的文本片段网址。

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

总结

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

致谢

文本片段由 Nick Burris 和 David Bokan 实现和指定,Grant Wang 也做出了贡献。感谢 Joe Medley 对本文档的全面审核。