背景
在网站中搭建了一个图片瀑布流加载,默认的滚轮事件是当鼠标在内容中滚动,整个页面也会随着一起滚动。所以很自然的会想到要加上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;
文档还说:除了wheel,mousewheel, touchstart and touchmove这些事件也会有相同问题;
分析
当{passive:true,},但是仍然调用preventDefault(),浏览器会报错:
也可以用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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。