From 9787a9e3795960d7d7e6b77644e3c7ab89c3051c Mon Sep 17 00:00:00 2001 From: oceanzhu Date: Sun, 23 Oct 2022 15:11:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(stage):=20=E9=87=8D=E6=9E=84mask?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E9=99=A4=E5=AF=B9stageCore=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E7=9A=84=E4=BE=9D=E8=B5=96=EF=BC=8C=E5=90=8C=E6=97=B6=E5=87=8F?= =?UTF-8?q?=E5=B0=91=E6=9A=B4=E9=9C=B2=E7=9A=84=E5=AF=B9=E8=B1=A1=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E9=87=8D=E6=9E=84=E9=83=A8=E5=88=86=E5=A4=A7=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E4=B8=BA=E5=B0=8F=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/stage/src/StageCore.ts | 42 +++++-- packages/stage/src/StageMask.ts | 186 +++++++++++++++++------------- packages/stage/src/StageRender.ts | 4 + 3 files changed, 142 insertions(+), 90 deletions(-) diff --git a/packages/stage/src/StageCore.ts b/packages/stage/src/StageCore.ts index bef1abf9..78af7fec 100644 --- a/packages/stage/src/StageCore.ts +++ b/packages/stage/src/StageCore.ts @@ -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 { 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; } diff --git a/packages/stage/src/StageMask.ts b/packages/stage/src/StageMask.ts index 3208f2e4..c1fd9901 100644 --- a/packages/stage/src/StageMask.ts +++ b/packages/stage/src/StageMask.ts @@ -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(); + this.initObserverIntersection(); + this.initObserverWrapper(); + } - 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大小 + * @param entries ResizeObserverEntry,获取页面最新大小 + */ + public pageResize(entries: ResizeObserverEntry[]): void { + const [entry] = entries; + const { clientHeight, clientWidth } = entry.target; + this.setHeight(clientHeight); + this.setWidth(clientWidth); - if (typeof ResizeObserver !== 'undefined') { - this.pageResizeObserver = new ResizeObserver((entries) => { - const [entry] = entries; - const { clientHeight, clientWidth } = entry.target; - this.setHeight(clientHeight); - this.setWidth(clientWidth); + this.scroll(); + } - 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; diff --git a/packages/stage/src/StageRender.ts b/packages/stage/src/StageRender.ts index b3580a61..fb8c9b4a 100644 --- a/packages/stage/src/StageRender.ts +++ b/packages/stage/src/StageRender.ts @@ -97,6 +97,10 @@ export default class StageRender extends EventEmitter { }); }; + public getDocument(): Document | undefined { + return this.contentWindow?.document; + } + /** * 销毁实例 */