在CxJS框架中,多数DOM事件可以通过模板声明方式绑定,但像onWheel这类高频且常被浏览器默认行为干扰的事件,仅靠声明式写法往往不够灵活。主动监听器指的是在widget的生命周期方法中,利用CxJS提供的API直接向真实DOM元素添加事件处理函数,从而精确控制事件捕获、阻止默认动作以及获取滚动参数。

为什么需要主动添加onWheel监听器
CxJS的模板系统允许在标签上写类似on-wheel的属性来响应滚轮,但这种方式在动态创建的widget或需要条件性监听时显得笨重。主动监听器把绑定逻辑放在controller或widget的init、mounted钩子里,可以在元素渲染完成后立刻拿到节点引用并调用原生addEventListener,避免模板解析时机问题。
另一个现实原因是浏览器的滚轮事件默认会触发页面滚动或缩放,如果不在监听器中调用preventDefault,自定义滚动区域会出现跳动。声明式绑定有时因为框架合成事件机制而延迟了preventDefault的调用,主动监听器则能同步执行,体验更顺滑。
核心API与绑定方式
CxJS的widget实例通常包含el属性指向根DOM节点,也提供attachEvent方法简化事件挂载。我们可以在afterRender或mounted阶段使用它。下面代码展示一个自定义滚动容器的widget,主动为根节点添加wheel监听并阻止默认滚动。
class ScrollBox extends cx.ui.Widget {
// 在渲染后阶段主动绑定onWheel
afterRender() {
super.afterRender();
// el为当前widget的真实DOM节点
this.attachEvent(this.el, 'wheel', this.onWheel, this);
}
// 监听器函数,this已被绑定为widget实例
onWheel(e) {
// 阻止页面默认滚动
e.preventDefault();
// 根据deltaY决定内部内容偏移
var delta = e.deltaY;
this.el.scrollTop += delta;
console.log('wheel delta:', delta);
}
// 组件销毁时解绑,防止内存泄漏
destroy() {
this.detachEvent(this.el, 'wheel', this.onWheel, this);
super.destroy();
}
}
上面例子中attachEvent是CxJS封装的方法,内部会处理浏览器兼容并自动在销毁时回收。如果使用的CxJS版本未提供该封装,也可直接用原生写法:this.el.addEventListener('wheel', this.onWheel.bind(this)),但记得在destroy里removeEventListener。
主动监听器的一个关键是作用域。事件回调里的this如果不绑定,会指向DOM元素而非widget,导致无法访问组件状态。通过attachEvent传入第三个参数this,或在addEventListener时使用bind,都能解决该问题。
与声明式on-wheel的差异对比
声明式写法通常在模板里写<div on-wheel="onWheel"/>,由框架在编译时生成绑定。它的优势是直观,但劣势是只能在静态模板存在时生效,且框架可能在事件触发后才执行用户函数,错失preventDefault的最佳时机。主动监听器则在节点存在后立刻生效,适合需要精确控制的场景。
| 维度 | 声明式on-wheel | 主动监听器 |
|---|---|---|
| 绑定时机 | 模板编译期 | 渲染后运行期 |
| 默认行为控制 | 可能延迟 | 同步可控 |
| 动态条件绑定 | 不灵活 | 逻辑内聚 |
从表中可以看出,当业务要求自定义滚动必须平滑且不能触发外层页面滚动时,主动监听器是更稳妥的方案。声明式适合简单提示类交互,比如滚轮时显示个tooltip。
完整使用示例
下面给出一个在CxJS控制器中创建widget并主动监听wheel的完整片段,包含横向滚动映射,方便直接套用。
// 控制器中定义widget配置
var cfg = {
type: 'scrollbox',
controller: {
onInit() {
// 可在控制器里集中处理数据
}
}
};
// 注册自定义widget
cx.ui.registerWidget('scrollbox', ScrollBox);
// 页面挂载
cx.dom.loadWidget(document.getElementById('app'), cfg);
// ScrollBox补充横向处理
ScrollBox.prototype.onWheel = function(e) {
e.preventDefault();
if (e.shiftKey) {
// 按住shift时横向滚动
this.el.scrollLeft += e.deltaY;
} else {
this.el.scrollTop += e.deltaY;
}
};
这段代码演示了如何把wheel事件同时服务于纵向与横向滚动,通过shiftKey做分支。由于监听器是主动添加的,任何时刻挂载到DOM都能立即工作,不必担心模板刷新导致的重复绑定。
最后提醒,使用主动监听器后务必在组件销毁阶段移除事件,否则单页应用路由切换时会累积大量无用回调,引发性能下降与错乱。CxJS的detachEvent或原生removeEventListener都能胜任,只要保证绑定与解绑使用同一引用即可。