React下拉组件键盘导航问题:方向键触发窗口滚动而非列表滚动
解决React下拉组件方向键触发页面滚动的问题
问题说明
基于React开发的带键盘导航下拉组件,通过自定义useKeyPress Hook实现了上下方向键切换列表选中状态、回车键选中对应项的功能,但按下上下方向键时会触发整个页面窗口滚动,希望仅让下拉菜单的<ul>区域滚动,同时保留原有键盘交互功能。
原有代码
自定义useKeyPress Hook
export const useKeyPress = function (targetKey: string) { const [keyPressed, setKeyPressed] = React.useState(false); function downHandler({ key }: { key: string }) { if (key === targetKey) { setKeyPressed(true); } } const upHandler = ({ key }: { key: string }) => { if (key === targetKey) { setKeyPressed(false); } }; React.useEffect(() => { window.addEventListener("keydown", downHandler); window.addEventListener("keyup", upHandler); return () => { window.removeEventListener("keydown", downHandler); window.removeEventListener("keyup", upHandler); }; }); return keyPressed; };
组件使用代码
const downPress = useKeyPress("ArrowDown"); const upPress = useKeyPress("ArrowUp"); const enterPress = useKeyPress("Enter"); useEffect(() => { if (countryDataInfo.length && downPress) { setCursor(prevState => prevState < countryDataInfo.length - 1 ? prevState + 1 : prevState ); } }, [downPress]); useEffect(() => { if (countryDataInfo.length && upPress) { setCursor(prevState => (prevState > 0 ? prevState - 1 : prevState)); } }, [upPress]); useEffect(() => { if (countryDataInfo.length && enterPress) { setSelected(countryDataInfo[cursor]); } }, [cursor, enterPress]);
<ul className={isDropdown ? "simple-phone-input-sri198-dropdown active" : "simple-phone-input-sri198-dropdown"} > {search && <div className="simple-phone-input-sri198-search-container"> <input placeholder={searchPlaceholder} {...searchProps} onChange={onSearchHandler} /> {showSearchIcon && <div className="simple-phone-input-sri198-search-icon"> {searchIconComponent ?? ( <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 48 48"><g fill="#616161"><path d="m29.175 31.99l2.828-2.827l12.019 12.019l-2.828 2.827z" /><circle cx="20" cy="20" r="16" /></g><path fill="#37474F" d="m32.45 35.34l2.827-2.828l8.696 8.696l-2.828 2.828z" /><circle cx="20" cy="20" r="13" fill="#64B5F6" /><path fill="#BBDEFB" d="M26.9 14.2c-1.7-2-4.2-3.2-6.9-3.2s-5.2 1.2-6.9 3.2c-.4.4-.3 1.1.1 1.4c.4.4 1.1.3 1.4-.1C16 13.9 17.9 13 20 13s4 .9 5.4 2.5c.2.2.5.4.8.4c.2 0 .5-.1.6-.2c.4-.4.4-1.1.1-1.5z" /></svg> )} </div> } </div> } {countryDataInfo.length === 0 && <div className="simple-phone-input-sri198-not-found"> {searchNotFound} </div> } {countryDataInfo.map((item, i) => ( <li key={i} onClick={() => handleSelected(item)} className={i === cursor ? "active" : ""}> <img src={`https://cdn.jsdelivr.net/gh/siamahnaf198/country-flags@main/img/${item.countryCode}.svg`} alt={item.country} width="20px" /> <span className="simple-phone-input-sri198-dropdown-text">{item.country}</span> <span className="simple-phone-input-sri198-dropdown-country-code">{item.callingCode}</span> </li> ))} </ul>
解决方案
1. 阻止页面默认滚动行为
修改useKeyPress Hook,支持传入回调函数处理按键事件,仅在下拉展开时阻止默认滚动行为,避免影响页面其他键盘操作:
export const useKeyPress = function (targetKey: string, onKeyDown?: (e: KeyboardEvent) => void) { const [keyPressed, setKeyPressed] = React.useState(false); function downHandler(e: KeyboardEvent) { if (e.key === targetKey) { setKeyPressed(true); onKeyDown?.(e); } } const upHandler = ({ key }: { key: string }) => { if (key === targetKey) { setKeyPressed(false); } }; React.useEffect(() => { window.addEventListener("keydown", downHandler); window.addEventListener("keyup", upHandler); return () => { window.removeEventListener("keydown", downHandler); window.removeEventListener("keyup", upHandler); }; }, [onKeyDown]); return keyPressed; };
在组件中使用时传入回调:
const downPress = useKeyPress("ArrowDown", (e) => { if (isDropdown) e.preventDefault(); }); const upPress = useKeyPress("ArrowUp", (e) => { if (isDropdown) e.preventDefault(); });
2. 让选中项自动滚动到下拉可视区域
通过React Ref获取下拉容器,切换选中项时让对应<li>滚动到容器可视范围内:
首先在组件中创建Ref:
import { useRef } from 'react'; const dropdownRef = useRef<HTMLUListElement>(null);
给下拉<ul>绑定Ref:
<ul ref={dropdownRef} className={isDropdown ? "simple-phone-input-sri198-dropdown active" : "simple-phone-input-sri198-dropdown"} > {/* 下拉内容 */} </ul>
修改上下键处理逻辑,添加滚动操作:
useEffect(() => { if (countryDataInfo.length && downPress) { setCursor(prev => { const newCursor = prev < countryDataInfo.length - 1 ? prev + 1 : prev; if (dropdownRef.current) { const activeLi = dropdownRef.current.querySelectorAll('li')[newCursor]; activeLi?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } return newCursor; }); } }, [downPress]); useEffect(() => { if (countryDataInfo.length && upPress) { setCursor(prev => { const newCursor = prev > 0 ? prev - 1 : prev; if (dropdownRef.current) { const activeLi = dropdownRef.current.querySelectorAll('li')[newCursor]; activeLi?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } return newCursor; }); } }, [upPress]);
3. 配置下拉容器滚动样式
给下拉容器添加CSS样式,设置固定最大高度并开启垂直滚动:
.simple-phone-input-sri198-dropdown.active { max-height: 300px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #ccc transparent; } /* Webkit浏览器滚动条美化 */ .simple-phone-input-sri198-dropdown.active::-webkit-scrollbar { width: 6px; } .simple-phone-input-sri198-dropdown.active::-webkit-scrollbar-thumb { background-color: #ccc; border-radius: 3px; }
修改后,按下上下方向键时仅触发下拉容器内部滚动,页面不会跟随滚动,同时保留了键盘切换选中项、回车选中的功能。
内容的提问来源于stack exchange,提问作者Simon Santana
相关产品推荐
相关产品推荐

