美文网首页
构建表单小组件之Select

构建表单小组件之Select

作者: skoll | 来源:发表于2022-04-09 22:47 被阅读0次

    简介

    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>
    

    相关文章

      网友评论

          本文标题:构建表单小组件之Select

          本文链接:https://www.haomeiwen.com/subject/gczlsrtx.html