背景

在网站中搭建了一个图片瀑布流加载,默认的滚轮事件是当鼠标在内容中滚动,整个页面也会随着一起滚动。所以很自然的会想到要加上preventDefault()来阻止页面的滚动。但是在React中,直接在元素上绑定的事件里加上event.preventDefault();一般是行得通的(https://react.dev/reference/react-dom/components/common#react...)。但是在特定的事件上是行不通的。

案例

一开始直接想到的就是在元素上直接绑定事件:

export function test() {
  function canvasRoll(event) {
    event.preventDefault();

    document.querySelect("#canvas").scrollBy({
      top: 33px,
      behavior: "smooth",
    })
  }  

  return (
    <>
      <div id="canvas" onWheel={canvasRoll}></div>
    </>
  )
}

目的是,在元素上滚动鼠标,只有元素在滚动。
实际上,整个页面如果有滚动距离,也会随着一起滚动。

原因

为什么这样的写法,event.preventDefault()不生效?
因为,在浏览器的定义中,事件绑定的参数默认是{passive: true,}

引用MDN的文档(https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/...)说法:

passive Optional

A boolean value that, if true, indicates that the function specified by listener will never call preventDefault(). If a passive listener calls preventDefault(), nothing will happen and a console warning may be generated.

If this option is not specified it defaults to false – except that in browsers other than Safari, it defaults to true for wheel, mousewheel, touchstart and touchmove events. See Using passive listeners to learn more.

文档说了:passive:true,会阻止preventDefault()生效;
文档又说:该值默认是false,但是除了Safari,其他浏览器都是true;
文档还说:除了wheelmousewheel, touchstart and touchmove这些事件也会有相同问题;

分析

{passive:true,},但是仍然调用preventDefault(),浏览器会报错:
图片.png

也可以用event.defaultPrevented;
引用MDN文档(https://developer.mozilla.org/en-US/docs/Web/API/Event/defaul...):

read-only property of the Event interface
A boolean value, where true indicates that the default user agent action was prevented, and false indicates that it was not.

解决方法

方法一

不使用React原生(https://react.dev/learn/responding-to-events#preventing-defau...)的事件绑定方式,改用Javascript原生(addEventListner)的事件绑定方式。

步骤一:建立一个useRef指向想要滚动的元素
步骤二:在合适的地方绑定事件,我通常在useEffect里组件生成的时候,并设置{passive: false,}
步骤三:在合适的地方解绑事件(removeEventListner)

完整代码:

export function test() {
  let canvasRef = useRef(null);

  useEffect(() => {
    canvasRef.current.addEventListener('wheel', canvasRoll, { passive: false, });
  }, [])

  function canvasRoll(event) {
    event.preventDefault();

    document.querySelect("#canvas").scrollBy({
      top: 33px,
      behavior: "smooth",
    })
  }  

  return (
    <>
      <div id="canvas" ref={canvasRef} onWheel={canvasRoll}></div>
    </>
  )
}

方法二

设置一个判断方法,并将所有的情况罗列出来,给出对应的操作,好像也可以避免默认事件。
比如使用元素底部距离视窗顶部的距离(getBoundingClientReact().bottom)作为判断依据:

export function test() {
  function canvasRoll(event) {
    let distance = event.target.getBoundingClientReact().bottom;

    if( distance < {一些条件} ){
      {do something}
    }else if( distance == {一些条件} ){
      {do something}
    }else if( distance > {一些条件} ){
      {do something}
    }
  }  

  return (
    <>
      <div id="canvas" onWheel={canvasRoll}></div>
    </>
  )
}

重点是要把所有的结果都罗列到,免得引起一些不想要的结果

参考

https://react.dev/learn/manipulating-the-dom-with-refs
https://github.com/facebook/react/issues/14856
https://github.com/facebook/react/issues/8968
https://stackoverflow.com/questions/57358640/cancel-wheel-event-with-e-preventdefault-in-react-event-bubbling


BreezingSummer
45 声望0 粉丝

« 上一篇
git
下一篇 »
升级npm