它是按钮还是链接?

发布于 大漠

链接按钮在Web中可以说是最常见不过的东东了。但很少人会去问自己在构建Web的时候,链接和按钮是否用对了,或者说什么时候用链接或按钮?事实上呢?在现代的Web中,特别是在移动端,构建链接和按钮不在局限于<a><button>标签上了,可以说是HTML中的其他元素,比如大家喜欢用的<div><span>,甚至还有不少用<p>以及其他标签。作为一位专业的Web开发者来说,这样做真的无所谓呢?还是可以改进呢?特别是想让Web更具语义化,更具可访问性的时候,这小小的差异就很值得我们去花时间探讨。

一直以来,我也从不纠结按钮和链接要怎么使用,但当我开始去深纠Web可访问性,即A11Y相关的知识时,我才发现原来这里面的每一个细节都值得自己去探索和学习。

链接和按钮

我觉得上图非常的有意思。好比两位小朋友在对话

“这是按钮吗?”

“不”

“如果不是,那它就是一链接”

“是的”

“它就是一链接”

往往很多开发者有一个习惯,就是喜欢根据视觉稿来定,它是一个链接或是一个按钮。只要长得和我们认识中的链接风格有差异的,都会被定认是按钮,反之和链接风格长得一样的都是链接,比如下图:

上图中的”Read more...“虽然从UI上看上去像按钮,但事实上,用户点击它会把用户带到另一个页面;而右上角的”Close“是一个带蓝色下划线的网格,看上去更像是一个链接的样式,但用户点击它之后并不会把用户带到新的地方,而是会关闭弹出的对话框?那么问题就来了?长得像按钮的并不是按钮,长得像链接的并不是链接。换句话说,在Web的使用中,我们并不能仅仅的根据设计的样式风格来决定某个元素是按钮还是链接。那么开发者应该如何来决策两者的选择?

从标签元素来看,<a><button>标签和其他的标签元素并没有太多的不同之处,但事实上,HTML中的<a><button>还是有很多东西需要我们花时间去了解,比如这两个标签的实现,相关的属性,最佳的样式,需要避免的事项等。接下来,让我们一起来看看链接和按钮的整个世界,以及与之相关的HTML、CSS、JavaScript、设计和可访问性等方面要考虑的事项。在这个过程中有很多陷阱和不好的做法需要避免。通过这篇文章的学习,我们可以了解到这两个元素的完整的,良好的UX实现。

接下来的内容主要按以下几个部分来展开:

  • HTML中的使用
  • CSS样式以及要注意的事项
  • JavaScript要考虑的事项
  • 可访问性的差异

感兴趣的同学,请继续往下阅读。

HTML中的使用

HTML的链接<a><button>都是最基本的元素之一:

其中<a>是行内文本元素,也被称为是锚元素,可以创建通向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他 URL的超链接。当用户单击链接,会把用户带到另一个页面或移动到同一页面中的另一位置。而<button>元素是一个表单元素,表示一个可点击的按钮,可以用在表单或文档其它需要使用简单标准按钮的地方。

在HTML中的使用也非常简单。

链接在HTML中的使用

链接在HTML中的使用主要有两种,其中一种是跳转到一个新的页面,比如:

<a href="https://www.fedev.cn">首页</a>

