React中tabIndex与onBlur导致侧边栏下拉菜单提前折叠问题
解决侧边栏因下拉菜单获取焦点提前折叠的问题
嘿,我懂你的困扰——当点击侧边栏里的下拉菜单时,焦点从侧边栏移到菜单选项,直接触发了onBlur导致侧边栏折叠,用户根本来不及选选项对吧?这是因为默认的onBlur不会区分焦点是移到内部子元素还是外部,只要失去焦点就触发隐藏逻辑。
咱们可以通过判断失去焦点后,新的焦点元素是否属于侧边栏的DOM树来解决这个问题,具体步骤如下:
1. 给侧边栏添加Ref
首先给侧边栏元素加一个ref,方便后续判断焦点元素是否在它内部:
sidebarRef = React.createRef();
然后在render里把ref绑定到侧边栏容器:
<div ref={this.sidebarRef} tabIndex="0" onBlur={this.handleSidebarBlur} // 其他属性... >
2. 修改onBlur处理函数
原来的onBlur直接隐藏侧边栏,现在我们要加一层判断:只有当焦点移到侧边栏外部时才隐藏。这里需要注意,onBlur触发时,新的焦点元素还没更新到document.activeElement,所以要加个setTimeout让浏览器完成焦点切换后再判断:
handleSidebarBlur = () => { setTimeout(() => { const activeElement = document.activeElement; // 检查当前焦点元素是否在侧边栏内部 if (this.sidebarRef.current && this.sidebarRef.current.contains(activeElement)) { return; // 焦点在内部,不隐藏 } // 焦点在外部,隐藏侧边栏 this.setState({ visible: false }); }, 0); }
3. 特殊情况:下拉菜单用Portal渲染
如果你的下拉菜单是用ReactDOM.createPortal渲染到body里的(不是侧边栏的直接子元素),那contains方法检测不到,这时候可以给下拉菜单的容器加个类名,比如dropdown-portal,然后修改判断逻辑:
handleSidebarBlur = () => { setTimeout(() => { const activeElement = document.activeElement; const isInSidebar = this.sidebarRef.current?.contains(activeElement); const isInDropdown = activeElement.closest('.dropdown-portal') !== null; if (isInSidebar || isInDropdown) { return; } this.setState({ visible: false }); }, 0); }
完整代码示例
把这些整合起来,你的组件大概会是这样:
import React from 'react'; class Sidebar extends React.Component { state = { visible: true }; sidebarRef = React.createRef(); componentDidMount() { this.focusSidebar(); } componentDidUpdate() { if (this.state.visible) { this.focusSidebar(); } } focusSidebar() { this.sidebarRef.current?.focus(); } handleSidebarBlur = () => { setTimeout(() => { const activeElement = document.activeElement; if (this.sidebarRef.current && this.sidebarRef.current.contains(activeElement)) { return; } this.setState({ visible: false }); }, 0); } render() { if (!this.state.visible) return null; return ( <div ref={this.sidebarRef} tabIndex="0" onBlur={this.handleSidebarBlur} style={{ width: '200px', border: '1px solid #eee', padding: '1rem', background: '#f5f5f5' }} > <h4>我的侧边栏</h4> {/* 下拉菜单示例 */} <div style={{ margin: '1rem 0' }}> <button tabIndex="0">选择操作 ▼</button> <div style={{ marginTop: '0.5rem', border: '1px solid #ddd', padding: '0.5rem' }}> <button tabIndex="0" style={{ display: 'block', margin: '0.25rem 0' }}>新建文件</button> <button tabIndex="0" style={{ display: 'block', margin: '0.25rem 0' }}>删除文件</button> <button tabIndex="0" style={{ display: 'block', margin: '0.25rem 0' }}>重命名</button> </div> </div> </div> ); } } export default Sidebar;
这样修改后,当用户点击下拉菜单的选项时,焦点在侧边栏内部,不会触发隐藏逻辑;只有点击侧边栏以外的区域时,才会折叠侧边栏,完美解决你的问题!
内容的提问来源于stack exchange,提问作者Fluidbyte
相关产品推荐
相关产品推荐

