实现Dropdown下拉菜单监听键盘上下键选中功能-React

用过ant design的小伙伴都知道,select组件是支持联想搜索跟上下键选中的效果的,但是在项目中我们可能会遇到用select组件无法实现我们的需求的情况,比如说一个div框,里面有input,又有tag标签,在input中输入内容触发联想,然后选中其中某一个,以tag标签的形式回填到div框中(类似这种需求)。这个时候,我们可以采用ant design的dropdown组件来帮助我们实现。

getBoundingClientRect()方法可以获取元素的大小及其相对视口的位置,这样可以帮助对元素是否在可视区域内进行判断

getBoundingClientRect() 是一个在 DOM(文档对象模型)中常用的方法,它返回一个 DOMRect 对象,
该对象提供了元素的大小及其相对于视口的位置。这个方法非常有用,
特别是在需要知道元素在页面上的确切位置或尺寸时。

DOMRect 对象包含以下属性:

x:元素左上角相对于视口(viewport)的 x 坐标(包括任何滚动偏移)。
y:元素左上角相对于视口(viewport)的 y 坐标(包括任何滚动偏移)。
width:元素的宽度(包括内边距 padding,但不包括边框 border、外边距 margin 和滚动条)。
height:元素的高度(包括内边距 padding,但不包括边框 border、外边距 margin 和滚动条)。
top:元素顶部边缘相对于视口(viewport)的 y 坐标(包括任何滚动偏移)。
right:元素右边缘相对于视口(viewport)的 x 坐标(包括任何滚动偏移)。
bottom:元素底部边缘相对于视口(viewport)的 y 坐标(包括任何滚动偏移)。
left:元素左边边缘相对于视口(viewport)的 x 坐标(包括任何滚动偏移)。

页面布局代码 ,需要根据实际情况来进行调整

componentDidMount(){
    // 添加键盘事件监听  
    document.addEventListener('keydown', this.handleKeyDown);
}

// 渲染下拉项
showGroupContcatMenu = ()=>{
    const {contactSearchList, currentFocusMenuIndex} = this.state;
    return <Menu>
      {
        contactSearchList.map((item, index)=>(
          <Menu.Item id={"dropdown-menu-item-" + index} key={item.id} onClick={()=>this.handleSelectMenuItem(item)} style={{background: index == currentFocusMenuIndex ? '#fff5e6' : ''}}>
            <div>{item.name}&nbsp;&nbsp;{item.enterpriseName}&nbsp;&nbsp;<span>{item.email && `<${item.email}>`}</span></div>
          </Menu.Item>
        ))
      }
      <Menu.Item id={"dropdown-menu-item-" + contactSearchList.length} key={contactSearchList.length} style={{background: currentFocusMenuIndex == contactSearchList.length ? '#fff5e6' : ''}} onClick={()=>this.openGroupContcatIcon(null)}>搜索联系人</Menu.Item>
    </Menu>
  }


// 布局代码
<div className="tag">
  {
    selectContact.length > 0 && selectContact.map(item=>(
      <Popover key={item.id} trigger="click" 
         content={
           <Spin spinning={showPhoneLoading}>
             <span style={{userSelect:'none'}}>{showPhoneLoading?'':trueEmail}</span>
           </Spin>
         } 
         overlayClassName='contact-email-tip'
         onVisibleChange={(visible) => this.onPopoverVisibleChange(visible, item)}>
         <Tag key={item.id} closable={true} onClose={(e)=>this.handleClose(item.id,item,e,'searchGroupContcat','contact')}>{item.name}{item.hiddenEmail || item.email || item.disPlayEmail?<span>&nbsp;&nbsp;</span>:''}{item.hiddenEmail || item.email || item.disPlayEmail}</Tag>
      </Popover>
    ))
  }
  <Dropdown 
     visible={onKeywordsContact ? true : false}
     overlay={()=>this.showGroupContcatMenu()} 
     overlayClassName='bropdown-overlay-class'
     destroyPopupOnHide={true} 
  >
     <div>
        {getFieldDecorator('onKeywordsContact', {
           initialValue: '',
        })(
           <AInput 
             className="input" 
             onBlur={(e)=>this.handleBlur('onKeywordsContact')}
             onPressEnter={this.handleInputPressEnter}
             onChange={this.handleGroupContcatChange}/>
        )}
     </div>
  </Dropdown>
