
本文详细介绍了在cxjs应用中,如何解决默认onwheel事件的被动监听限制,从而成功阻止其默认行为。通过利用onref属性获取dom元素引用,并结合cx/util中的addEventlistenerwithoptions方法,我们可以灵活地添加非被动式(active)的wheel事件监听器,确保e.preventdefault()功能正常生效,实现对滚轮事件的精确控制。
理解Cxjs中的滚轮事件限制
在CxJS应用中,以及其底层框架react中,为了优化滚动性能,诸如onWheel、onTouchStart等触摸和滚轮事件默认被浏览器注册为“被动(passive)”事件监听器。这意味着当这些事件触发时,浏览器不会等待事件处理函数执行完毕,而是会立即处理滚动。这种机制显著提升了页面的滚动流畅度,但也带来了一个重要的限制:在被动事件监听器中调用event.preventDefault()将无效,并且在开发模式下可能会抛出警告或错误,提示“Unable to preventDefault inside passive event listener”。
当我们的应用场景需要阻止页面的默认滚动行为(例如,实现自定义的缩放、平移或特定的滚动区域)时,默认的onWheel事件监听器就无法满足需求。为了实现对滚轮事件的完全控制并允许阻止其默认行为,我们需要显式地注册一个非被动式(active)的滚轮事件监听器。
实现非被动式滚轮事件监听
CxJS提供了一种灵活且健壮的方法来解决这一限制,即结合使用组件的onRef属性和cx/util模块中的addEventListenerWithOptions方法。
1. 获取目标DOM元素的引用
由于我们无法直接通过JSX属性(如<div onWheel={…}>)来配置事件的passive状态,我们需要获取到目标DOM元素的实际引用,然后在其上添加原生的事件监听器。CxJS组件的onRef属性正是为此目的而设计。它接受一个回调函数作为值,当组件渲染并其对应的DOM元素可用时,该DOM元素会作为参数传递给这个回调函数。
<div onRef='addElementListener'> {/* 此div内的内容 */} </div>
在这里,addElementListener将是我们在CxJS组件类中定义的一个方法,它会在div元素被创建并挂载到DOM时被调用。
2. 使用addEventListenerWithOptions添加非被动式监听器
cx/util模块提供了一个名为addEventListenerWithOptions的实用函数,它封装了原生Element.prototype.addEventListener,并允许我们方便地传递事件选项,包括passive状态。
首先,您需要在组件文件中导入这个函数:
import { addEventListenerWithOptions } from 'cx/util';
接下来,在您的CxJS组件类中定义addElementListener方法。这个方法将负责在接收到DOM元素时,添加我们的非被动式滚轮事件监听器,并确保其生命周期得到妥善管理。
// 假设这是您的CxJS组件类内部 class MyComponent extends Widget { // 用于存储取消订阅函数的变量,以便在组件更新或卸载时清理旧的监听器 unsubscribeScroll: () => void; /** * onRef回调函数,当目标DOM元素可用时被调用。 * @param {Element} el - 目标DOM元素。 */ addElementListener(el: Element) { // 在添加新的监听器之前,如果存在旧的监听器,先将其移除。 // 这对于处理组件更新或重新渲染的情况至关重要,防止重复添加和内存泄漏。 if (this.unsubscribeScroll) { this.unsubscribeScroll(); } // 如果元素不存在(例如,组件正在卸载),则直接返回 if (!el) { return; } // 使用addEventListenerWithOptions添加非被动式滚轮事件监听器 this.unsubscribeScroll = addEventListenerWithOptions( el, // 第一个参数:目标DOM元素 'wheel', // 第二个参数:事件类型,注意是原生DOM事件名称 'wheel' (e: WheelEvent) => { // 第三个参数:事件处理函数 e.preventDefault(); // 在这里,preventDefault()将正常工作,阻止默认滚动 // 在此处添加您的自定义滚轮逻辑,例如: // console.log('滚轮事件触发,阻止了默认滚动。DeltaY:', e.deltaY); // 根据e.deltaY的值实现自定义缩放、滚动或其他交互 }, { passive: false } // 第四个参数:事件选项,关键是设置 passive: false ); } /** * 组件生命周期方法:在组件即将被销毁时调用。 * 确保在此处移除事件监听器,以防止内存泄漏。 */ onDestroy() { if (this.unsubscribeScroll) { this.unsubscribeScroll(); } } }
在上述代码中:
- 我们声明了一个unsubscribeScroll变量来存储addEventListenerWithOptions返回的取消订阅函数。这个函数在被调用时会移除对应的事件监听器,对于管理事件监听器的生命周期至关重要。
- 在addElementListener方法内部,我们首先检查并移除任何可能存在的旧监听器,以妥善处理组件的更新和重新渲染。
- addEventListenerWithOptions的第三个参数是我们的事件处理函数,其中我们可以安全地调用e.preventDefault()。
- 最关键的是第四个参数 { passive: false },它明确告诉浏览器这是一个非被动式监听器,允许我们阻止默认行为。
- onDestroy生命周期方法用于确保当组件被销毁时,事件监听器也会被正确移除,避免潜在的内存泄漏。
完整示例
将上述代码片段整合到一个完整的CxJS组件中,即可实现一个带有非被动式滚轮事件监听器的div。
import { Widget, VDOM } from 'cx/ui'; import { addEventListenerWithOptions } from 'cx/util'; // 定义一个CxJS组件,用于演示非被动式滚轮事件 class CustomScrollDiv extends Widget { // 用于存储取消订阅函数的变量 unsubscribeScroll: () => void; /** * onRef回调函数,当目标DOM元素可用时被调用。 * @param {Element} el - 目标DOM元素。 */ addElementListener(el: Element) { // 清理旧的监听器,防止重复添加和内存泄漏 if (this.unsubscribeScroll) { this.unsubscribeScroll(); } // 如果元素不存在(例如,组件正在卸载),则直接返回 if (!el) { return; } // 添加非被动式滚轮事件监听器 this.unsubscribeScroll = addEventListenerWithOptions( el, 'wheel', (e: WheelEvent) => { e.preventDefault(); // 阻止默认的页面滚动行为 console.log('自定义滚轮事件触发,阻止了默认滚动。DeltaY:', e.deltaY); // 在这里添加您的自定义逻辑,例如: // if (e.deltaY < 0) { // // 向上滚动逻辑 // } else { // // 向下滚动逻辑 // } }, { passive: false } // 关键选项:将事件设置为非被动式 ); } /** * 组件生命周期方法:在组件即将被销毁时调用。 * 确保在此处移除事件监听器,以防止内存泄漏。 */ onDestroy() { if (this.unsubscribeScroll) { this.unsubscribeScroll(); } } render() { // 渲染一个带有onRef属性的div return ( <div onRef='addElementListener' // 将addElementListener方法绑定到div的onRef属性 style={{ width: '300px', height: '200px', border: '2px solid #4CAF50', overflow: 'auto', // 确保该div本身有滚动条以观察效果 padding: '20px', backgroundColor: '#e8f5e9', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }} > <p>请在此绿色边框区域内滚动鼠标滚轮。您会发现页面的默认滚动行为被阻止,同时控制台会输出滚轮事件信息。</p> <p>内容1</p><p>内容2</p><p>内容3</p><p>内容4</p><p>内容5</p> <p>内容6</p><p>内容7</p><p>内容8</p><p>内容9</p><p>内容10</p> <p>内容11</p><p>内容12</p><p>内容13</p><p>内容14</p><p>内容15</p> </div> ); } } // 您可以在您的CxJS应用中像这样使用这个组件: // <CustomScrollDiv />
注意事项与最佳实践
- 清理监听器是关键: 始终确保在组件卸载时,或者在onRef回调因组件重新渲染而再次被调用时,移除旧的事件监听器。addEventListenerWithOptions返回的函数就是为此目的而设计。不清理监听器会导致内存泄漏和潜在的意外行为。
- 事件类型: 在addEventListenerWithOptions中,您应该使用原生DOM事件名称(例如’wheel’),而不是JSX属性名称(例如’onWheel’)。
- 性能考量: 默认将事件设置为被动是为了优化性能。只有当您确实需要阻止默认行为时,才应使用{ passive: false }。过度使用非被动事件可能会对页面的滚动性能产生负面影响,因为它强制浏览器等待您的事件处理函数执行完毕,才能进行滚动。
- 浏览器兼容性: 现代浏览器普遍支持passive选项。对于一些旧版浏览器,passive选项可能会被忽略,preventDefault()会正常工作(但可能导致滚动卡顿)。CxJS的addEventListenerWithOptions已经考虑了这些兼容性。
总结
通过巧妙地利用CxJS的onRef属性来获取DOM元素引用,并结合cx/util模块中强大的addEventListenerWithOptions方法,我们可以有效地解决onWheel事件的被动监听限制。这种方法赋予了开发者在需要时精确控制滚


