mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +08:00
refactor(stage): 重构mask,解除对stageCore对象的依赖,同时减少暴露的对象,并重构部分大函数为小函数
This commit is contained in:
parent
638e996516
commit
9787a9e379
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -97,6 +97,10 @@ export default class StageRender extends EventEmitter {
|
||||
});
|
||||
};
|
||||
|
||||
public getDocument(): Document | undefined {
|
||||
return this.contentWindow?.document;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实例
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user