<a>标签中使用了href属性,用来指定超链接指向的URL,而且href的值是一个“绝对”(或称为完全限定URL的链接。与绝对URL相对应的还有一个称为相对URL,比如:

<a href="./index.html">首页</a>

这样可能很有用,例如,在开发中,域名可能与生产站点不同,但是仍然希望能够单击链接。特别是在同一域名下,比如导航菜单之类的,都喜欢使用相对链接,而对于引用的外部链接,都一般采用绝对链接。

href除了可以指定URL(绝对或相对链接地址)之外还可以是**URL片段**,即是哈希标记,一般以#符号开头并带有一定的名称。哈希标记指定当前文档中的内部目标位置(通常是HTML元素的ID名称)。对于该现象的使用,也常称为锚点跳转,比如下面这样的一个常见效果,点击页在右侧的列表项,可以跳转到相应标题对应的内容:

查看代码,你会发现它们的使用:

对应的HTML大致类似下面这样:

<a href="#section-2">Section Two</a>
<!-- 点击上面的链接,将会跳到下面这个区域 -->
<section id="section-2"></section>

另外这种交互行为在站点上经常能看到类似的,比如在很多站点的右下角都会有一个“返回顶部”的交互操作,其实该效果就是使用<a>来做跳转的:

<a href="#top-of-page">返回顶部</a>

跳转链接有时候也用于链接到其他没有href属性的锚(<a>)元素。这些被称为“占位符”链接:

<a id="section-2"></a>
<h3>Section 2</h3>

这种骚操作一般都是考虑Web可访问性才会使用(提高Web可访问性),虽然有让HTML标签变得冗余的嫌疑,但为了给用户更好的体验,还是可以接受的。而且在<a>元素不带href属性时,是禁用链接的唯一实用方法

HTML规范这样描述

The href attribute on a and area elements is not required; when those elements do not have href attributes they do not create hyperlinks.

大致意思是“<a><area>元素上没有href属性时,它们不会创建超链接”。

MDN针对这方面有一个更易于理解的定义

This attribute may be omitted (as of HTML5) to create a placeholder link. A placeholder link resembles a traditional hyperlink, but does not lead anywhere.

在HTML中,<a>元素可以省略href属性,以创建占位符链接。占位符链接类似于传统的超链接,但不指向任何地方。

那么为什么要禁用链接呢?比如这样的一个场景,在表单中有一个链接,而该链接只有在用户登录或注册后才会被激活。

有一点需要特别注意,当<a>元素没有href属性时,它就失去了标签原有的角色,也不会有焦点,也没有键盘事件。在这个时候,它和一个<span>标签没有太多的差异。比如下面这个示例:

示例中的第二个<a>标签,没有显式设置href属性,原本<a>默认的样式也丢失了,焦点或者链接事件也丢失了。你可以尝试着按Tab键,只会在第一个<a>上得到焦点,在第二个<a>上是得不到的:

前成提到过,当用户点击一个链接的时候,可以将用户带到一个新的页面。那么这个新的页面可以是在当前页面打开(新的覆盖旧的),也可以在新的窗口或选项卡中打开。如果你希望在一个新的窗口或选项卡中打开链接,则需要在链接添加target属性。该属性指定在何处显示链接的资源。 取值为标签(tab),窗口(window),或框架(iframe)等浏览上下文的名称或其他关键词。以下关键字具有特殊的意义:

  • _self: 当前页面加载,即当前的响应到同一HTML 4 frame(或HTML5浏览上下文)。此值是默认的,如果没有指定属性的话
  • _blank: 新窗口打开,即到一个新的未命名的HTML4窗口或HTML5浏览器上下文
  • _parent: 加载响应到当前框架的HTML4父框架或当前的HTML5浏览上下文的父浏览上下文。如果没有parent框架或者浏览上下文,此选项的行为方式与 _self 相同
  • _top: IHTML4中:加载的响应成完整的,原来的窗口,取消所有其它frame。 HTML5中:加载响应进入顶层浏览上下文(即,浏览上下文,它是当前的一个的祖先,并且没有parent)。如果没有parent框架或者浏览上下文,此选项的行为方式相同_self

链接跳转到新窗口或选项卡,只需显式设置target的值为_blank即可,但一般不建议这么操作

<a href="https://www.fedev.cn" target="_blank" rel="noopener noreferrer">W3cplus.com</a>

在使用target="_blank"时,别忘了rel属性和值,它们使其更安全、更快

注意,在使用target时,考虑rel="noopener noreferrer"以防止针对window.opener API的恶意行为。另外使用target="_blank"链接到另一个页面将在与页面相同的进程中运行新页面。如果新页面执行代价高昂的JS时,那么页面的性能可能会受到一定的影响。使用rel="noopener"可以避免这种情况

链接是否在新的标签中打开链接一直都是UX方面讨论的话题,@Chris Coyier的《When to use target=”_blank”》一文有做过这方面的讨论:

不要使用target="_blank" 使用 target="_blank"
因为你或你的用户更喜欢它 因为用户正在当前页面上做一些事情,比如正在播放媒体或有未保存的工作
因为你想要增加你在现场的时间 你有一些模糊的技术原因迫使你这样做(即使这样,仍然可能是规则而不是例外)
因为你要区分内部和外部链接或内容类型  
因为这是你处理无限滚动的方法  

<a>元素的href除了指定跳转的链接或锚点跳转之外,还有可能会链接到一些文件,用户点击之后可以下载。在HTML5中,<a>标签元素还有一个download属性,该属性将指示浏览器下载链接的文件,而不是在当前页面或选项卡中打开它。这对于用户体验来说是一个较好的改善。具体的一点说,在<a>上显式的设置了download属性可以指示浏览器下载URL而不是导航到它,因此将提示用户将其保存为本地文件。如果属性有一个值,那么此值将在下载保存过程中作为预填充的文件名(如果用户需要,仍然可以更改文件名)。此属性对允许的值没有限制,但是/\会被转换为下划线。大多数文件系统限制了文件名中的标点符号,故此,浏览器将相应地调整建议的文件名。

我们来看一个小示例:

注意,在<a>链接中使用download属性时有一些条件限制:

  • 此属性仅适用于同源 URL
  • 尽管 HTTP URL 需要位于同一源中,但是可以使用 [blob: URL]//developer.mozilla.org/zh-CN/docs/Web/API/URL.createObjectURL) 和 data: URL ,以方便用户下载使用 JavaScript 生成的内容(例如使用在线绘图 Web 应用程序创建的照片)。
  • 如果 HTTP 头中的 Content-Disposition 属性赋予了一个不同于此属性的文件名,HTTP 头属性优先于此属性。
  • 如果 HTTP 头属性 Content-Disposition 被设置为inline(即 Content-Disposition='inline'),那么 Firefox 优先考虑 HTTPContent-Dispositiondownload 属性。

有关于这方面的介绍还可以阅读:

<a>链接元素中还有一个特别有意思的属性,那就是rel属性,前面在介绍target属性的时候,建议加上rel="noopener noreferrer",比如:

<a href="https://www.fedev.cn" target="_blank" rel="noopener noreferrer">W3cplus.com</a>

这个rel属性可以用来描述链接到目标之间的关系。

说实在的,以前我一直以为rel<link>元素的私有属性(它不用于创建超级链接,而是用于诸如CSS和预加载等内容,比如rel=“stylesheet”),没想到在<a>链接元素中也可以使用rel属性。比如:

<a href="/page/3" rel="next">下一页</a>
<span>2</span>
<a href="/page/1" rel="prev">上一页</a>

<a href="/licenses.html" rel="license">版权归W3cplus所有</a>

<a href="/topics/" rel="directory">所有话题</a>

在HTML中,rel有些可以用于<a>(或<area>),而且有些是用于<link>上:

rel属性值 支持的元素 描述 备注
alternate <a><area><link> 文档的替代版本  
author <a><area><link> 定义一个超链接到一个描述作者信息的页面或者提供一个方法联系作者  
help <a><area><link> 链接到一个关于父亲标签和它的后代的进一步帮助资源  
license <a><area><link> 表示超链接指向描述许可信息的文档 如果不在<head>元素内,则该标准不会区分应用于文档特定部分或整个文档的超链接。 仅页面上的数据可以表明这一点
manifest <link> Web应用程序清单文档 不能用于<a><area>
next <a><area><link> 表明该超链接指向的是当前页面所在序列中的下一个资源  
prev <a><area><link> 指示超链接指向当前页面所在序列的优先资源  
search <a><area><link> 表示超链接引用了一个文档,该文档的接口专门设计用于在此文档或站点及其资源中进行搜索  

在MDN有一个关于rel类型的列表清单,更多的类型还可以点击这里查看

rel有些值是用于通知搜索引擎的:

rel属性值 描述 备注
sponsored 标记链接是广告或付费广告作为赞助商 通常也称为付费链接
ugc 对于不太可信的用户生成内容 比如评论或论坛的帖子
nofollow 告诉搜索引擎忽略这一点,不要把这个网站与它链接到的地方联系起来 nofollow主要是被一些使用人气排名技术的搜索引擎所使用,不能用于<link>

rel使用上表的值,表示链接通常不会被跟随。请记住,链接的页面可以通过其他方式找到,例如站点地图或来自其他站点的链接,因此它们仍然可以被搜索引擎抓取。这几个rel的值仅在<a>标记中使用(因为Google只能跟随<a>标记指ttmk的链接),除了nofollow,它也可以用于<meta>标签为rotots的时候。

rel有些属性值还可以用于安全性方面:

rel属性值 描述 备注
noopener 指示浏览器打开链接而不授予新的浏览上下文对打开它的文档的访问权限 -- 通过在打开的窗口中不设置Window.opener属性(返回null);当打开不受信任的链接时,这特别有用,以确保它们无法通过Window.opener属性来篡改原始文档(有关更多详细信息,请参阅 About rel=noopener),同时仍提供 Referer HTTP标头(除非也使用noreferrer 使用noopener时,在决定是否打开新窗口/选项卡方面,除_top_self_parent 以外的非空目标名称都被视为_blank
noreferrer 防止其他网站或跟踪服务(如谷歌分析)识别您的页面点击链接的来源  

注意,如果有需要,可以在rel中同时使用noopenernoreferrer两个值,只需要用空格将它们隔离开来即可:rel="noopener noreferrer"

最后,rel还能提供一些“微格式”的标记。

微格式(Microformat)是用来显示特定数据类型的特殊HTML代码,比如地址。

rel属性值 描述 备注
directory 指示超级链接的目标是包含当前页面项的目录列表  
tag 表示该超链接的目的地是当前页面的作者指定的“标记”(或关键字/主题) 不应在标签云的链接成员上设置此链接类型,因为这些链接对象不适用于单个文档,而不适用于一组页面
payment 指示该超链接的目标提供了显示或提供对当前页面的支持的方式  
help 声明链接到的资源是当前文档的帮助文件或FAQ  

链接<a>标签在显式设置了href属性的时候,那么其角色(role)就是link

角色role是ARIA(Accessible Rich Internet Applications)中的概念,为了让Web更具可访问性(主要用于无障碍方面),ARIA在HTML中的使用非常的频繁。如果你对ARIA方面感兴趣的话,可以阅读A11Y系列中的《WAI-ARIA初探》一文。

虽然ARIA有助于Web的可访问性(比如屏幕阅读器),但ARIA的乱用反而会降低可访问性,给用户造成不必要的障碍。比如说,我们的<a>链接显式设置href的时候,其role就是link了,如果你再像下面这样使用,就有显画蛇添足:

<a role="link" href="/">Link</a>

事实上呢?role="link"一般都是用于非<a>(或<area>)标签来模拟链接的行为(在移动端上,现在很多同学都借助于JavaScript的行为,用其他的HTML标签来模拟一个链接<a>),对于这样的场景,我们就可以在标签上添加role="link"

<span 
    tabindex="0"
    role="link"
    onclick="goToLink(event, 'https://www.fedev.cn')"
    onkeydown="goToLink(event, 'https://www.fedev.cn')">W3C website</span>

<img 
    tabindex="0"
    role="link"
    onclick="goToLink(event, 'https://www.fedev.cn')"
    onkeydown="goToLink(event, 'https://www.fedev.cn')"
    src="images/w3cplus-logo.png"
    alt="W3cplus Website" />

 <span 
    tabindex="0"
    role="link"
    class="link3"
    onclick="goToLink(event, 'https://www.fedev.cn')"
    onkeydown="goToLink(event, 'https://www.fedev.cn')"
    aria-label="W3cplus website"></span>

从上面的代码中不难发现,使用非<a>链接标签来做模拟链接跳转,会有很多额外的工作量,比如链接角色、链接跳转以及一些样式等。也正因为这样的原因,社区中很多同学都在呼吁或提倡在Web的开发中,尽可能的使用原生HTML标签:

我曾在《编写HTML时要考虑可访问性》一文中专门和大家聊过这样的话题。

链接<a>标签中还有一个很有意思的属性,那就是title,在<a>链接上显式设置了title属性的话,那么鼠标悬浮在该链接时,title对应的值就会显式出来,比如:

<a href="/" title="记述前端那些事,引领Web前沿,打造精品教程" id="site_name">w3cplus</a>

效果如下:

虽然用户鼠标悬浮会触发链接title对应的值弹出来,可以给到用户一定的提示作用(比如告诉用户这个链接可能会去到哪或其他信息?),但弹出来的Tips样式开发者无法自己定义。另外还有一点,它无法在任何触控设备上使用。如果一个链接需要更的上下文信息,可以在链接的实际内容中提供这些信息,或者使用描述性文本提供链接本身。

上面我们看到的都是文本链接,在Web中除了文本链接之外,还可以是图标或图像。比如可以将图标(比如SVG图标)嵌套在<a>链接里:

<a href="/">
    <svg class="heart" width="24" height="24" viewBox="0 0 24 24">
        <path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"></path>
    </svg>
</a>

链接中就仅仅一个图标的话,对于链接上下文信息是不够的,特别是对于可访问性而言,就可访问性而言,文本链接总是更清晰的。如果你的需求是不能用文本链接,还可以使用一些ARIA来辅助,比如使用aria-labelaria-labelledbyaria-describedby,就像下面这样:

<a href="/" aria-label="赞">
    <svg role="img" focusable="false" aria-hidden="true">...</svg>
</a>

<a href="/" aria-labelledby="foo">
    <svg role="img" focusable="false" aria-hidden="true">...</svg>
</a>

<a href="/" aria-describedby="foo">
    <svg role="img" focusable="false" aria-hidden="true">...</svg>
</a>

<p id="foo">赞</p>

除了借助ARIA来增加图标链接的上下文信息之外,还可以在链接中同时嵌套图标和描述链接上下文信息的文本,然后将文本隐藏达到相同的效果:

<a href="/">
    <svg role="img" focusable="false" aria-hidden="true">...</svg>
    <span class="sr-only">赞</span>
</a>

.sr-only { 
    position: absolute; 
    height: 1px; 
    width: 1px; 
    clip: rect(1px 1px 1px 1px); 
    clip: rect(1px,1px,1px,1px); 
    clip-path: polygon(0px 0px, 0px 0px, 0px 0px); 
    -webkit-clip-path: polygon(0px 0px, 0px 0px, 0px 0px); 
    overflow: hidden !important; 
}

效果如下:

链接中除了可以嵌套图标之外,还可以嵌套图像(<img>),让整个图像都变成链接,比如:

<a href="https://developer.mozilla.org/en-US/">
    <img src="https://mdn.mozillademos.org/files/6851/mdn_logo.png" 
    alt="MDN logo" />
</a>

<a>中嵌套<img>的时候,我们没有必要在alt中描述图像是一个链接,因为辅助技术知道它是一个链接,但为了让链接上下文信息更清晰,alt可以尽可能地把图像描述清楚。比如上面示例中,在<img>中的alt描述图像是MDN的Logo。

为了让链接具有更大的可点击区域,比如让整个卡片都可以被点击(点击卡片的任何区域都可以跳转),特别是在移动端,这样的场景更是如此:

很多开发者可能会用一个链接将卡片中所需的元素都放置在<a>内:

<a href="/">
    <img src="/path/to/image.png" alt="">
    <h2>title</h2>
    <p>content</p>
</a>

其实这样做是最糟糕的一种做法。首先在样式上需要在<a>链接不同状态做相应的处理,比如链接的下划线,其次这样做对于屏幕阅读器用户也很糟糕,比如整个卡片的内容在被宣布为链接之前就被读取了。另外,很多开发人员有一种误解,认为屏幕阅读器用户在页面上按Tab键浏览所有内容,因此他们常常忘记测试屏幕阅读器扫描页面的主要方式 —— PC上通过箭头来操作,触摸屏幕则是通过滑动来操作。

如果仅是从交互行为来考虑,即整个卡片区域都可以点击,我们还可以有其他的方式来完成,比如在《伪元素能帮助我们做些什么》一文中提到的,可以使用伪元素::before::after将链接扩大到整个卡片区域。

代码也很简单:

.card a[href]::after {
    content: "";
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

有关于这方面的示例可以点击这里查看。如果你想更深入的了解关于更具包容性的卡片组件制作的话,建议你花点时间阅读下面两篇文章:

在HTML中使用<a>标签除了可以创建文本、图标和图像链接之外,还可以创建一个email链接、电话链接,比如:

<a href="mailto:airenliao@gmail.com">可以发邮件给我哟!</a>
<a href="tel:+491570156">+49 157 0156</a>

有关于<a>链接在HTML中的使用,还可以参阅:

按钮在HTML中的使用

在Web中什么时候使用<button>元素呢?其实有一个简单的规则:

当没有“有意义的href”时使用<button>,或没有JavaScript时,点击它不会做任何事情,它应该也是一个<button>

在Web中使用按钮最常见的有两个场景,一个是在<form>中使用,另一个是在非表单中使用。如果<button>用于表单中,该按钮具有一个默认行为,就是可以提交(或重置)该表单。如果<button>没有被用于表单中,默认情况之下该按钮不具备任何交互行为,如果希望该按钮具备一定的交互行为,就需要使用JavaScript脚本来完成。

接下来,我们先来看看<form>中的<button>怎么使用。

一个<form>中的<button>默认会做提交(type="submit"重置(type="reset"。具体行为需要根据其type属性来判断:

  • submit:此按钮将表单数据提交给服务器。如果未指定属性,或者属性动态更改为空值或无效值,则此值为默认值
  • reset:此按钮重置所有组件为初始值
  • button:此按钮没有默认行为。它可以有与元素事件相关的客户端脚本,当事件出现时可触发
  • menu:此按钮打开一个由指定<menu>元素进行定义的弹出菜单

比如下面的示例代码:

<form action="/" method="POST">
    <button>Submit</button>

    <button type="submit">Submit</button>

    <button type="reset">Reset</button>

    <button type="button">Non-submitting button</button>
</form>

另外就是,在<form>中使用<button>时,有一些小技巧,可以覆盖<form>本身的属性。这些小技巧其实就是<button>的一些属性:

  • formaction:表示程序处理button提交信息的URI。如果指定了,将重写button表单拥有者的action属性
  • formenctype:如果buttonsubmit类型,此属性值指定提交表单到服务器的内容类型。可选值主要有application/x-www-form-urlencoded(未指定时的默认值)、multipart/form-data(如果使用type属性的<input>元素设置文件,使用此值)和text/plain。如果指定此属性,它将重写button的表单拥有者的enctype属性
  • formmethod:如果buttonsubmit类型,此属性指定浏览器提交表单使用的HTTP方法。可选值主要有post(来自表单的数据被包含在表单内容中,被发送到服务器)和get(来自表单的数据以'?'作为分隔符被附加到formURI属性中,得到的URI被发送到服务器。当表单没有副作用,且仅包含ASCII字符时使用这种方法)。如果指定了,此属性会重写button拥有者的method属性
  • formnovalidate:如果buttonsubmit类型,此布尔属性指定当表单被提交时不需要验证。如果指定了,它会重写button拥有者的novalidate属性
  • formtarget:如果buttonsubmit类型,此属性指定一个名称或关键字,表示接收提交的表单后在哪里显示响应。这是一个浏览上下文(例如tabwindow或内联框架)的名称或关键字。如果指定了,它会重写button拥有者的target 属性

来看一段简单的示例代码:

<form action="/" method="get">

    <!-- formaction覆盖form的action属性 -->
    <button formaction="/elsewhere/" type="submit">Submit</button>

    <!-- formenctype覆盖form的encytype属性 -->
    <button formenctype="multipart/form-data" type="submit">Submit</button>

    <!-- formmethod覆盖form的method属性 -->
    <button formmethod="post" type="submit">Submit</button>

    <!-- 不要验证字段 -->
    <button formnovalidate type="submit">Submit</button>

    <!-- 覆盖目标,例如在新标签页打开 -->
    <button formtarget="_blank" type="submit">Submit</button>

</form>

事实上大家平时很少会在<button>中添加上面所提到的属性,但有一个autofocus属性来说还是很有意思的。众所周知,<button>也是可**聚焦元素**之一。在<button>元素上(其实很多表单控件的元素)使用autofocus属性对于想让页面一加载就得到焦点来说,是非常容易的。

<div class="modal">
    <h2>Save document?</h2>

    <button>Cancel</button>
    <button autofocus>OK</button>
</div>

正如上面的modal对话框,其中在OK按钮上显式设置了autofocus属性,这样一来该按钮就是默认动作,用户只需要按钮下Enter键(触摸屏幕还是需要点击该按钮),就能将文档保存(相当于一个确认动作)。这对于用户体验是有较大的帮助。

虽然autofocus在未经用户允许的情况之下就能移动焦点,但这样的骚操作对于屏幕阅读器和屏幕放大镜用户来说可能是一个问题。因此在使用autofocus属性时首先要注意:自动聚焦表单控件会使使用屏幕阅读器技术的视障碍人士感到困惑。当自动获取焦点被分配时,屏幕阅读器会首先把表单控件(就是设置了autofocus属性的元素)给用户,而不是事先通知他们。正如上面的示例,屏幕阅读器首先会给用户传递“OK,按钮”这样的信息,试想一下,没有上下文的信息,用户是不是会感到困惑。

如果真的要用autofocus属性的话,建议你配合aria-labelledbyaria-describedby一起使用。就拿上面的示例,可以改成:

<div class="modal" role="dialog" aria-labelledby="acc__title">
    <h2 id="acc__title">Save document?</h2>

    <button>Cancel</button>
    <button autofocus>OK</button>
</div>

这个时候屏幕阅读器会读出:“Save document? 对话框。OK,按钮”。

**建议:**在同一个页面不要同时给多个元素显式设置autofocus属性。

如果你还想了解更多有关于autofocus属性相关的知识,还可以阅读:

<a>链接中,不带href属性的<a>链接是一个禁用链接,但在<button>中有所不同,如果我们想让禁用一个按钮,只需要显式的给按钮添加disabled属性:

<button disabled>Delete</button>

另外,显式设置了disabled<button>是不具有交互行为的。如果你想让交互体验更好,可以在禁用按钮边上添加相应的描述性文本。还有一个更好的方法是让某人提交表单,然后解释为什么它在验证反馈消息中不起作用。

<button disabled>Pay Now</button>
<p class="error-message">Correct the form above to submit payment.</p>

HTML中的<button><a>类似,默认也是具有role的,其role就是button。不过role="button"也是只用于非<button>元素上,用来模拟一个button角色:

<!-- 不要像下面这样使用 -->
<button role="button">Play</button>

<!-- 可以像下面这样使用 -->
<span 
    tabindex="0"
    role="button"
    onclick="play()"
    onkeydown="play()">Play</span>

在HTML中或者说<form>表单中的<input>控件,如果type取值为submitreset或者button时,他和<button>在功能上是相同的,但在某种意义上是有差异的,比如说<input>不能包含子元素,而<button><a>有点相似,可以包含子元素。比如说,希望制作一个只含有Icon图标的按钮时,就可以像下面这样:

<button>
    <svg class="heart" width="24" height="24" viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true">
        <path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"></path>
    </svg>
    <span class="sr-only">赞</span>
</button>

甚至可以包含其他的元素,比如下面这个Demo:

虽然说<button>可以内嵌其他元素,但不会像<a>那样做,同样拿卡片来举例:

<!-- 不这样使用 -->
<button>
    <img src="/path/to/image.png" alt="">
    <h2>title</h2>
    <p>content</p>
</button>

但卡片上只有一个<button>,而且想让整个卡片具有可点的话,方法和<a>可以是一样的:

<a><button>添加样式

客户端浏览器对于Web中的任何一个元素都具有一定的默认样式,比如<a><button>在现代主流浏览器下的默认效果:

不同浏览器下默认样式是有所差异的,特别是<button>元素:

为了避免同一元素在不同浏览器中渲染的差异性,会对其做一些初始化样式,比如normalize.css就针对<button>做过样式初始化:

button {
    font-family: inherit;       /* For all browsers */
    font-size: 100%;            /* For all browsers */
    line-height: 1.15;          /* For all browsers */
    margin: 0;                  /* Firefox and Safari have margin */
    overflow: visible;           /* Edge hides overflow */
    text-transform: none;       /* Firefox inherits text-transform */
    -webkit-appearance: button; /* Safari otherwise prevents some styles */
}

button::-moz-focus-inner {
    border-style: none;
    padding: 0;
}

button:-moz-focusring {
    outline: 1px dotted ButtonText;
}

这些默认样式往往都无法达到在视觉上的需求,不管是链接还是按钮,如果希望他们的样式能达到视觉上的效果就需要通过CSS来完成。比如下面这个示例:

CSS的世界就是一个魔幻世界,CSS具有强大的魔力,只要你想的,它都基本上能帮你实现。你可以把一个<a>整得像一个<button>,你也可以把一个<button>整得像一个链接。比如下面的这个示例,从UI上看,它是按钮,事实上它是个链接:

另外,不管是<a>链接还是<button>按钮,我们还可以在不同的状态添加不同的样式:

对于CSS怎么给<a><button>添加样式,这里就不再做更多的阐述,如果你感兴趣的话,可以阅读 @shadeed9 的《Styling The Good Ol' Button Element》一文。

虽然这篇文章是介绍怎么给<button>元素添加样式的,但文章中所涉及到的CSS也同样可以应用于<a>元素上。特别是在实现类似按钮风格的<a>链接。

JavaScript对链接和按钮的影响

前端开发者都清楚,一个Web页面或应用程序会由HTML、CSS和JavaScript构成:

其中JavaScript可以让Web中的元素动起来或具有一定的交互性,同样也可以阻止一些交互行为:

前面的内容中我们了解到:

  • 带有href<a>链接,可以在同一个页面中跳转或跳转到一个新页面(或新窗)
  • <from>中的<button>按钮其默认行为是可以提并表单,如果不在<form>中的<button>不具有任何行为

如果我们想改变他们的默认行为的话,则需要通过JavaScript来实现。比如你想让用户点击一个链接时没有任何效果(不能正常工作),就可以使用preventDefault来阻止:

const jumpLinks = document.querySelectorAll("a[href^='#']");

jumpLinks.forEach(link => {
    link.addEventListener('click', event => {
        event.preventDefault();
        // ...
    });
});

同样的,JavaScript也可以给<button>添加你想做的事情,也可以禁用他的默认行为。比如页面上有一个按钮,用户点击可以提交一笔付款。对于这样的操作,如果处理不好是会造成一定的资损的,比如点击按钮多次提交多个付款请求。在这种情况之下,可以让按钮只有一次点击行为:

document.querySelector("button").addEventListener('click', function(event) {
    event.currentTarget.setAttribute("disabled", true);
}, {
    once: true
});

其实JavaScript很强大的,你可以根据自己的业务需求来做不同的处理。

可访问性的考虑

在《编写HTML时要考虑可访问性》一文中多次提到过,使用带有语义化的标签对于可访问性的重要性:

也就是说,在Web中不要使用其他的标签元素,比如divspan来模拟<a><button>元素。虽然说可以这么做,事实上很多发者也这样做了。

<div role="link" tabindex="0">Link</div>
<div role="button" tabindex="0">Button</div>

这样做也是要付出一定代价的,比如带有href<a>可以直接将用户到新的地方,可是用非<a>标签来模拟的话,就需要通过JavaScript来实现。<button>是同样的,比如下面这样的示例代码:

const buttons = document.querySelectorAll('.button');

[...buttons].forEach(button => {
    button.addEventListener('click', doSomething);
    button.addEventListener('keyup', (event) => {
        if (event.key == 'Enter' || event.key == ' ') {
            doSomething();
        }
    });
});

function doSomething() {
    console.log('Something!');
}

除了要添加一些JavaScript之外,还需要添加CSS样式,比如获得焦点的样式,悬停状态的样式等。即使你采用的是<a><button>元素,CSS样式对于可访问性也是有较大的影响,相关的影响在《编写CSS时要考虑可访问性》中有详细的介绍过,这里就不做过多的阐述。

就算是在CSS和JavaScript做得比较好了,如果使用ARIA相关技术来实现linkbutton的角色,也还会涉及到ARIA其他的知识。另外,可访问性所涉及到的知识面非常的广,如果你对这方面的知识感兴趣的话,可以关注小站有关于A11Y相关的更新

什么时候使用链接,什么时候使用按钮?

如果不追求HTML的语义化和可访问性来说,HTML中的很多标签元素都可以是链接或按钮,但我们这里聊的是链接和按钮,在文章快要结束的时候,我想问问,什么时候应该使用链接,什么时候又应该使用按钮?这里有一个快速的规则:

  • 你是否为用户提供了访问另一个页面或同一个页的不同部分的方法?如果是,那就使用<a>链接,并且带有href
  • 你是否在使用JavaScript来做一些交互动作,比如说click?如果是,那就使用<button>按钮
  • 你是否需要提交一个<form>?如果是,那就使用<input stype="submit">(也可以使用<button type=“submit”>

有关于这方面更多的讨论还可以阅读: