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

如何在Yew.rs中实现纯CSS下拉菜单的外部点击关闭功能?

如何在Yew.rs中实现点击外部或内部链接自动关闭的下拉菜单(不依赖Bootstrap JS)

纯CSS无法可靠实现「点击外部区域关闭下拉菜单」的逻辑,因为CSS没有事件监听外部点击的能力。我们可以结合Yew的DOM事件处理能力,配合Bootstrap的CSS样式来实现需求,具体方案如下:

核心实现思路

  1. 通过Yew的use_node_ref获取下拉容器的DOM节点,用于判断点击事件是否发生在菜单内部
  2. 给下拉按钮添加点击事件,切换菜单显示状态并阻止默认跳转行为
  3. 给所有下拉项添加点击事件,点击后直接关闭菜单
  4. 监听全局document的点击事件,判断点击目标是否在下拉容器外部,若是则关闭菜单

修改后的完整代码

use yew::prelude::*;
use web_sys::Event;

#[function_component(App)]
pub fn app() -> Html {
    let is_open = use_state(|| false);
    let dropdown_ref = use_node_ref();

    // 切换下拉菜单状态,阻止默认跳转
    let toggle_dropdown = {
        let is_open = is_open.clone();
        Callback::from(move |e: MouseEvent| {
            e.prevent_default();
            is_open.set(!*is_open);
        })
    };

    // 点击下拉项时关闭菜单
    let close_on_item_click = {
        let is_open = is_open.clone();
        Callback::from(move |_: MouseEvent| {
            is_open.set(false);
        })
    };

    // 监听外部点击,关闭菜单
    {
        let is_open = is_open.clone();
        let dropdown_ref = dropdown_ref.clone();
        use_effect_with_deps(move |_| {
            let callback = Callback::from(move |e: Event| {
                let target = e.target().unwrap();
                let element = target.dyn_into::<web_sys::Element>().unwrap();
                
                if let Some(dropdown) = dropdown_ref.cast::<web_sys::Element>() {
                    if !dropdown.contains(Some(&element)) {
                        is_open.set(false);
                    }
                }
            });

            // 添加全局点击事件监听
            let handler = callback.clone();
            let _ = web_sys::window()
                .unwrap()
                .document()
                .unwrap()
                .add_event_listener_with_callback("click", handler.as_ref().unchecked_ref());

            // 组件卸载时清理事件监听,避免内存泄漏
            move || {
                let _ = web_sys::window()
                    .unwrap()
                    .document()
                    .unwrap()
                    .remove_event_listener_with_callback("click", callback.as_ref().unchecked_ref());
            }
        }, ());
    }

    html! {
        <main>
            <div class="dropdown" ref={dropdown_ref}>
                <a
                    class={classes!("btn", "btn-secondary", "dropdown-toggle")}
                    href="#"
                    role="button"
                    aria-expanded={*is_open}
                    onclick={toggle_dropdown}
                >
                 {"Dropdown link"}
                </a>

                <ul class={classes!("dropdown-menu", if *is_open { "show" } else { "" })}>
                 <li><a class="dropdown-item" href="#" onclick={close_on_item_click}>{"Action"}</a></li>
                 <li><a class="dropdown-item" href="#" onclick={close_on_item_click}>{"Another action"}</a></li>
                 <li><a class="dropdown-item" href="#" onclick={close_on_item_click}>{"Something else here"}</a></li>
                </ul>
            </div>
        </main>
    }
}

关键细节说明

  • 节点引用dropdown_ref:用于获取下拉容器的DOM元素,判断点击事件的目标是否在菜单内部
  • 阻止默认跳转:下拉按钮的href="#"会触发页面滚动到顶部,通过e.prevent_default()避免该行为
  • 全局事件监听清理:使用use_effect_with_deps的返回值作为清理函数,组件卸载时移除全局点击监听,防止内存泄漏
  • 可访问性优化:动态设置aria-expanded属性,告知屏幕阅读器菜单的展开状态

内容的提问来源于stack exchange,提问作者Igor Lysenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:45:03