用过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} {item.enterpriseName} <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> </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;
}
}
}