前端拾遗

137 阅读4分钟

封闭开发了一段时间重感冒了,本来应该整理为几篇文章,康复后发现细节已淡忘,记录为几个不同的片段吧

问题场景(简要)

  • 有一个仿 ant-design 的 Select:输入框有值时显示筛选下拉,输入为空时显示历史记录列表。
  • 如果在 Input 的 onBlur 里关闭下拉,任何在下拉内的点击都会先触发 input 的 blur 导致下拉被隐藏,从而下拉内的点击无效(例如删除按钮或选择项无法响应)。
  • 最初用的解决方案是:全局监听 document 的 mousedown(useClickAnywhere 钩子),点击不在下拉区域则关闭下拉。问题是:当页面嵌入子域 iframe 时,iframe 内的点击不会触发父页面的 document 事件,导致下拉一直保持(或不能正确关闭)。与子域协商后,他们通过 postMessage 在 iframe 内发出 click 通知,父域接收后关闭下拉,但每个子域都需配合,成本高。

解决办法

  • 利用阻止默认行为:在下拉容器最外层添加 onMouseDown={(e) => e.preventDefault()},使得点击下拉内部时不会导致输入框失去焦点(即阻止触发 input 的 blur),从而下拉内部的按钮/项点击能够正常响应;点击下拉外部仍会触发 blur,关闭下拉。
  • 示例:给按钮或下拉外层绑定 onMouseDown 并在处理函数里 e.preventDefault()。

e.preventDefault()

复盘发现下面写的我都看不懂 看上面的简要总结吧

背景:可模拟ant-design的select组件,在输入框有值显示下拉选择列表的基础上,当输入框为空的时候,显示10条之前的搜索历史记录列表

image.png 如果在Input组件的onBlur方法中,让下拉列表弹窗消失,弹窗里的任何点击事件都失效,包括删除按钮的点击事件和点击列表项。所以一开始写了一个useHook,即排除弹窗区域,点击弹窗外的任何地方,弹窗消失,在首页没有任何问题

/**
 * 自定义钩子:点击页面任何位置时触发回调函数
 * @param onClickAway - 点击页面任何位置时需要执行的回调函数
 * @param ref - React 引用对象,指向需要排除点击事件的 DOM 元素
 */
const useClickAnywhere = (onClickAway: () => void, ref: RefObject<HTMLElement>): void => {
  useEffect(() => {
    // 点击事件处理函数
    const handleClickOutside = (event: MouseEvent): void => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        onClickAway();
      }
    };

    // 绑定点击事件
    document.addEventListener('mousedown', handleClickOutside);

    // 组件卸载时移除事件监听
    return (): void => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [onClickAway, ref]);
};

export default useClickAnywhere;

可是子域嵌的Iframe,无法监听Iframe的点击事件, 导致子域弹窗一直尴尬的在那儿。和其中一位子域研发沟通后,他给我传了click事件,message监听到之后,处理弹窗消失。但是每个子域都需要这样做,每个子域研发不同,有点麻烦,e.preventDefault()出现了!!

e.preventDefault() 可以阻止某些特定的默认行为,但并不能阻止所有事件的触发。

  • e.preventDefault() 可以用于阻止 onPressEnter 事件的默认行为,例如在表单中按下回车键时阻止表单提交。
  • e.preventDefault() 可以用于阻止 onBlur 事件的默认行为,例如在某些情况下阻止输入框失去焦点。

所以只要在列表的最外层元素上加上onMouseDown={(e: React.MouseEvent<HTMLDivElement>) => e.preventDefault()}这段代码,就可以实现点击弹窗区域,弹窗不消失,点击弹窗之外,触发onBlur,弹窗消失

举个例子,假设我们有一个输入框和一个按钮,当点击按钮时,阻止输入框失去焦点(阻止 onBlur 事件):

import React, { useRef } from 'react';
import { Input, Button } from 'antd';

const PreventBlurExample = () => {
  const inputRef = useRef(null);

  const handleMouseDown = (e) => {
    e.preventDefault(); // 阻止按钮点击时输入框失去焦点
  };

  return (
    <div>
      <Input ref={inputRef} placeholder="Click the button to prevent blur" />
      <Button onMouseDown={handleMouseDown}>Prevent Blur</Button>
    </div>
  );
};

export default PreventBlurExample;

在这个例子中,当点击按钮时,e.preventDefault() 阻止了 onMouseDown 事件的默认行为,从而阻止了输入框的 onBlur 事件。

pointer-events: none

背景:react项目,div包了一个svg元素,div上有个onClick点击事件,本地调试没有问题,pre环境发现点击svg的边缘可以触发onClick,点击svg没有反应。

一开始的解决方法是把svg改为了Img,后来大佬推荐了pointer-events: none;

当时时间紧迫且别人维护的代码又臭又长,没太研究具体原因

猜测可能的原因:

  1. 事件传播问题:SVG元素可能会阻止事件传播到其父元素(即div),尤其是当SVG内部有自己的一些事件处理逻辑时。即事件在SVG元素上被阻止或中断了。
  2. SVG默认行为:一些SVG元素可能会有自己的默认行为,阻止了事件的传播或者处理。

解释

CSS pointer-events 属性

  • 在CSS中,pointer-events: none; 的作用是禁用元素的鼠标事件。这意味着,当你把鼠标移动到该元素上时,它不会响应任何鼠标事件(如点击、悬停等)。
  • pointer-events: none; 属性使得 SVG 元素不会拦截点击事件。这样,当你点击 SVG 元素时,事件会直接传递给它的父元素(即 div)。