块级链接:寻找完美的解决方案

Avatar of Vikas Parashar
Vikas Parashar

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 价值 200 美元的免费积分!

我正在阅读 Chris 撰写的 这篇文章,他在文章中谈论了块级链接(您知道,比如将整个卡片元素包裹在锚点中)是一个糟糕的想法。这对于无障碍来说是糟糕的,因为它会影响屏幕阅读器。而且,它对于用户体验来说也是糟糕的,因为它阻止了简单的用户任务,例如选择文本。

但是,也许还有其他因素在起作用。也许问题不在于模式本身,而在于其实现方式。这让我相信,现在是时候写一篇后续文章,看看我们是否可以解决 Chris 指出的某些问题。

在本文中,我将使用“卡片”一词来描述使用块级链接模式的组件。以下是我们的意思。

让我们看看我们希望卡片组件如何工作

  1. 整个组件应该是可链接且可点击的。
  2. 它应该能够包含多个链接。
  3. 内容应该是语义化的,以便辅助技术能够理解它。
  4. 文本应该是可选择的,就像普通链接一样。
  5. 诸如右键单击和键盘快捷键之类的东西应该可以使用它
  6. 它的元素在 Tab 键按下时应该可聚焦。

这是一个很长的清单!而且由于浏览器没有提供任何标准的卡片小部件,所以我们也没有任何标准的指南来构建它。

就像网络上的大多数事物一样,制作卡片组件的方法不止一种。但是,我还没有找到满足我们刚刚介绍的所有要求的东西。在本文中,我们将尝试满足所有这些要求。这正是我们现在要做的!

方法 1:将所有内容包裹在一个 <a>

这是制作链接卡片最常见且最简单的方法。获取卡片的 HTML 代码,并将整个内容包裹在锚点标签中。

<a href="/">
  <!-- Card markup -->
</a>

以下是结果

  1. 它是可点击的。
  2. 它支持右键单击和键盘快捷键。

嗯,不太好。我们仍然无法

  1. 将另一个链接放入卡片中,因为整个卡片是一个单独的链接
  2. 与屏幕阅读器一起使用 - 内容不是语义化的,因此辅助技术将从时间戳开始宣布卡片中的所有内容
  3. 选择文本

这足以让我们 👎 可能不应该使用它。让我们继续学习下一种技术。

这是一个不错的折衷方案,它牺牲了一点用户体验来提高无障碍性。

使用这种模式,我们实现了大部分目标

  1. 我们可以随意添加链接。
  2. 内容是语义化的。
  3. 我们可以从卡片中选择文本。
  4. 右键单击和键盘快捷键可以正常使用。
  5. 在 Tab 键按下时,焦点顺序是正确的。

但是它缺少我们想要在卡片中实现的主要功能:整个卡片应该是可点击的!看起来我们需要尝试其他方法。

方法 3:传统的 ::before 伪元素

在这种方法中,我们添加一个 ::before 或 ::after 元素,将其放置在卡片上方并使用绝对定位,将其拉伸到卡片的整个宽度和高度,使其可点击。

但是现在

  1. 我们仍然无法添加多个链接,因为任何其他链接都位于伪元素层之下。我们可以尝试将所有文本放在伪元素之上,但卡片链接本身在点击文本时将无法使用。
  2. 我们仍然无法选择文本。同样,我们可以交换层级,但这样我们又回到了可点击链接问题。

让我们尝试在我们最终的技术中真正勾选所有框。

方法 4:在第二种方法上撒点 JavaScript

让我们从第二种方法开始。回想一下,我们是在哪里链接所有我们想要链接的内容

<article class="card">
  <time datetime="2020-03-20">Mar 20, 2020</time>
  <h2><a href="https://css-tricks.cn/a-complete-guide-to-calc-in-css/" class="main-link">A Complete Guide to calc() in CSS</a></h2>
  <p>
    In this guide, let’s cover just about everything there is to know about this very useful function.
  </p>
  <a class="author-name" href="https://css-tricks.cn/author/chriscoyier/" target="_blank">Chris Coyier</a>
    <div class="tags">
      <a class="tag" href="https://css-tricks.cn/tag/calc/" >calc</a>
    </div>
</article>

那么我们如何让整个卡片可点击呢?我们可以使用 JavaScript 作为渐进增强来实现。我们将首先向卡片添加一个 click 事件监听器,并在触发它时触发主链接的点击事件。

const card = document.querySelector(".card")
const mainLink = document.querySelector('.main-link')


card.addEventListener("click", handleClick)


function handleClick(event) {
  mainLink.click();
}

暂时来说,这引入了我们无法选择文本的问题,而我们一直试图解决这个问题。以下技巧:我们将使用相对不为人知的 Web API window.getSelection。来自 MDN

Window.getSelection() 方法返回一个 Selection 对象,该对象表示用户选择的文本范围或光标的当前位置。

尽管此方法返回一个对象,但我们可以使用 toString() 将其转换为字符串。

const isTextSelected = window.getSelection().toString()

只需一行代码,无需使用事件监听器的复杂技巧,我们就知道用户是否选择了文本。让我们在 handleClick 函数中使用它。

const card = document.querySelector(".card")
const mainLink = document.querySelector('.main-link')


card.addEventListener("click", handleClick)


function handleClick(event) {
  const isTextSelected = window.getSelection().toString();
  if (!isTextSelected) {
    mainLink.click();
  }
}

这样,在没有选择文本的情况下,主链接可以被点击,而这只需要几行 JavaScript 代码。这满足了我们的要求

  1. 整个组件是可链接且可点击的。
  2. 它能够包含多个链接。
  3. 此内容是语义化的,以便辅助技术能够理解它。
  4. 文本应该是可选择的,就像普通链接一样。
  5. 诸如右键单击和键盘快捷键之类的东西应该可以使用它
  6. 它的元素在 Tab 键按下时应该可聚焦。

我们满足了所有要求,但仍然存在一些问题,例如卡片中的链接和按钮等可点击元素上的双重事件触发。我们可以通过在所有这些元素上添加一个点击事件监听器并停止事件传播来解决此问题。

// You might want to add common class like 'clickable' on all elements and use that for the query selector.
const clickableElements = Array.from(card.querySelectorAll("a"));
clickableElements.forEach((ele) =>
  ele.addEventListener("click", (e) => e.stopPropagation())
);

以下是包含我们添加的所有 JavaScript 代码的最终演示

我认为我们成功了!现在您知道如何制作完美的可点击卡片组件了。

其他模式呢?例如,如果卡片包含博客文章的摘要,后面跟着一个“阅读更多”链接?该链接应该放在哪里?它会成为“主”链接吗?图片呢?

对于这些问题以及更多问题,以下是一些关于该主题的进一步阅读材料