BIM 中事件交互的一种架构设计
所有的事件交互其实都是那几个提供的原生事件,所以对其进行抽象,封装,形成一种统一的架构思路,对于遇到的其他功能事件交互都可以按照这种架构设计进行处理。以下以测量交互为例。
封装测量操作器基类
class OpMeasure {
app: any;
constructor(app: any) {
this.app = app;
}
onLButtonDown(event: any) {}
onMButtonDown(event: any) {}
onRButtonDown(event: any) {}
onLButtonUp(event: any) {}
onMButtonUp(event: any) {}
onRButtonUp(event: any) {}
onLButtonDownAndMove(event: any) {}
onRButtonDownAndMove(event: any) {}
onMButtonDownAndMove(event: any) {}
onNoButtonDownAndMove(event: any) {}
dispose() {}
}
这是最基本代码结构,在实际业务中可以增加更多的属性,函数。
实际的测量操作器
距离自动测量操作器
class OpMeasureAutoDistance extends OpMeasure {
constructor(app: any) {
super(app);
}
onLButtonUp(event: any) {
// TODO 业务
}
onNoButtonDownAndMove(event: any) {
// TODO 业务
}
// 创建测量结果业务,以这个为例介绍实际还有不少业务函数,这里省略。
createNewMeasure() {}
}
在距离自动测量这个功能中,鼠标交互主要只有两个,1.鼠标左键 up 后,2.鼠标没有按下的鼠标移动,所以只需要重写这两个函数即可。
构件最小间距测量操作器
class OpMeasureMinimumDistance extends OpMeasure {
constructor(app: any) {
super(app);
}
onLButtonUp(event: any) {
// TODO 业务
}
onLButtonDownAndMove(event: any) {
// TODO 业务
}
onNoButtonDownAndMove(event: any) {
// TODO 业务
}
// 创建测量结果业务,以这个为例介绍实际还有不少业务函数,这里省略。
createNewMeasure() {}
dispose() {
// TODO 特有业务
super.dispose();
}
}
在构件最小间距测量这个功能中,鼠标交互主要只有三个,1.鼠标左键 up 后,2.鼠标左键按下的移动,3.鼠标没有按下的鼠标移动,所以只需要重写这三个函数即可。
测量结果封装
测量交互过程中会产生结果,为了代码易于理解和可维护,也可以面向对象的思想进行抽象封装
class MeasureMesh {
scene: Scene;
font: Font;
material: LineBasicMaterial;
constructor(scene: Scene, font: Font) {
this.scene = scene;
this.font = font;
this.material = new LineBasicMaterial({ color: 0x9933ff });
// TODO 更多的属性
}
setMaterialColor(color: Color) {}
setVisible(visible: boolean) {}
dispose() {}
// TODO 更多的方法
}
class MeasureAutoDistanceMesh extends MeasureMesh {
constructor(scene: Scene, font: Font) {
super(scene, font);
}
createMesh(start: Vector3, end: Vector3, hasMeasureVal: boolean) {}
}
class MeasureMinimumDistanceMesh extends MeasureMesh {
constructor(scene: Scene, font: Font) {
super(scene, font);
}
createMesh(start: Vector3, end: Vector3) {}
dispose() {}
}
操作器的使用
以上的封装是交互细节,还没有涉及到外层的事件绑定,操作器的调用。所以封装以下代码
class MeasureModeGroup {
app: any;
private currentOpMeasure: any;
private measureAutoDistance: OpMeasureAutoDistance;
private measureMinimumDistance: OpMeasureMinimumDistance;
private onMouseDownHand: (event: any) => void;
private onMouseUpHand: (event: any) => void;
private onMouseMoveHand: (event: any) => void;
constructor(app: any) {
this.app = app;
this.currentOpMeasure = null;
this.measureAutoDistance = new OpMeasureAutoDistance(app);
this.measureMinimumDistance = new OpMeasureMinimumDistance(app);
this.onMouseDownHand = this.onMouseDown.bind(this);
this.onMouseUpHand = this.onMouseUp.bind(this);
this.onMouseMoveHand = this.onMouseMove.bind(this);
this.initEvent();
}
dispose() {
this.disposeEvent();
this.measureAutoDistance.dispose();
this.measureMinimumDistance.dispose();
}
initEvent() {
this.app.bimViewer.dom.addEventListener('mousedown', this.onMouseDownHand, false);
this.app.bimViewer.dom.addEventListener('mouseup', this.onMouseUpHand, false);
this.app.bimViewer.dom.addEventListener('mousemove', this.onMouseMoveHand, false);
}
disposeEvent() {
this.app.bimViewer.dom.removeEventListener('mousedown', this.onMouseDownHand, false);
this.app.bimViewer.dom.removeEventListener('mouseup', this.onMouseUpHand, false);
this.app.bimViewer.dom.removeEventListener('mousemove', this.onMouseMoveHand, false);
}
onMouseDown(event: any) {
if (!this.currentOpMeasure) return;
if (event.button === 0) {
this.currentOpMeasure.onLButtonDown(event);
} else if (event.button === 1) {
this.currentOpMeasure.onMButtonDown(event);
} else if (event.button === 2) {
this.currentOpMeasure.onRButtonDown(event);
}
}
onMouseUp(event: any) {
if (!this.currentOpMeasure) return;
if (event.button === 0) {
this.currentOpMeasure.onLButtonUp(event);
} else if (event.button === 1) {
this.currentOpMeasure.onMButtonUp(event);
} else if (event.button === 2) {
this.currentOpMeasure.onRButtonUp(event);
}
}
onMouseMove(event: any) {
if (!this.currentOpMeasure) return;
if (event.buttons & 1) {
this.currentOpMeasure.onLButtonDownAndMove(event);
} else if (event.buttons & 2) {
this.currentOpMeasure.onRButtonDownAndMove(event);
} else if (event.buttons & 4) {
this.currentOpMeasure.onMButtonDownAndMove(event);
} else {
this.currentOpMeasure.onNoButtonDownAndMove(event);
}
}
// 设置不同的测量模式
setCurrentMeasureMode(type: number) {
this.closeCurrentMeasureMode();
switch (type) {
case 1:
this.currentOpMeasure = this.measureAutoDistance;
break;
case 2:
this.currentOpMeasure = this.measureMinimumDistance;
break;
}
}
}
在我的业务中,进入测量模式后,分别有六种不同的测量,所以为了避免测量功能切换之间,频繁的 new 不同的测量对象,直接 new 所有的测量功能,在切换时,进行不同的指针指向即可。
使用方调用
// 使用方调用示例
// 经过两层封装,使用方调用时不必关心实现细节,只需new出这个对象
this.measureModeGroup = new MeasureModeGroup(this.props.app);
// 切换不同测量时,调用这个方法
this.measureModeGroup.setCurrentMeasureMode(1);
this.measureModeGroup.setCurrentMeasureMode(2);
// 退出
this.measureModeGroup.dispose();
总结
1.使用了面向对象的思想,前端是一个灵活多变的领域,基本啥都能做,编程思想既能用面向过程,也能用面向对象。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;这个思想比较好理解,符合人的一般思维,但是写好函数式编程也不是简单的事。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
所以在前端开发过程中,可以根据业务场景选择使用面向对象还是面向过程。
可以看这篇文章理解 juejin.cn/post/684490… 函数=>构造函数=>对象=>原型与原型链=>类
2.使用了前端事件,这里以 mouse 事件为例,其实用 pointer 事件更好
可以看这篇文章理解 juejin.cn/post/698727… 事件处理的多种绑定方式