refactor(stage): 重构mask,解除对stageCore对象的依赖,同时减少暴露的对象,并重构部分大函数为小函数

This commit is contained in:
oceanzhu 2022-10-23 15:11:39 +08:00 committed by roymondchen
parent 638e996516
commit 9787a9e379
3 changed files with 142 additions and 90 deletions

View File

@ -62,6 +62,7 @@ export default class StageCore extends EventEmitter {
public isContainer: IsContainer; public isContainer: IsContainer;
private canSelect: CanSelect; private canSelect: CanSelect;
private pageResizeObserver: ResizeObserver | null = null;
constructor(config: StageCoreConfig) { constructor(config: StageCoreConfig) {
super(); super();
@ -76,7 +77,7 @@ export default class StageCore extends EventEmitter {
this.containerHighlightType = config.containerHighlightType; this.containerHighlightType = config.containerHighlightType;
this.renderer = new StageRender(config.runtimeUrl, this.render); this.renderer = new StageRender(config.runtimeUrl, this.render);
this.mask = new StageMask({ core: this }); this.mask = new StageMask(this.renderer.getDocument()?.documentElement);
this.dr = new StageDragResize({ core: this, container: this.mask.content, mask: this.mask }); this.dr = new StageDragResize({ core: this, container: this.mask.content, mask: this.mask });
this.multiDr = new StageMultiDragResize({ core: this, container: this.mask.content, mask: this.mask }); this.multiDr = new StageMultiDragResize({ core: this, container: this.mask.content, mask: this.mask });
this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper }); this.highlightLayer = new StageHighlight({ core: this, container: this.mask.wrapper });
@ -86,6 +87,7 @@ export default class StageCore extends EventEmitter {
}); });
this.renderer.on('page-el-update', (el: HTMLElement) => { this.renderer.on('page-el-update', (el: HTMLElement) => {
this.mask?.observe(el); this.mask?.observe(el);
this.observePageResize(el);
}); });
this.mask this.mask
@ -163,7 +165,6 @@ export default class StageCore extends EventEmitter {
public getElementsFromPoint(event: MouseEvent) { public getElementsFromPoint(event: MouseEvent) {
const { renderer, zoom } = this; const { renderer, zoom } = this;
const doc = renderer.contentWindow?.document;
let x = event.clientX; let x = event.clientX;
let y = event.clientY; let y = event.clientY;
@ -175,7 +176,7 @@ export default class StageCore extends EventEmitter {
} }
} }
return doc?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[]; return renderer.getDocument()?.elementsFromPoint(x / zoom, y / zoom) as HTMLElement[];
} }
public async getElementFromPoint(event: MouseEvent) { public async getElementFromPoint(event: MouseEvent) {
@ -223,15 +224,16 @@ export default class StageCore extends EventEmitter {
this.dr.select(el, event); this.dr.select(el, event);
if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) { if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
this.mask.intersectionObserver?.observe(el); this.mask.observerIntersection(el);
} }
this.selectedDom = el; this.selectedDom = el;
if (this.renderer.contentWindow) { const doc = this.renderer.getDocument();
removeSelectedClassName(this.renderer.contentWindow.document); if (doc) {
removeSelectedClassName(doc);
if (this.selectedDom) { if (this.selectedDom) {
addSelectedClassName(this.selectedDom, this.renderer.contentWindow.document); addSelectedClassName(this.selectedDom, doc);
} }
} }
} }
@ -257,7 +259,7 @@ export default class StageCore extends EventEmitter {
runtime?.update?.(data); runtime?.update?.(data);
// 更新配置后,需要等组件渲染更新 // 更新配置后,需要等组件渲染更新
setTimeout(() => { setTimeout(() => {
const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`); const el = this.renderer.getDocument()?.getElementById(`${config.id}`);
// 有可能dom已经重新渲染不再是原来的dom了所以这里判断id而不是判断el === this.selectedDom // 有可能dom已经重新渲染不再是原来的dom了所以这里判断id而不是判断el === this.selectedDom
if (el && el.id === this.selectedDom?.id) { if (el && el.id === this.selectedDom?.id) {
this.selectedDom = el; this.selectedDom = el;
@ -340,7 +342,7 @@ export default class StageCore extends EventEmitter {
public async addContainerHighlightClassName(event: MouseEvent, exclude: Element[]) { public async addContainerHighlightClassName(event: MouseEvent, exclude: Element[]) {
const els = this.getElementsFromPoint(event); const els = this.getElementsFromPoint(event);
const { renderer } = this; const { renderer } = this;
const doc = renderer.contentWindow?.document; const doc = renderer.getDocument();
if (!doc) return; if (!doc) return;
@ -362,18 +364,36 @@ export default class StageCore extends EventEmitter {
* *
*/ */
public destroy(): void { public destroy(): void {
const { mask, renderer, dr, highlightLayer } = this; const { mask, renderer, dr, highlightLayer, pageResizeObserver } = this;
renderer.destroy(); renderer.destroy();
mask.destroy(); mask.destroy();
dr.destroy(); dr.destroy();
highlightLayer.destroy(); highlightLayer.destroy();
pageResizeObserver?.disconnect();
this.removeAllListeners(); this.removeAllListeners();
this.container = undefined; this.container = undefined;
} }
/**
*
*/
private observePageResize(page: HTMLElement): void {
if (typeof ResizeObserver !== 'undefined') {
this.pageResizeObserver = new ResizeObserver((entries) => {
this.mask.pageResize(entries);
if (this.dr.moveable) {
this.dr.updateMoveable();
}
});
this.pageResizeObserver.observe(page);
}
}
/** /**
* stageRender供其回调 * stageRender供其回调
*/ */
@ -386,7 +406,7 @@ export default class StageCore extends EventEmitter {
private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> { private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') { if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
const el = this.renderer.contentWindow?.document.getElementById(`${idOrEl}`); const el = this.renderer.getDocument()?.getElementById(`${idOrEl}`);
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`); if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
return el; return el;
} }

View File

@ -23,8 +23,6 @@ import { createDiv, injectStyle } from '@tmagic/utils';
import { Mode, MouseButton, ZIndex } from './const'; import { Mode, MouseButton, ZIndex } from './const';
import Rule from './Rule'; import Rule from './Rule';
import type StageCore from './StageCore';
import type { StageMaskConfig } from './types';
import { getScrollParent, isFixedParent, isMoveableButton } from './util'; import { getScrollParent, isFixedParent, isMoveableButton } from './util';
const wrapperClassName = 'editor-mask-wrapper'; const wrapperClassName = 'editor-mask-wrapper';
@ -71,9 +69,7 @@ const createWrapper = (): HTMLDivElement => {
export default class StageMask extends Rule { export default class StageMask extends Rule {
public content: HTMLDivElement = createContent(); public content: HTMLDivElement = createContent();
public wrapper: HTMLDivElement; public wrapper: HTMLDivElement;
public core: StageCore;
public page: HTMLElement | null = null; public page: HTMLElement | null = null;
public pageScrollParent: HTMLElement | null = null;
public scrollTop = 0; public scrollTop = 0;
public scrollLeft = 0; public scrollLeft = 0;
public width = 0; public width = 0;
@ -82,12 +78,14 @@ export default class StageMask extends Rule {
public wrapperWidth = 0; public wrapperWidth = 0;
public maxScrollTop = 0; public maxScrollTop = 0;
public maxScrollLeft = 0; public maxScrollLeft = 0;
public intersectionObserver: IntersectionObserver | null = null;
public isMultiSelectStatus: Boolean = false; public isMultiSelectStatus: Boolean = false;
private mode: Mode = Mode.ABSOLUTE; private mode: Mode = Mode.ABSOLUTE;
private pageResizeObserver: ResizeObserver | null = null; private pageScrollParent: HTMLElement | null = null;
private intersectionObserver: IntersectionObserver | null = null;
private wrapperResizeObserver: ResizeObserver | null = null; private wrapperResizeObserver: ResizeObserver | null = null;
private renderEl?: HTMLElement;
/** /**
* *
* @param event * @param event
@ -96,35 +94,17 @@ export default class StageMask extends Rule {
this.emit('highlight', event); this.emit('highlight', event);
}, throttleTime); }, throttleTime);
constructor(config: StageMaskConfig) { constructor(renderEl: HTMLElement | undefined) {
const wrapper = createWrapper(); const wrapper = createWrapper();
super(wrapper); super(wrapper);
this.wrapper = wrapper; this.wrapper = wrapper;
this.core = config.core; this.renderEl = renderEl;
this.content.addEventListener('mousedown', this.mouseDownHandler); this.initContentEventListener();
this.wrapper.appendChild(this.content); this.wrapper.appendChild(this.content);
this.content.addEventListener('wheel', this.mouseWheelHandler);
this.content.addEventListener('mousemove', this.highlightHandler);
this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase()); this.initMultiSelectEvent();
const ctrl = isMac ? 'meta' : 'ctrl';
KeyController.global.keydown(ctrl, (e) => {
e.inputEvent.preventDefault();
this.isMultiSelectStatus = true;
});
// ctrl+tab切到其他窗口需要将多选状态置为false
KeyController.global.on('blur', () => {
this.isMultiSelectStatus = false;
});
KeyController.global.keyup(ctrl, (e) => {
e.inputEvent.preventDefault();
this.isMultiSelectStatus = false;
});
} }
public setMode(mode: Mode) { public setMode(mode: Mode) {
@ -140,63 +120,37 @@ export default class StageMask extends Rule {
} }
/** /**
* * mask蒙层所在的wrapper大小变
* @description mask的大小 * @description
* @param page Dom节点 * @param page Dom节点
*/ */
public observe(page: HTMLElement): void { public observe(page: HTMLElement): void {
if (!page) return; if (!page) return;
this.page = page; this.page = page;
this.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null; this.initObserverIntersection();
this.pageResizeObserver?.disconnect(); this.initObserverWrapper();
this.wrapperResizeObserver?.disconnect(); }
this.intersectionObserver?.disconnect();
if (typeof IntersectionObserver !== 'undefined') { /**
this.intersectionObserver = new IntersectionObserver( * mask大小
(entries) => { * @param entries ResizeObserverEntry
entries.forEach((entry) => { */
const { target, intersectionRatio } = entry; public pageResize(entries: ResizeObserverEntry[]): void {
if (intersectionRatio <= 0) { const [entry] = entries;
this.scrollIntoView(target); const { clientHeight, clientWidth } = entry.target;
} this.setHeight(clientHeight);
this.intersectionObserver?.unobserve(target); this.setWidth(clientWidth);
});
},
{
root: this.pageScrollParent,
rootMargin: '0px',
threshold: 1.0,
},
);
}
if (typeof ResizeObserver !== 'undefined') { this.scroll();
this.pageResizeObserver = new ResizeObserver((entries) => { }
const [entry] = entries;
const { clientHeight, clientWidth } = entry.target;
this.setHeight(clientHeight);
this.setWidth(clientWidth);
this.scroll(); /**
if (this.core.dr.moveable) { *
this.core.dr.updateMoveable(); * @param el
} */
}); public observerIntersection(el: HTMLElement): void {
this.intersectionObserver?.observe(el);
this.pageResizeObserver.observe(page);
this.wrapperResizeObserver = new ResizeObserver((entries) => {
const [entry] = entries;
const { clientHeight, clientWidth } = entry.target;
this.wrapperHeight = clientHeight;
this.wrapperWidth = clientWidth;
this.setMaxScrollLeft();
this.setMaxScrollTop();
});
this.wrapperResizeObserver.observe(this.wrapper);
}
} }
/** /**
@ -228,13 +182,89 @@ export default class StageMask extends Rule {
this.content?.remove(); this.content?.remove();
this.page = null; this.page = null;
this.pageScrollParent = null; this.pageScrollParent = null;
this.pageResizeObserver?.disconnect();
this.wrapperResizeObserver?.disconnect(); this.wrapperResizeObserver?.disconnect();
this.content.removeEventListener('mouseleave', this.mouseLeaveHandler); this.content.removeEventListener('mouseleave', this.mouseLeaveHandler);
super.destroy(); super.destroy();
} }
/**
* content的事件监听
*/
private initContentEventListener(): void {
this.content.addEventListener('mousedown', this.mouseDownHandler);
this.content.addEventListener('wheel', this.mouseWheelHandler);
this.content.addEventListener('mousemove', this.highlightHandler);
this.content.addEventListener('mouseleave', this.mouseLeaveHandler);
}
/**
*
*/
private initMultiSelectEvent(): void {
const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
const ctrl = isMac ? 'meta' : 'ctrl';
KeyController.global.keydown(ctrl, (e) => {
e.inputEvent.preventDefault();
this.isMultiSelectStatus = true;
});
// ctrl+tab切到其他窗口需要将多选状态置为false
KeyController.global.on('blur', () => {
this.isMultiSelectStatus = false;
});
KeyController.global.keyup(ctrl, (e) => {
e.inputEvent.preventDefault();
this.isMultiSelectStatus = false;
});
}
/**
* 使
*/
private initObserverIntersection(): void {
this.pageScrollParent = getScrollParent(this.page as HTMLElement) || this.renderEl || null;
this.intersectionObserver?.disconnect();
if (typeof IntersectionObserver !== 'undefined') {
this.intersectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const { target, intersectionRatio } = entry;
if (intersectionRatio <= 0) {
this.scrollIntoView(target);
}
this.intersectionObserver?.unobserve(target);
});
},
{
root: this.pageScrollParent,
rootMargin: '0px',
threshold: 1.0,
},
);
}
}
/**
* mask的容器大小变化
*/
private initObserverWrapper(): void {
this.wrapperResizeObserver?.disconnect();
if (typeof ResizeObserver !== 'undefined') {
this.wrapperResizeObserver = new ResizeObserver((entries) => {
const [entry] = entries;
const { clientHeight, clientWidth } = entry.target;
this.wrapperHeight = clientHeight;
this.wrapperWidth = clientWidth;
this.setMaxScrollLeft();
this.setMaxScrollTop();
});
this.wrapperResizeObserver.observe(this.wrapper);
}
}
private scroll() { private scroll() {
this.fixScrollValue(); this.fixScrollValue();
@ -319,8 +349,6 @@ export default class StageMask extends Rule {
const targetClassList = (event.target as HTMLDivElement).classList; const targetClassList = (event.target as HTMLDivElement).classList;
console.log(targetClassList);
// 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为 // 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为
if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) { if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) {
return; return;

View File

@ -97,6 +97,10 @@ export default class StageRender extends EventEmitter {
}); });
}; };
public getDocument(): Document | undefined {
return this.contentWindow?.document;
}
/** /**
* *
*/ */