简介
1 .之前看了ant design的select的dom结构,是ui>li的结构,可能根源就在这里
2 .而且他的考虑还很全。
实现目标
1 .状态:正常,活动,打开
2 .键鼠都支持
3 .细节:
1 .控件处于活动状态,用户点击空间以外的位置
2 .控件是活动状态,但用户使用键盘将焦点移动到另一个小部件
3 .支持tab移动焦点
4 .出于可访问性方面的原因,所有尺寸都会由em值表示,用来确保用户在文本模式下使用浏览器缩放时组件的可缩放性.1em=16px
4 .活动状态的触发
1 .用户点击
2 .用户按下tab让控件获得了焦点
3 .控件呈现打开状态,然后用户点击控件
5 .打开状态的触发
1 .控件在非打开时被用户点击
6 .关闭状态的触发
1 .点击页面别的地方
2 .点击其中一个选项
3 .按esc快捷键
7 .值怎么改变
1 .用户在打开状态下点击一个选项
2 .控件在活动状态下用户按下键盘上方向键
8 .交互逻辑还需要搞这个:https://en.wikipedia.org/wiki/Usability_testing 用户可行性测试http://uxdesign.com/
成果
展示方面 <style>
.select{
position: relative;
/* 先创建一个上下文定位 */
display: inline-block;
/* 组件变成文本流,还能调整大小 */
/* 美化部分 */
font-size: 0.625em;
box-sizing: border-box;
/* 向下的箭头需要一些额外的空间 */
padding: .1em 2.5em .2em .5em;
width: 10em;
border:.2em solid #000;
border-radius: .4em;
box-shadow: 0 .1em .2em rgba(0,0,0,.45);
background:#F0F0F0;
}
.select:after{
/* 向下的箭头 */
content:"▼";
position: absolute;
z-index: 1;
/* 防止箭头覆盖选项列表 */
top: 0;
right: 0;
box-sizing:border-box;
height: 100%;
width: 2em;
padding-top:.1em;
border-left:.2em solid #000;
border-radius: 0 .1em .1em 0;
background-color: #000;
color:#FFF;
text-align: center;
}
/* 选中或者focus的样式 */
.select .active,.select:focus{
outline: none;
box-shadow: 0 0 3px 1px #227755;
}
.select .value{
display: inline-block;
width: 100%;
/* 确保item的宽度不会超过父元素的宽度 */
overflow: hidden;
vertical-align: top;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
/* 显示列表部分 */
.select .list{
position: absolute;
top:100%;
left:0;
z-index: 2;
list-style: none;
margin: 0;
padding: 0;
box-sizing: border-box;
min-width: 100%;
max-height: 10em;
overflow-y:auto;
overflow-x: hidden;
border:.2em solid #000;
border-top-width:.1em;
border-radius:0 0 .4em .4em;
box-shadow: 0 .2em .4em rgba(0,0,0,.4);
background:#f0f0f0;
}
.select .option{
padding:.2em .3em;
}
.select .highlight{
background-color: #000;
color:#fff;
}
/* 列表隐藏的时候 */
.select .list .hidden{
max-height: 0;
visibility: hidden;
}
</style>
<div class="select" tabindex="0">
<!--用来显示当前选的值 -->
<span class="value">Apple</span>
<ul class="list">
<li class="option">Apple</li>
<li class="option">Orange</li>
<li class="option">Banana</li>
</ul>
</div>
部分
1 .在浏览器中,js是一种不可靠技术
1 .用户关掉:可能性很小
2 .脚本没有加载,移动端,网络不好的地方非常常见
3 .脚本和第三方脚本冲突。用户使用的跟踪脚本和一些书签工具引发
4 .脚本和浏览器的拓展冲突
5 .用户老旧的浏览器
2 .在使用自定义部件前,还需要添加一个标准的select元素。向下兼容
缺陷
1 .如果有滚动的话,鼠标上下键移动到最底边,没有自动发生滚动。
2 .ant design虽然也会有联动,但是超出一屏也是有问题的
总结
1 .tabindex的使用,这里tabindex=-1可以保证原生组件永远不会获得焦点。而且还能保证当用户使用键盘和鼠标时,我们的自定义组件能够获得焦点
2 .语义化
真正的语义化
1 .虽然我们做出来的已经看起来是一个选择框了,但是从浏览器角度来看并不是,所以辅助技术并不能明白这是一个选择框。
2 .ARIA技术:是一组用来扩展HTML的属性集,可以让我们更好地描述角色,状态和属性,就像我们刚才设计的元素是他试图传递的原生元素一样
3 .role属性:接受一个值,定义了元素的用途。每一个role定义了自己的需求和行为,在这里面我们使用listbox这一个role
4 .window.aria.start()我还以为这是一个方法,直接调用浏览器的api就可以了,原来还有乾坤
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.widget select,.no-widget .select{
position: absolute;
left: -5000em;
/* 隐藏的操作都是这种left设置的很远,这是为啥呢,和其他不一样的区别是什么呢 */
height: 0;
overflow:hidden;
}
.select{
position: relative;
/* 先创建一个上下文定位 */
display: inline-block;
/* 组件变成文本流,还能调整大小 */
/* 美化部分 */
font-size: 0.625em;
box-sizing: border-box;
/* 向下的箭头需要一些额外的空间 */
padding: .1em 2.5em .2em .5em;
width: 10em;
border:.2em solid #000;
border-radius: .4em;
box-shadow: 0 .1em .2em rgba(0,0,0,.45);
background:#F0F0F0;
user-select: none;
}
.select:after{
/* 向下的箭头 */
content:"▼";
position: absolute;
z-index: 1;
/* 防止箭头覆盖选项列表 */
top: 0;
right: 0;
box-sizing:border-box;
height: 100%;
width: 2em;
padding-top:.1em;
border-left:.2em solid #000;
border-radius: 0 .1em .1em 0;
background-color: #000;
color:#FFF;
text-align: center;
}
/* 选中或者focus的样式 */
.select .active,.select:focus{
outline: none;
box-shadow: 0 0 3px 1px #227755;
}
.select .value{
display: inline-block;
width: 100%;
/* 确保item的宽度不会超过父元素的宽度 */
overflow: hidden;
vertical-align: top;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
/* 显示列表部分 */
.select .list{
position: absolute;
top:100%;
left:0;
z-index: 2;
list-style: none;
margin: 0;
padding: 0;
box-sizing: border-box;
min-width: 100%;
max-height: 10em;
overflow-y:auto;
overflow-x: hidden;
border:.2em solid #000;
border-top-width:.1em;
border-radius:0 0 .4em .4em;
box-shadow: 0 .2em .4em rgba(0,0,0,.4);
background:#f0f0f0;
}
.select .option{
padding:.2em .3em;
}
.select .highlight{
background-color: #000;
color:#fff;
}
/* 列表隐藏的时候 */
.select .list.hidden{
max-height: 0;
visibility: hidden;
}
</style>
</head>
<body>
<form action="" class="no-widget">
<select name="myFruit">
<option>Cherry</option>
<option>Lemon</option>
<option>Banana</option>
<option>Strawberry</option>
<option>Apple</option>
</select>
<!-- js没好之前先用这个 -->
<div class="select" role='listbox'>
<span class="value">Cherry</span>
<ul class="list hidden" role="presentation">
<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>
<script>
function deactiveSelect(select){
if(!select.classList.contains('active'))return
let optList=select.querySelector('.list')
optList.classList.add('hidden')
optList.classList.remove('active')
}
function activeSelect(select,selectList){
if(select.classList.contains('active'))return
selectList.forEach(deactiveSelect)
select.classList.add('active')
}
// 打开和收齐列表
function toggleList(select){
let list=select.querySelector('.list')
list.classList.toggle('hidden')
}
// 高亮一个选项
function highlightOption(select,option){
let list=select.querySelectorAll('.option')
list.forEach((other)=>{
other.classList.remove('highlight')
})
option.classList.add('highlight')
}
//点击更新值的操作
function updateValue(select,index){
const nativeWidget=select.previousElementSibling
const value=select.querySelector('.value')
const optionList=select.querySelectorAll('.option')
optionList.forEach((option)=>{
option.setAttribute('aria-selected','false')
})
optionList[index].setAttribute('aria-selected','true')
nativeWidget.selectedIndex=index
value.innerHTML=optionList[index].innerHTML
highlightOption(select,optionList[index])
}
function getIndex(select){
let nativeWidget=select.previousElementSibling;
return nativeWidget.selectedIndex
}
window.addEventListener('load',function(){
let form=document.querySelector('form')
form.classList.remove('no-widget')
form.classList.add('widget')
// 绑定js
let selectList=document.querySelectorAll('.select')
selectList.forEach((select)=>{
const optionList=select.querySelectorAll('.option')
//选到每一个list
const selectedIndex=getIndex(select)
select.tabIndex=0
// 让自定义的节点可以获得焦点
select.previousElementSibling.tabIndex=-1
// 让原生组件取消焦点
updateValue(select,selectedIndex)
optionList.forEach((option,index)=>{
option.addEventListener('mouseover',()=>{
highlightOption(select,option)
})
option.addEventListener('click',(event)=>{
updateValue(select,index)
})
})
select.addEventListener('click',(event)=>{
toggleList(select)
})
select.addEventListener('focus',()=>{
activeSelect(select,selectList)
})
select.addEventListener('blur',()=>{
deactiveSelect(select)
})
select.addEventListener('keyup',(event)=>{
// 添加键盘事件
let len=optionList.length;
let index=getIndex(select)
if(event.keyCode===40&&index<len-1){
index++
}
if(event.keyCode===38&&index>0){
index--
}
updateValue(select,index)
})
})
})
</script>
</body>
</html>
网友评论