在许多状况下,[可用的HTML表单组件]()是不够的。若你想在诸如<select>
元素的组件上[应用高级样式]()、或者想定制组件的行为,你就只能选择建立本身的表单组件。java
咱们将经过本文学习如何构建一个表单组件。为达到目的,咱们选择重构<select>
元素做为例子。jquery
注意:咱们会专一于构建组件,但不会关注如何保证代码的通用和可重用。构建组件时会涉及到一些特殊的JavaScript代码和未知上下文中的DOM操做,而这些内容已经超出了本文的讨论范围。git
在构建一个定制组件前,应先从明确你想要达到的效果开始,这会节省你宝贵的时间。具体来说,清晰地定义组件的全部状态是很重要的。要作到这点,最好从一个已经存在的、状态和行为已经为人所熟知的组件开始,这样你就只需尽量地模仿该组件便可。github
在咱们的例子中,咱们会重构<select>
元素。下面是咱们指望达到的结果:web
上面的截屏展现了咱们组件的三个主要状态:普通状态(左)、激活状态(中)和打开状态(右)。chrome
至于组件的行为,咱们但愿能够像其余原生组件同样,经过鼠标和键盘来操控它。先从定义组件如何到达各个状态开始:segmentfault
组件变为普通状态:数组
页面加载
组件激活且用户点击了组件外任意地方
组件激活且用户用键盘把焦点移动到别的组件
注意:在页面上移动焦点一般是经过敲tab键来实现的,但不是全部地方都遵循这个惯例。好比Safari上默认是用Option+Tab组合键来实如今页面上移动焦点。
组件变为激活状态:
用户点击了组件
用户按tab键且组件得到了焦点
组件处于打开状态且用户点击了组件
组件变为打开状态:
组件处于其余非打开状态且用户点击了它
在知道如何改变状态后,定义组件的值如何被改变也是很重要的:
组件的值改变:
在组件处于打开状态时,用户点击了一个选项
在组件处于激活状态时,用户按了上下方向键
最后咱们来定义下组件选项的行为:
当组件处于打开状态时,被选中的选项会高亮
当鼠标移到一个选项上,该选项会高亮且原先高亮状态的选项会恢复到普通状态
考虑例子的演示目的,咱们的分析就到此为止;然而若是你认真读过上文,会发现咱们漏了一些效果。好比,当组件处于打开状态时,若是用户按了tab键会发生什么呢?答案是--什么都不会发生。正确的效果虽然显而易见(译注:参考select原生组件,也是什么都不会发生),但事实是咱们没有在上述说明中定义它,这个效果很容易就会被忽视。在团队协做中,若是设计组件的人和实现它的人不一样,这是特别容易出现的问题。
另外一个有趣的问题是:组件处于打开状态时,用户按上下方向键会发生什么?要回答它,须要一点技巧。若考虑激活状态和打开状态是彻底不相干的,那答案就仍是“什么都不会发生”,由于咱们并未给打开状态定义任何键盘交互。另外一方面,若是考虑激活状态和打开状态有部分重叠,那答案就是:值可能会改变但选项也所以不会被高亮(译注:大概由于组件已经处于激活状态了吧),这也是由于当组件处于打开状态时,咱们并未给选项未定义任何键盘交互(只是定义了组件打开时应该发生什么,却没定义打开后要干吗)。
在咱们的例子中,缺失的特性仍是比较明显的,因此咱们还能处理得了它;但当面对来自外部的新组件时,因为没人知道正确的行为是什么,这时就会形成真正的麻烦。所以,花些时间在设计阶段是颇有必要的,若是你此时定义了一个不佳的交互,或忘记了去定义,后续在用户使用了该交互时再去重定义是很困难的。若(处理交互时)你有疑问,应积极寻求他人的帮助;而若你心中有数,则应绝不犹豫地进行用户测试。上面讨论的过程,可称之为UX(译注:用户体验)设计。若是你想了解更多这方面的内容,能够参考下面这些资源:
注意:在多数系统中,还有有一种方法能够打开
<select>
元素以查看全部可用的选项(这和用鼠标点击<select>
元素是同样的)。这个方法在Windows下是用Alt+下方向键来实现的,咱们的例子中并未实现它--但要这样作也很简单,由于整个操做的机制已经被用于实现click事件了。
上面咱们肯定了组建的基本功能,如今能够来构建咱们的组件了。第一步咱们要定义其HMLT结构,并为其添加基本的语义。下面是咱们重构<select>
元素所需的代码:
<!-- 这是组件的主要容器. tabindex 特性用于让用户能聚焦到该组件。 用JavaScript来设置它是一个更好的办法 --> <div class="select" tabindex="0"> <!-- 这个容器用于展现组件的当前值 --> <span class="value">Cherry</span> <!-- 这个容器会包含组件里的全部可用选项,由于选项是一个列表,全部采用ul元素更加合适 --> <ul class="optList"> <!-- 每一个选项只会包含要展现的内容,稍后咱们会了解如何处理其真实值,用来和表单数据一块儿发出去 --> <li class="option">Cherry</li> <li class="option">Lemon</li> <li class="option">Banana</li> <li class="option">Strawberry</li> <li class="option">Apple</li> </ul> </div>
要注意此处class名的使用;这些class标记了每一个相关的元素,而不须要依赖其实际使用的HTML元素。这么作能确保咱们不会把CSS和JavaScript与HTML结构做强关联,从而作到改变后续的组件代码实现时,不破坏使用该组件的代码。好比你想实现一个一样的<optgroup>
元素时,可用直接用相同的代码来调用。
如今咱们已经有了组件的结构了,接下来要来设计组件了。建立这个自定义组件的目的,是为了用咱们想要的形式来给该组件添加样式。要作到这点,咱们要把CSS的编码工做拆为两部分:第一部分是让咱们组件和<select>
元素看起来一致的必要CSS规则,第二部分是用来让组件变成咱们想要的样子的样式。
必要的样式是用来处理咱们组件的三个状态的。
.select { /* 给选项列表建立一个定位上下文 */ position: relative; /* 让咱们的组件成为文本流的一部分,并使之可伸缩 */ display : inline-block; }
咱们须要一个额外类名active
,来定义组件处于激活状态时的外观。由于咱们的组件是能够得到操做焦点的,因此还要将相同的样式用于:focus
伪类,保证激活和得到焦点时的行为一致。
.select.active, .select:focus { outline: none; /* box-shadow 属性不是必要的,但它能够做为默认值保证激活状态可见,去掉它也是能够的。 */ box-shadow: 0 0 3px 1px #227755; }
接下来处理选项列表:
/* 这里的 .select 选择器,用来确保后面选择器匹配的元素就是咱们组件中那个 */ .select .optList { /* 下面样式确保选项列表会展现在当前值下面、并在HTML文档流以外 */ position : absolute; top : 100%; left : 0; }
咱们须要一个额外的class来处理选项列表的隐藏状态。为了管理激活和展开两个不一样的状态,这么作是颇有必要的。
.select .optList.hidden { /* 下面是一个以无障碍方式来隐藏列表的简单方法,咱们会在文末讨论更多关于无障碍访问的内容。 */ max-height: 0; visibility: hidden; }
在有了基本的功能以后,有趣的部分开始了。下面是一个可选的例子,效果和本文开头的那个截图一致。可是你也能够自由探索、看看你能实现怎样的效果。
.select { /* 全部的大小值都会采用em值来保证无障碍访问 (保证组件在用户使用浏览器纯文字模式下的缩放时,还保留自适应的能力)。 在计算时,假设1em == 16px,这也是大多数浏览器的默认值。 若是你对px到em的转换感到困惑,能够访问:http://riddle.pl/emcalc/ */ font-size : 0.625em; /* this (10px) is the new font size context for em value in this context */ font-family : Verdana, Arial, sans-serif; -moz-box-sizing : border-box; box-sizing : border-box; /* 须要额外的空间来添加向下箭头 */ padding : .1em 2.5em .2em .5em; /* 1px 25px 2px 5px */ width : 10em; /* 100px */ border : .2em solid #000; /* 2px */ border-radius : .4em; /* 4px */ box-shadow : 0 .1em .2em rgba(0,0,0,.45); /* 0 1px 2px */ /* 第一句声明用于不支持线性渐变的浏览器。 第二句声明是由于基于Webkit的浏览器对线性渐变属性还要加个前缀。 若你还想支持老旧浏览器,可参考http://www.colorzilla.com/gradient-editor/ */ background : #F0F0F0; background : -webkit-linear-gradient(90deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); background : linear-gradient(0deg, #E3E3E3, #fcfcfc 50%, #f0f0f0); } .select .value { /* 由于value元素可能会比组件还宽,因此咱们得保障这不会改变组件的宽度 */ display : inline-block; width : 100%; overflow : hidden; vertical-align: top; /* 若是内容溢出了,最好能有省略号来替代。 */ white-space : nowrap; text-overflow: ellipsis; }
咱们不须要额外的元素来设计向下箭头,而是使用:after
伪元素。但其实这也能在select类上用一个简单的背景图片来实现。
.select:after { content : "▼"; /* 使用 unicode 字符 U+25BC;参见 http://www.utf8-chartable.de */ position: absolute; z-index : 1; /* 用来保证箭头会叠在选项列表上面 */ top : 0; right : 0; -moz-box-sizing : border-box; box-sizing : border-box; height : 100%; width : 2em; /* 20px */ padding-top : .1em; /* 1px */ border-left : .2em solid #000; /* 2px */ border-radius: 0 .1em .1em 0; /* 0 1px 1px 0 */ background-color : #000; color : #FFF; text-align : center; }
接下来,给选项列表添加样式:
.select .optList { z-index : 2; /* 代表选项列表会始终叠在向下箭头之上 */ /* 重置ul元素的默认样式 */ list-style: none; margin : 0; padding: 0; -moz-box-sizing : border-box; box-sizing : border-box; /* 确保即便值太少让选项列表小于组件主体,也能让选项列表会和组件主体同样大 */ min-width : 100%; /* 若是列表太长了,其内容会在垂直方向上溢出(默认会自动添加一个垂直方向的滚动条), 但不会在水平方向上也这样(由于咱们没有设置宽度,列表会有个自适应宽度,若是不能自适应, 内容就会被截断) */ max-height: 10em; /* 100px */ overflow-y: auto; overflow-x: hidden; border: .2em solid #000; /* 2px */ border-top-width : .1em; /* 1px */ border-radius: 0 0 .4em .4em; /* 0 0 4px 4px */ box-shadow: 0 .2em .4em rgba(0,0,0,.4); /* 0 2px 4px */ background: #f0f0f0; }
对于选项,咱们须要添加一个highlight类来标明用户会选取(或已经选取)的值。
.select .option { padding: .2em .3em; /* 2px 3px */ } .select .highlight { background: #000; color: #FFFFFF; }
下面就是咱们三个状态的实现效果了:
效果
如今咱们组件的结构和设计都已经作好,能够来写JavaScript代码让组件真正能运行起来了。
警告:下面的代码是教学代码,在实际编码时不能直接像下面同样使用。其中许多部分,并无将来使用的保障、并且也不能在老旧浏览器上使用。此外,这些代码也有在生产环境中应该被优化掉的冗余部分。
注意:建立可复用的组件是颇有技巧性的。W3C Web Component 草案是这个特定问题的一个解决方案。X-tag project是这一规范的实验性实现;咱们鼓励你好好了解下它。
在开始以前,咱们须要知道JavaScript的一个严重问题:在浏览器里,它是一个不可靠的技术。当你在建立自定义组件的时候,你不得不依赖JavaScript,由于它是把全部东西维系在一块儿的绳索。可是,在许多状况下JavaScript并不能在浏览器中运行:
用户禁用了JavaScript:这已是个最不常见的状况了,如今不多有人会禁用JavaScript。
脚本没有加载:这是最广泛的状况,特别是在网络不太可靠的移动端。
脚本有bug:你要常常考虑这一可能性。
脚本和第三方脚本冲突了:使用了追踪脚本或用户自用的书签时会发生这种状况。
用户使用了老旧浏览器,而且你须要的一种特性不被支持:这一般发生在你用了很新的API时。
因为有这些风险,咱们须要认真考虑下JavaScript不起做用时会发生什么。深刻处理这个问题已经超出了本文的论述范围,由于这和你但愿如何让脚本通用和可复用密切相关,咱们不会在例子中考虑这点。
在本文的例子中,若JavaScript代码不能运行,咱们会回退到展现标准的<select>
元素。要作到这点,得先来作两件事。
首先,咱们要在使用自定义组件以前,添加一个普通的<select>
元素。而为了能让自定义组件的数据和剩下的表单数据一块儿发送,这一步也是颇有必要的。后边咱们还会详细介绍。
<body class="no-widget"> <form> <select name="myFruit"> <option>Cherry</option> <option>Lemon</option> <option>Banana</option> <option>Strawberry</option> <option>Apple</option> </select> <div class="select"> <span class="value">Cherry</span> <ul class="optList hidden"> <li class="option">Cherry</li> <li class="option">Lemon</li> <li class="option">Banana</li> <li class="option">Strawberry</li> <li class="option">Apple</li> </ul> </div> </form> </body>
第二,咱们还得添加两个新的类名,实现隐藏不须要的元素(即在脚本能运行时的<select>
元素、或脚本不能运行时的自定义组件)。要注意的是在默认状况下,此处的HTML代码会隐藏自定义组件。
.widget select, .no-widget .select { /* 这个CSS选择器意思是: - 要么body的类名被设为"widget",此处就要隐藏`<select>`元素 - 要么body的类名没有改变,还是"no-widget",那么类名为"select"的元素就要被隐藏了 */ position : absolute; left : -5000em; height : 0; overflow : hidden; }
至此,咱们只须要一个JavaScript开关来决定脚本是否能运行了。这个开关很简单:若页面加载了脚本并运行,就会移除no-widget
类并添加widget
类,实现对<select>
元素和自定义组件可见与否的切换。
window.addEventListener("load", function () { document.body.classList.remove("no-widget"); document.body.classList.add("widget"); });
注意:若你真的想让你的组件变得通用和可复用,除了做类名的切换,更好的方法是(在脚本能执行时)只添加
widget
类名隐藏<select>
元素,并在页面中的每一个<select>
元素后面指定自定义的组件、动态添加到DOM树中。
在将要建立的代码中,咱们会使用标准的DOM API来完成工做。然而,尽管浏览器对DOM API的支持已经愈来愈好,但在老旧浏览器上仍存在一些问题(特别在很老的IE上)。
若你想避免老旧浏览器上的麻烦,有两种方法能够作到:使用诸如jQuery, $dom, prototype, Dojo, YUI之类的稳定框架;或者补充那些缺失的但你要用的特性(经过条件加载能够很容易作到这点,好比可使用yepnope库)。
咱们计划使用的特性以下(从风险最大到最安全排列):
forEach(不属于DOM可是现代JavaScript的特性)
除了上述特性的可用性,在开发以前仍存在一个问题。querySelector()
方法返回的是一个NodeList
而不是数组。Array
对象支持forEach
方法、但NodeList
不支持。由于NodeList
看起来像数组、也由于forEach
方法用起来很方便,因此咱们能够很简单地就给NodeList
添加forEach
支持、让咱们的工做轻松些,就像下面这样:
NodeList.prototype.forEach = function (callback) { Array.prototype.forEach.call(this, callback); }
咱们说这很简单可不是瞎说的哦。
前期工做已经作好了,咱们如今能够来定义用户和咱们的组件交互时要用到的全部函数了。
/* 这个函数会在取消激活自定义组件时被使用 须要一个参数: select: 类名为`select`且要被取消激活的DOM节点 */ function deactivateSelect(select) { /* 若组件未被激活,则什么都不作 */ if (!select.classList.contains('active')) return; /* 获取自定义组件的选项列表 */ var optList = select.querySelector('.optList'); /* 关闭选项列表 */ optList.classList.add('hidden'); /* 取消自定义组件的激活状态 */ select.classList.remove('active'); } /* 该函数用于让用户(取消)激活组件 须要两个参数: select:类名为`select`且要被激活的DOM节点 selectList:类名为`select`的全部DOM节点的列表 */ function activeSelect(select, selectList) { /* 若组件已经激活,则什么都不作 */ if (select.classList.contains('active')) return; /* 全部自定义组件的激活状态都得取消, 由于deactivateSelect函数知足了做为forEach回调函数的要求, 因此咱们会直接使用它而不是用一个中间的匿名函数 */ selectList.forEach(deactivateSelect); /* 开启该组件的激活状态 */ select.classList.add('active'); } /* 该函数用于让用户打开和关闭选项列表 须要一个参数: select:有一个列表要切换状态的DOM节点 */ function toggleOptList(select) { /* 选项列表能够从组件那得到 */ var optList = select.querySelector('.optList'); /* 改变列表的类名来展现和隐藏它 */ optList.classList.toggle('hidden'); } /* 该函数用于高亮一个选项 须要两个参数: select:类名为`select`且包含要被高亮选项的DOM节点 option:类名为`option`且要被高亮的DOM节点 */ function highlightOption(select, option) { /* 得到自定义select元素的全部可用选项 */ var optionList = select.querySelectorAll('.option'); /* 移除全部选项的高亮 */ optionList.forEach(function (other) { other.classList.remove('highlight'); }); /* 高亮正确的选项 */ option.classList.add('highlight'); };
上面就是处理自定义组件的多个状态所需的全部函数。
接下来,咱们把这些函数绑到合适的事件上:
/* 在文档加载出来后处理下事件绑定 */ window.addEventListener('load', function () { var selectList = document.querySelectorAll('.select'); /* 每一个自定义组件都要被初始化 */ selectList.forEach(function (select) { /* 全部的`select`元素也要被初始化 */ var optionList = select.querySelectorAll('.option'); /* 用户把鼠标放到一个选项上时,高亮该选项 */ optionList.forEach(function (option) { option.addEventListener('mouseover', function () { /* 注意:在咱们的函数调用内,`select`和`option`变量都是局部的 */ highlightOption(select, option); }); }); /* 用户点击了自定义的select元素 */ select.addEventListener('click', function (event) { /* 注意:在咱们的函数调用内,`select`变量是局部的 */ /* 改变选项列表的可见状态 */ toggleOptList(select); }); /* 组件得到焦点时 /* 用户点击组件或用tab键访问组件时,组件会得到焦点 */ select.addEventListener('focus', function (event) { /* 注意:在咱们的函数调用内,`select`和`selectList`变量都是局部的 */ /* 激活该组件 */ activeSelect(select, selectList); }); /* 组件失去焦点时 */ select.addEventListener('blur', function (event) { /* 注意:在咱们的函数调用内,`select`变量是局部的 */ /* 取消激活该组件 */ deactivateSelect(select); }); }); });
至此,组件已经能根据咱们的设计来改变其状态了,但它的值目前还不会更新,接下来咱们就会处理这点。
如今组件已经能用了,但咱们还得加点代码,根据用户的输入更新它的值、并让其能随着表单数据一块儿发送它的值。
要作到这点,最简单的方式就是在私底下用一个原生组件。这样一来,自定义组件就会跟踪浏览器提供的内置控件的值,并和平时同样在表单提交时发送它的值。在浏览器已经为咱们作好这一切时,没有必要来从新发明轮子了。
如前所示,出于可访问性的缘由,咱们已经用了一个原生的select组件来做为回退;同步这个组件的值和自定义组件的值是很容易的:
// 该函数用于更新展现的值,并和原生组件做同步 // 须要两个参数: // select:类名为`select`且值要更新的DOM节点 // index:选定的值的索引 function updateValue(select, index) { // 咱们得为给定的自定义组件获取原生组件 // 本例中,原生组件是自定义组件的兄弟节点 var nativeWidget = select.previousElementSibling; // 得到自定义组件的值容器 var value = select.querySelector('.value'); // 得到完整的选项列表 var optionList = select.querySelectorAll('.option'); // 设置选中索引为咱们选择的选项的索引 nativeWidget.selectedIndex = index; // 更新对应的值容器 value.innerHTML = optionList[index].innerHTML; // 高亮自定义组件中关联的选项 highlightOption(select, optionList[index]); }; // 该函数返回原生组件当前选中的索引 // 须要一个参数: // select:类名为`select`且和原生组件关联的DOM节点 function getIndex(select) { // 咱们得为给定的自定义组件获取原生组件 // 本例中,原生组件是自定义组件的兄弟节点 var nativeWidget = select.previousElementSibling; return nativeWidget.selectedIndex; };
咱们能够用上面这两个函数来绑定原生组件和自定义组件:
// 在文档加载出来后处理下事件绑定 window.addEventListener('load', function () { var selectList = document.querySelectorAll('.select'); // 每一个自定义组件都要被初始化 selectList.forEach(function (select) { var optionList = select.querySelectorAll('.option'), selectedIndex = getIndex(select); // 让自定义组件能聚焦 select.tabIndex = 0; // 让原生组件不可聚焦 select.previousElementSibling.tabIndex = -1; // 确保默认选择的值被正确展现 updateValue(select, selectedIndex); // 用户点击选项时,更新对应的值 optionList.forEach(function (option, index) { option.addEventListener('click', function (event) { updateValue(select, index); }); }); // 用户在聚焦的组件上按键盘时,更新对应的值 select.addEventListener('keyup', function (event) { var length = optionList.length, index = getIndex(select); // 当用户按下箭头时,跳到后一选项 if (event.keyCode === 40 && index < length - 1) { index++; } // 当用户按上箭头时,跳到前一选项 if (event.keyCode === 38 && index > 0) { index--; } updateValue(select, index); }); }); });
上面的代码里,要注意tabIndex属性的使用。该属性用来确保原生组件不会得到焦点,并确保自定义组件能在用户用键盘或鼠标访问时得到焦点。
经过上面的工做,咱们已经完成任务了!下面就是结果:
等等,咱们真的完成了吗?
咱们已经构建了一个能够运行的组件,虽然距离获得一个具备完整特性的选择框还很远,但它运行得还不错。然而,咱们以前所作的只是在处理DOM而已,这个组件并非真正语义化的,并且虽然它看起来像个选择框,但在浏览器的角度它却并非这样,所以无障碍技术也不会认为它是个选择框。简而言之,它就是个无障碍性不好的漂亮选择框!
幸运的是,咱们有个解决方案叫ARIA。ARIA表示“无障碍的富Internet应用”,它是个W3C规范,用来让web应用和自定义组件变得无障碍。基本上这个规范就是一系列拓展了HTML的特性,用这些特性,咱们能够更好地描述角色、状态和属性,让咱们刚才设计的元素变得像其尽力模仿的原生元素同样。使用这些特性很简单,下面咱们来试试。
ARIA使用的关键特性是role。该特性会接收一个定义了元素用途的值,每一个值都表明了元素的特色和行为。在本例中,咱们会使用一个listbox做为role值,这个值是个“复合的role”,指定的元素能够包含多个特定role的子元素(本例中,至少有一个元素role值为option
)。
值得注意的是,ARIA定义的role默认会自动用于标准的HTML标签中。好比说,<table>
元素对应grid
,<ul>
元素对应list
。由于咱们的组件使用了<ul>
元素,因此得确保组件的listbox
role能覆盖掉<ul>
元素的list
值。为此,可使用presentation
这个role值,该值用来指明一个没有特殊含义的元素,并且该元素只用来展现信息而已。这里咱们会给<ul>
应用presentation
值。
要使用listbox
这个role值,得像下面同样修改HTML:
<!-- 给最外层元素指定role="listbox" --> <div class="select" role="listbox"> <span class="value">Cherry</span> <!-- 给ul元素指定role="presentation" --> <ul class="optList" role="presentation"> <!-- 给全部li元素指定role="presentation" --> <li role="option" class="option">Cherry</li> <li role="option" class="option">Lemon</li> <li role="option" class="option">Banana</li> <li role="option" class="option">Strawberry</li> <li role="option" class="option">Apple</li> </ul> </div>
注意:若是你想兼容那些不支持CSS特性选择器的老旧浏览器,同时使用
role
特性和class
特性这种作法是必须的。
仅使用role特性是不够的,ARIA自己也提供了不少许多状态和属性特性。对这些特性用得越多和越恰当,网页就越能被无障碍技术所理解。在咱们的例子中,只会用到一个特性:aria-selected
。
aria-selected
特性用于标记当前选中的选项,这样无障碍技术就能提示用户当前选中项是什么。咱们会在JavaScript中动态地使用它,在用户选中一个选项时能标记该选中项。为此,得修改下updateValue()
函数:
function updateValue(select, index) { var nativeWidget = select.previousElementSibling; var value = select.querySelector('.value'); var optionList = select.querySelectorAll('.option'); // 确保全部的选项未被选中 optionList.forEach(function (other) { other.setAttribute('aria-selected', 'false'); }); // 确保选择的那个选项被选中 optionList[index].setAttribute('aria-selected', 'true'); nativeWidget.selectedIndex = index; value.innerHTML = optionList[index].innerHTML; highlightOption(select, optionList[index]); };
上述修改的最终效果以下(访问该组件时使用无障碍技术,譬如NVDA或VoiceOver,会有更好的体验):
至此咱们已经了解了建立定制表单组件的全部基本知识,但如你所见,这么作并不简单,若是使用第三方库的话会比本身从头写起更好、更简单(固然除非你是想构建这样一个库)。
下面是你在本身开发以前应该参考下的库:
若你想更进一步使用本例,为让其中的代码变得通用和可复用,还要对代码作一些改进。这个练习你能够本身尝试下,这里有两个提示:首先,全部函数的第一个参数都相同,这就意味着这些函数须要有同一个执行上下文,使用一个对象来共享执行上下文是很明智的。此外,代码还得保证兼容,即代码最好能在兼容不一样Web标准的多种浏览器下运行。