diff --git a/packages/core/src/App.ts b/packages/core/src/App.ts index f6cee2ed..b904acdd 100644 --- a/packages/core/src/App.ts +++ b/packages/core/src/App.ts @@ -107,6 +107,16 @@ class App extends EventEmitter { Object.entries(styleObj).forEach(([key, value]) => { if (key === 'backgroundImage') { value && (results[key] = fillBackgroundImage(value)); + } else if (key === 'transform' && typeof value !== 'string') { + results[key] = Object.entries(value as Record) + .map(([transformKey, transformValue]) => { + let defaultValue = 0; + if (transformKey === 'scale') { + defaultValue = 1; + } + return `${transformKey}(${transformValue || defaultValue})`; + }) + .join(' '); } else if (!whiteList.includes(key) && value && /^[-]?[0-9]*[.]?[0-9]*$/.test(value)) { results[key] = `${value / 100}rem`; } else { diff --git a/packages/editor/src/utils/props.ts b/packages/editor/src/utils/props.ts index 5cf7524e..1fbc1c5a 100644 --- a/packages/editor/src/utils/props.ts +++ b/packages/editor/src/utils/props.ts @@ -161,6 +161,21 @@ export const fillConfig = (config: FormConfig = []) => [ }, ], }, + { + type: 'fieldset', + legend: '变形', + name: 'transform', + items: [ + { + name: 'rotate', + text: '旋转角度', + }, + { + name: 'scale', + text: '缩放', + }, + ], + }, ], }, ], diff --git a/packages/stage/src/StageDragResize.ts b/packages/stage/src/StageDragResize.ts index 95307aae..ac36fed8 100644 --- a/packages/stage/src/StageDragResize.ts +++ b/packages/stage/src/StageDragResize.ts @@ -203,49 +203,41 @@ export default class StageDragResize extends EventEmitter { this.bindResizeEvent(); this.bindDragEvent(); + this.bindRotateEvent(); + this.bindScaleEvent(); } private bindResizeEvent(): void { if (!this.moveable) throw new Error('moveable 为初始化'); const frame = { - translate: [0, 0], + left: 0, + top: 0, }; this.moveable .on('resizeStart', (e) => { - if (e.dragStart) { - const rect = this.moveable!.getRect(); - const offset = getAbsolutePosition(e.target as HTMLElement, rect); - e.dragStart.set([offset.left, offset.top]); - } + if (!this.target) return; + + this.dragStatus = ActionStatus.START; + this.moveableHelper?.onResizeStart(e); + + frame.top = this.target.offsetTop; + frame.left = this.target.offsetLeft; }) - .on('resize', ({ width, height, drag }) => { + .on('resize', (e) => { + const { width, height, drag } = e; if (!this.moveable || !this.target || !this.dragEl) return; const { beforeTranslate } = drag; - frame.translate = beforeTranslate; this.dragStatus = ActionStatus.ING; + this.moveableHelper?.onResize(e); + this.target.style.width = `${width}px`; this.target.style.height = `${height}px`; - this.dragEl.style.width = `${width}px`; - this.dragEl.style.height = `${height}px`; - - // 流式布局 - if (this.mode === Mode.SORTABLE) { - this.dragEl.style.top = `${beforeTranslate[1]}px`; - this.target.style.top = `0px`; - return; - } - - this.dragEl.style.left = `${beforeTranslate[0]}px`; - this.dragEl.style.top = `${beforeTranslate[1]}px`; - - const offset = getAbsolutePosition(this.target, { left: beforeTranslate[0], top: beforeTranslate[1] }); - - this.target.style.left = `${offset.left}px`; - this.target.style.top = `${offset.top}px`; + this.target.style.left = `${frame.left + beforeTranslate[0]}px`; + this.target.style.top = `${frame.top + beforeTranslate[1]}px`; }) .on('resizeEnd', () => { this.dragStatus = ActionStatus.END; @@ -311,6 +303,60 @@ export default class StageDragResize extends EventEmitter { }); } + private bindRotateEvent(): void { + if (!this.moveable) throw new Error('moveable 为初始化'); + + this.moveable + .on('rotateStart', (e) => { + this.dragStatus = ActionStatus.START; + this.moveableHelper?.onRotateStart(e); + }) + .on('rotate', (e) => { + if (!this.target || !this.dragEl) return; + this.dragStatus = ActionStatus.ING; + this.moveableHelper?.onRotate(e); + const frame = this.moveableHelper?.getFrame(e.target); + this.target.style.transform = frame?.toCSSObject().transform || ''; + }) + .on('rotateEnd', (e) => { + this.dragStatus = ActionStatus.END; + const frame = this.moveableHelper?.getFrame(e.target); + this.emit('update', { + el: this.target, + style: { + transform: frame?.get('transform'), + }, + }); + }); + } + + private bindScaleEvent(): void { + if (!this.moveable) throw new Error('moveable 为初始化'); + + this.moveable + .on('scaleStart', (e) => { + this.dragStatus = ActionStatus.START; + this.moveableHelper?.onScaleStart(e); + }) + .on('scale', (e) => { + if (!this.target || !this.dragEl) return; + this.dragStatus = ActionStatus.ING; + this.moveableHelper?.onScale(e); + const frame = this.moveableHelper?.getFrame(e.target); + this.target.style.transform = frame?.toCSSObject().transform || ''; + }) + .on('scaleEnd', (e) => { + this.dragStatus = ActionStatus.END; + const frame = this.moveableHelper?.getFrame(e.target); + this.emit('update', { + el: this.target, + style: { + transform: frame?.get('transform'), + }, + }); + }); + } + private sort(): void { if (!this.target || !this.ghostEl) throw new Error('未知错误'); const { top } = this.ghostEl.getBoundingClientRect(); @@ -331,14 +377,15 @@ export default class StageDragResize extends EventEmitter { } private update(isResize = false): void { - const rect = this.moveable!.getRect(); + if (!this.target) return; + const offset = - this.mode === Mode.SORTABLE ? { left: 0, top: 0 } : getAbsolutePosition(this.target as HTMLElement, rect); + this.mode === Mode.SORTABLE ? { left: 0, top: 0 } : { left: this.target.offsetLeft, top: this.target.offsetTop }; const left = this.calcValueByFontsize(offset.left); const top = this.calcValueByFontsize(offset.top); - const width = this.calcValueByFontsize(rect.width); - const height = this.calcValueByFontsize(rect.height); + const width = this.calcValueByFontsize(this.target.clientWidth); + const height = this.calcValueByFontsize(this.target.clientHeight); this.emit('update', { el: this.target, @@ -369,15 +416,16 @@ export default class StageDragResize extends EventEmitter { } private updateDragEl(el: HTMLElement) { - const { width, height } = el.getBoundingClientRect(); const offset = getOffset(el); + const { transform } = getComputedStyle(el); this.dragEl.style.cssText = ` position: absolute; + transform: ${transform}; left: ${offset.left}px; top: ${offset.top}px; - width: ${width}px; - height: ${height}px; + width: ${el.clientWidth}px; + height: ${el.clientHeight}px; z-index: ${ZIndex.DRAG_EL}; `; @@ -415,6 +463,8 @@ export default class StageDragResize extends EventEmitter { dragArea: false, draggable: true, resizable: true, + scalable: false, + rotatable: false, snappable: isAbsolute || isFixed, snapGap: isAbsolute || isFixed, snapThreshold: 5, diff --git a/packages/stage/src/TargetCalibrate.ts b/packages/stage/src/TargetCalibrate.ts index 27d8a136..3c3e8590 100644 --- a/packages/stage/src/TargetCalibrate.ts +++ b/packages/stage/src/TargetCalibrate.ts @@ -46,14 +46,15 @@ export default class TargetCalibrate extends EventEmitter { } public update(el: HTMLElement, prefix: String): HTMLElement { - const { width, height } = el.getBoundingClientRect(); const { left, top } = this.getOffset(el); + const { transform } = getComputedStyle(el); this.operationEl.style.cssText = ` position: absolute; + transform: ${transform}; left: ${left}px; top: ${top}px; - width: ${width}px; - height: ${height}px; + width: ${el.clientWidth}px; + height: ${el.clientHeight}px; `; this.operationEl.id = `${prefix}${el.id}`; @@ -65,35 +66,10 @@ export default class TargetCalibrate extends EventEmitter { } private getOffset(el: HTMLElement): Offset { - const { transform } = getComputedStyle(el); const { offsetParent } = el; - let left = el.offsetLeft; - let top = el.offsetTop; - - if (transform.indexOf('matrix') > -1) { - let a = 1; - let b = 1; - let c = 1; - let d = 1; - let e = 0; - let f = 0; - transform.replace( - /matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/, - ($0: string, $1: string, $2: string, $3: string, $4: string, $5: string, $6: string): string => { - a = +$1; - b = +$2; - c = +$3; - d = +$4; - e = +$5; - f = +$6; - return transform; - }, - ); - - left = a * left + c * top + e; - top = b * left + d * top + f; - } + const left = el.offsetLeft; + const top = el.offsetTop; if (offsetParent) { const parentOffset = this.getOffset(offsetParent as HTMLElement); diff --git a/packages/stage/src/types.ts b/packages/stage/src/types.ts index 34df4a75..c87096dd 100644 --- a/packages/stage/src/types.ts +++ b/packages/stage/src/types.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import { MoveableOptions } from 'react-moveable/declaration/types'; +import { MoveableOptions } from 'moveable'; import Core from '@tmagic/core'; import { Id, MApp, MNode } from '@tmagic/schema'; @@ -58,6 +58,7 @@ export type Rect = { width: number; height: number; } & Offset; + export interface Offset { left: number; top: number; @@ -72,10 +73,14 @@ export interface UpdateEventData { el: HTMLElement; ghostEl: HTMLElement; style: { - width: number; - height: number; + width?: number; + height?: number; left?: number; top?: number; + transform?: { + rotate?: string; + scale?: string; + }; }; } diff --git a/packages/stage/src/util.ts b/packages/stage/src/util.ts index b09b585b..4ab4c3f2 100644 --- a/packages/stage/src/util.ts +++ b/packages/stage/src/util.ts @@ -30,35 +30,10 @@ const getParents = (el: Element, relative: Element) => { }; export const getOffset = (el: HTMLElement): Offset => { - const { transform } = getComputedStyle(el); const { offsetParent } = el; - let left = el.offsetLeft; - let top = el.offsetTop; - - if (transform.indexOf('matrix') > -1) { - let a = 1; - let b = 1; - let c = 1; - let d = 1; - let e = 0; - let f = 0; - transform.replace( - /matrix\((.+), (.+), (.+), (.+), (.+), (.+)\)/, - ($0: string, $1: string, $2: string, $3: string, $4: string, $5: string, $6: string): string => { - a = +$1; - b = +$2; - c = +$3; - d = +$4; - e = +$5; - f = +$6; - return transform; - }, - ); - - left = a * left + c * top + e; - top = b * left + d * top + f; - } + const left = el.offsetLeft; + const top = el.offsetTop; if (offsetParent) { const parentOffset = getOffset(offsetParent as HTMLElement);