You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 09:25:13