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;
private canSelect: CanSelect;
private pageResizeObserver: ResizeObserver | null = null;
constructor(config: StageCoreConfig) {
super();
@ -76,7 +77,7 @@ export default class StageCore extends EventEmitter {
this.containerHighlightType = config.containerHighlightType;
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.multiDr = new StageMultiDragResize({ core: this, container: this.mask.content, mask: this.mask });
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.mask?.observe(el);
this.observePageResize(el);
});
this.mask
@ -163,7 +165,6 @@ export default class StageCore extends EventEmitter {
public getElementsFromPoint(event: MouseEvent) {
const { renderer, zoom } = this;
const doc = renderer.contentWindow?.document;
let x = event.clientX;
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) {
@ -223,15 +224,16 @@ export default class StageCore extends EventEmitter {
this.dr.select(el, event);
if (this.config.autoScrollIntoView || el.dataset.autoScrollIntoView) {
this.mask.intersectionObserver?.observe(el);
this.mask.observerIntersection(el);
}
this.selectedDom = el;
if (this.renderer.contentWindow) {
removeSelectedClassName(this.renderer.contentWindow.document);
const doc = this.renderer.getDocument();
if (doc) {
removeSelectedClassName(doc);
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);
// 更新配置后,需要等组件渲染更新
setTimeout(() => {
const el = this.renderer.contentWindow?.document.getElementById(`${config.id}`);
const el = this.renderer.getDocument()?.getElementById(`${config.id}`);
// 有可能dom已经重新渲染不再是原来的dom了所以这里判断id而不是判断el === this.selectedDom
if (el && el.id === this.selectedDom?.id) {
this.selectedDom = el;
@ -340,7 +342,7 @@ export default class StageCore extends EventEmitter {
public async addContainerHighlightClassName(event: MouseEvent, exclude: Element[]) {
const els = this.getElementsFromPoint(event);
const { renderer } = this;
const doc = renderer.contentWindow?.document;
const doc = renderer.getDocument();
if (!doc) return;
@ -362,18 +364,36 @@ export default class StageCore extends EventEmitter {
*
*/
public destroy(): void {
const { mask, renderer, dr, highlightLayer } = this;
const { mask, renderer, dr, highlightLayer, pageResizeObserver } = this;
renderer.destroy();
mask.destroy();
dr.destroy();
highlightLayer.destroy();
pageResizeObserver?.disconnect();
this.removeAllListeners();
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供其回调
*/
@ -386,7 +406,7 @@ export default class StageCore extends EventEmitter {
private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
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}的元素`);
return el;
}

View File

@ -23,8 +23,6 @@ import { createDiv, injectStyle } from '@tmagic/utils';
import { Mode, MouseButton, ZIndex } from './const';
import Rule from './Rule';
import type StageCore from './StageCore';
import type { StageMaskConfig } from './types';
import { getScrollParent, isFixedParent, isMoveableButton } from './util';
const wrapperClassName = 'editor-mask-wrapper';
@ -71,9 +69,7 @@ const createWrapper = (): HTMLDivElement => {
export default class StageMask extends Rule {
public content: HTMLDivElement = createContent();
public wrapper: HTMLDivElement;
public core: StageCore;
public page: HTMLElement | null = null;
public pageScrollParent: HTMLElement | null = null;
public scrollTop = 0;
public scrollLeft = 0;
public width = 0;
@ -82,12 +78,14 @@ export default class StageMask extends Rule {
public wrapperWidth = 0;
public maxScrollTop = 0;
public maxScrollLeft = 0;
public intersectionObserver: IntersectionObserver | null = null;
public isMultiSelectStatus: Boolean = false;
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 renderEl?: HTMLElement;
/**
*
* @param event
@ -96,35 +94,17 @@ export default class StageMask extends Rule {
this.emit('highlight', event);
}, throttleTime);
constructor(config: StageMaskConfig) {
constructor(renderEl: HTMLElement | undefined) {
const wrapper = createWrapper();
super(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.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());
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;
});
this.initMultiSelectEvent();
}
public setMode(mode: Mode) {
@ -140,63 +120,37 @@ export default class StageMask extends Rule {
}
/**
*
* @description mask的大小
* mask蒙层所在的wrapper大小变
* @description
* @param page Dom节点
*/
public observe(page: HTMLElement): void {
if (!page) return;
this.page = page;
this.pageScrollParent = getScrollParent(page) || this.core.renderer.contentWindow?.document.documentElement || null;
this.pageResizeObserver?.disconnect();
this.wrapperResizeObserver?.disconnect();
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,
},
);
this.initObserverIntersection();
this.initObserverWrapper();
}
if (typeof ResizeObserver !== 'undefined') {
this.pageResizeObserver = new ResizeObserver((entries) => {
/**
* mask大小
* @param entries ResizeObserverEntry
*/
public pageResize(entries: ResizeObserverEntry[]): void {
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();
}
});
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);
}
/**
*
* @param el
*/
public observerIntersection(el: HTMLElement): void {
this.intersectionObserver?.observe(el);
}
/**
@ -228,13 +182,89 @@ export default class StageMask extends Rule {
this.content?.remove();
this.page = null;
this.pageScrollParent = null;
this.pageResizeObserver?.disconnect();
this.wrapperResizeObserver?.disconnect();
this.content.removeEventListener('mouseleave', this.mouseLeaveHandler);
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() {
this.fixScrollValue();
@ -319,8 +349,6 @@ export default class StageMask extends Rule {
const targetClassList = (event.target as HTMLDivElement).classList;
console.log(targetClassList);
// 如果单击多选选中区域,则不需要再触发选中了,而可能是拖动行为
if (!this.isMultiSelectStatus && targetClassList.contains('moveable-area')) {
return;

View File

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