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

借助文本片段,您可以在网址片段中指定文本摘要。 当导航到包含此类文本片段的网址时,浏览器可能会突出显示并/或吸引用户的注意。

Chrome 80 是一个重大版本。其中包含许多备受期待的功能,例如 Web Worker 中的 ECMAScript 模块nullish 合并可选链接等。此次发布版本一如既往,是在 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 来实现这一点。正如您在开发者工具的“元素”面板中看到的,相关元素有一个值为 HTML1id 属性。

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

如果使用 JavaScript 的 URL() 构造函数解析此网址,就会看到不同的组成部分。请注意 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 的内容,该怎么办?假设我想链接到 Web Worker 中的 ECMAScript 模块标题。如您在下面的屏幕截图中所看到,相关 <h1> 没有 id 属性,这意味着我无法链接到此标题。这就是 Text Fragment 解决的问题。

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

文本 fragment

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

浏览器兼容性

浏览器支持

  • Chrome:89。
  • Edge:89。
  • Firefox:131。
  • Safari Technology Preview:受支持。

来源

出于安全考虑,此功能要求在 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 等支持的浏览器中点击链接,系统会突出显示相应文本片段并滚动到该片段:

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

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)中点击后,整个部分会突出显示并滚动到视野中:

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

现在,您可能想知道我选择 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 到视野中。例如,以下网址加载了“Cat”的 Wiki 页面,并滚动到了 `text` 参数中列出的内容。
“文本片段”公告博文摘要。

请注意,在上面的屏幕截图中,“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”出现的所需位置匹配文本 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>

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

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

指向文本片段的链接 浏览器扩展程序。

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

请注意,一个网址中可以显示多个文本片段。特定文本片段需要用和号字符 & 进行分隔。以下是一个包含三个文本片段的链接示例: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 指令 :~:。为避免与现有网址元素片段出现如上所示的兼容性问题,文本片段规范引入了 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 功能在某种程度上可以进行多填充。对于没有为文本 Fragment(该功能在 JavaScript 中实现)提供内置支持的浏览器,我们提供了扩展程序在内部使用的 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。还记得上面的示例中,由于 <h1> 没有 id,因此无法链接到 ECMAScript Modules in Web Workers 标题,但包括我在内的任何人都可以通过精心构建文本片段来链接到任何位置吗?

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

另一种攻击可能基于滚动位置来利用网络流量。假设我有权访问受害者的网络流量日志,例如作为公司内网的管理员。现在假设有一篇长篇人力资源文档,名为“如果您患有…该怎么办”,其中列出了倦怠焦虑等一系列情况。我可以在列表中的每个项目旁边放置一个跟踪像素。然后,如果我确定文档的加载时间与“倦怠”项旁边的跟踪像素的加载时间一致,那么作为 Intranet 管理员,我就可以确定某位员工点击了包含 :~: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 搜索引擎结果页。状态栏会显示文本 fragment 网址。
点击后,页面的相关部分会滚动到视野中。

总结

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

致谢

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