</div>

监听键盘响应事件的核心代码

handleKeyDown = (event) =>{
    const {onKeywordsContact, currentFocusMenuIndex, contactSearchList} = this.state
    let newCurrentFocusMenuIndex = currentFocusMenuIndex;
    if (event.key === 'ArrowUp' && onKeywordsContact) {  
      event.preventDefault(); 
      newCurrentFocusMenuIndex = currentFocusMenuIndex <= 0 ? contactSearchList.length : currentFocusMenuIndex - 1;
      this.scrollIntoViewIfNeeded(newCurrentFocusMenuIndex, contactSearchList.length);
    } else if (event.key === 'ArrowDown' && onKeywordsContact) {  
      event.preventDefault();  
      newCurrentFocusMenuIndex = currentFocusMenuIndex >= contactSearchList.length ? 0 : currentFocusMenuIndex + 1;
      this.scrollIntoViewIfNeeded(newCurrentFocusMenuIndex, contactSearchList.length);
    }  
    this.setState({
      currentFocusMenuIndex: newCurrentFocusMenuIndex // 记录当前选中高亮的元素
    }) 
  }


scrollIntoViewIfNeeded = (newCurrentFocusMenuIndex, maxLength)=>{
      const ulEle = $('.bropdown-overlay-class .ant-dropdown-menu')[0];
      const liEle = $('#dropdown-menu-item-' + newCurrentFocusMenuIndex)[0];
      const ulRect = ulEle.getBoundingClientRect();  
      const liRect = liEle.getBoundingClientRect();
      
      if(newCurrentFocusMenuIndex == 0){
        ulEle.scrollTop = 0;
      } else if(newCurrentFocusMenuIndex == maxLength){
        ulEle.scrollTop = ulEle.scrollHeight;
      } else {
        // 检查li是否在ul的上方  
        if (liRect.top < ulRect.top) {  
          // 滚动ul到li的顶部位置  
          ulEle.scrollTop = liEle.offsetTop;  
        }  
        // 检查li是否在ul的下方(这里假设我们不想滚动超过li的底部)  
        else if (liRect.bottom > ulRect.bottom) {  
          // 滚动ul到li的底部位置减去ul的高度,以确保li的底部在可视区域内  
          ulEle.scrollTop = liEle.offsetTop + liEle.offsetHeight - ulEle.offsetHeight;   
        }  
      }
    }

相关推荐

  1. Element-Plus Dropdown 菜单样式修改

    2024-06-07 05:50:04       39 阅读
  2. css实现二级导航菜单

    2024-06-07 05:50:04       52 阅读
  3. Vue 监听键盘 与 组合键

    2024-06-07 05:50:04       29 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-07 05:50:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-07 05:50:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-07 05:50:04       82 阅读
  4. Python语言-面向对象

    2024-06-07 05:50:04       91 阅读

热门阅读

  1. 互联网简史:分久必合,合久必分

    2024-06-07 05:50:04       28 阅读
  2. Docker:定义未来的软件部署

    2024-06-07 05:50:04       33 阅读
  3. overpass-api 部署(docker)

    2024-06-07 05:50:04       26 阅读
  4. ubuntu 20.04上docker 使用gpu

    2024-06-07 05:50:04       29 阅读
  5. 算法设计与分析(期末复习版3)

    2024-06-07 05:50:04       26 阅读
  6. 实验室自动化中驱动对接使用的常见通讯技术

    2024-06-07 05:50:04       32 阅读
  7. PCL Loess曲线回归拟合(二维)

    2024-06-07 05:50:04       32 阅读
  8. VL27 不重叠序列检测

    2024-06-07 05:50:04       32 阅读
  9. MySQL学习记录

    2024-06-07 05:50:04       24 阅读
  10. minio是什么?minio支持什么语言?

    2024-06-07 05:50:04       30 阅读
  11. NLP入门——数据预处理:编码规范化

    2024-06-07 05:50:04       24 阅读
  12. 运维开发详解

    2024-06-07 05:50:04       29 阅读