From e0d7bf0f6ddf78106703f587cf09df0ded8bae53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=92=AC=E8=BD=AE=E7=8C=AB?= <10928033@qq.com> Date: Wed, 26 Oct 2022 22:11:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=8D=E5=AE=8C=E7=BE=8E=E7=9A=84?= =?UTF-8?q?=E6=97=8B=E8=BD=AC=E7=BC=A9=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/center-panel/index.vue | 197 +++++++++++---- .../components/handle-panel/index.vue | 9 + src/store/global/index.ts | 4 + src/store/global/types.ts | 5 + src/utils/index.ts | 46 ++++ src/utils/scale-core.ts | 225 ++++++++++++++++++ src/utils/types.ts | 4 + 7 files changed, 445 insertions(+), 45 deletions(-) create mode 100644 src/utils/scale-core.ts create mode 100644 src/utils/types.ts diff --git a/src/components/webtopo-svgedit/components/center-panel/index.vue b/src/components/webtopo-svgedit/components/center-panel/index.vue index e9ca796..1c88b80 100644 --- a/src/components/webtopo-svgedit/components/center-panel/index.vue +++ b/src/components/webtopo-svgedit/components/center-panel/index.vue @@ -105,7 +105,17 @@ IDoneJson } from '../../../../store/global/types'; import { useSvgEditLayoutStore } from '../../../../store/svgedit-layout'; - import { randomString } from '../../../../utils'; + import { getCenterPoint, randomString } from '../../../../utils'; + import { + calculateBottom, + calculateLeft, + calculateLeftBottom, + calculateLeftTop, + calculateRight, + calculateRightBottom, + calculateRightTop, + calculateTop + } from '../../../../utils/scale-core'; import HandlePanel from '../handle-panel/index.vue'; // import HandlePanel from '../handle-panel/index.vue'; const globalStore = useGlobalStore(); @@ -159,6 +169,8 @@ e.preventDefault(); }; const onSvgMouseDown = (select_item: IDoneJson, index: number, e: MouseEvent) => { + console.log(172, e); + e.preventDefault(); e.cancelBubble = true; //鼠标在画布上的组件按下记录选中的组件信息和鼠标位置信息等 @@ -192,62 +204,144 @@ //有选中组件 移动组件 globalStore.handle_svg_info.info.x = globalStore.mouse_info.new_position_x; globalStore.handle_svg_info.info.y = globalStore.mouse_info.new_position_y; + globalStore.handle_svg_info.info.client = { + x: clientX, + y: clientY + }; globalStore.intention = EGlobalStoreIntention.Move; } else if (globalStore.intention == EGlobalStoreIntention.MoveCanvas) { //移动画布 svgEditLayoutStore.center_offset.x = globalStore.mouse_info.new_position_x; svgEditLayoutStore.center_offset.y = globalStore.mouse_info.new_position_y; } else if (globalStore.intention === EGlobalStoreIntention.Zoom) { + if (!globalStore.handle_svg_info) { + return; + } + //当前鼠标坐标 + const curPositon = { + x: e.clientX, + y: e.clientY + }; + let new_length = { + width: 0, + height: 0, + is_old_width: false, + is_old_height: false + }; + if (globalStore.scale_info.type === EScaleInfoType.TopLeft) { + new_length = calculateLeftTop( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate + ); + } else if (globalStore.scale_info.type === EScaleInfoType.TopRight) { + new_length = calculateRightTop( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate + ); + } else if (globalStore.scale_info.type === EScaleInfoType.BottomRight) { + new_length = calculateRightBottom( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate + ); + } else if (globalStore.scale_info.type === EScaleInfoType.BottomLeft) { + new_length = calculateLeftBottom( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate + ); + } else if (globalStore.scale_info.type === EScaleInfoType.TopCenter) { + new_length = calculateTop( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate, + globalStore.handle_svg_info.info.client + ); + } else if (globalStore.scale_info.type === EScaleInfoType.Right) { + new_length = calculateRight( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate, + globalStore.handle_svg_info.info.client + ); + } else if (globalStore.scale_info.type === EScaleInfoType.BottomCenter) { + new_length = calculateBottom( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate, + globalStore.handle_svg_info.info.client + ); + } else if (globalStore.scale_info.type === EScaleInfoType.Left) { + new_length = calculateLeft( + curPositon, + globalStore.scale_info.symmetric_point, + globalStore.handle_svg_info.info.rotate, + globalStore.handle_svg_info.info.client + ); + } + console.log('坐标', globalStore.scale_info.symmetric_point, curPositon, new_length); + //缩放 - const move_length_x = - globalStore.scale_info.type === EScaleInfoType.TopLeft || - globalStore.scale_info.type === EScaleInfoType.Left || - globalStore.scale_info.type === EScaleInfoType.BottomLeft - ? globalStore.mouse_info.new_position_x - globalStore.mouse_info.now_position_x - : globalStore.scale_info.type === EScaleInfoType.TopRight || - globalStore.scale_info.type === EScaleInfoType.Right || - globalStore.scale_info.type === EScaleInfoType.BottomRight - ? globalStore.mouse_info.now_position_x - globalStore.mouse_info.new_position_x - : 0; - const move_length_y = - globalStore.scale_info.type === EScaleInfoType.TopLeft || - globalStore.scale_info.type === EScaleInfoType.TopCenter || - globalStore.scale_info.type === EScaleInfoType.TopRight - ? globalStore.mouse_info.new_position_y - globalStore.mouse_info.now_position_y - : globalStore.scale_info.type === EScaleInfoType.BottomLeft || - globalStore.scale_info.type === EScaleInfoType.BottomCenter || - globalStore.scale_info.type === EScaleInfoType.BottomRight - ? globalStore.mouse_info.now_position_y - globalStore.mouse_info.new_position_y - : 0; + // const move_length_x = + // globalStore.scale_info.type === EScaleInfoType.TopLeft || + // globalStore.scale_info.type === EScaleInfoType.Left || + // globalStore.scale_info.type === EScaleInfoType.BottomLeft + // ? -(newTopLeftPoint.x - globalStore.mouse_info.now_position_x) + // : globalStore.scale_info.type === EScaleInfoType.TopRight || + // globalStore.scale_info.type === EScaleInfoType.Right || + // globalStore.scale_info.type === EScaleInfoType.BottomRight + // ? globalStore.mouse_info.now_position_x - newTopLeftPoint.x + // : 0; + // const move_length_y = + // globalStore.scale_info.type === EScaleInfoType.TopLeft || + // globalStore.scale_info.type === EScaleInfoType.TopCenter || + // globalStore.scale_info.type === EScaleInfoType.TopRight + // ? newTopLeftPoint.y - globalStore.mouse_info.now_position_y + // : globalStore.scale_info.type === EScaleInfoType.BottomLeft || + // globalStore.scale_info.type === EScaleInfoType.BottomCenter || + // globalStore.scale_info.type === EScaleInfoType.BottomRight + // ? globalStore.mouse_info.now_position_y - newTopLeftPoint.y + // : 0; //算出缩放倍数 - if (globalStore.handle_svg_info) { - const scale_x = - (globalStore.handle_svg_info.info.actual_bound.width * - globalStore.scale_info.scale_times.x - - move_length_x) / - globalStore.handle_svg_info.info.actual_bound.width; - const scale_y = - (globalStore.handle_svg_info.info.actual_bound.height * - globalStore.scale_info.scale_times.y - - move_length_y) / - globalStore.handle_svg_info.info.actual_bound.height; + if (globalStore.handle_svg_info && new_length.width > 0 && new_length.height > 0) { + const scale_x = !new_length.is_old_width + ? new_length.width / + (globalStore.handle_svg_info.info.actual_bound.width * + globalStore.scale_info.scale_times.x) + : 1; + const scale_y = !new_length.is_old_height + ? new_length.height / + (globalStore.handle_svg_info.info.actual_bound.height * + globalStore.scale_info.scale_times.y) + : 1; + // const move_length_x = + // new_length.width - + // globalStore.handle_svg_info.info.actual_bound.width * + // globalStore.scale_info.scale_times.x; + // const move_length_y = + // new_length.height - + // globalStore.handle_svg_info.info.actual_bound.height * + // globalStore.scale_info.scale_times.y; if (scale_x > 0) { globalStore.handle_svg_info.info.scale_x = scale_x; - globalStore.handle_svg_info.info.x = - globalStore.scale_info.type === EScaleInfoType.TopLeft || - globalStore.scale_info.type === EScaleInfoType.Left || - globalStore.scale_info.type === EScaleInfoType.BottomLeft - ? globalStore.scale_info.scale_item_info.x + move_length_x / 2 - : globalStore.scale_info.scale_item_info.x - move_length_x / 2; + //现在是沿着中心缩放 后续这里要改下 + // globalStore.handle_svg_info.info.x = + // globalStore.scale_info.type === EScaleInfoType.TopLeft || + // globalStore.scale_info.type === EScaleInfoType.Left || + // globalStore.scale_info.type === EScaleInfoType.BottomLeft + // ? globalStore.scale_info.scale_item_info.x - move_length_x / 2 + // : globalStore.scale_info.scale_item_info.x + move_length_x / 2; } if (scale_y > 0) { globalStore.handle_svg_info.info.scale_y = scale_y; - globalStore.handle_svg_info.info.y = - globalStore.scale_info.type === EScaleInfoType.TopLeft || - globalStore.scale_info.type === EScaleInfoType.TopCenter || - globalStore.scale_info.type === EScaleInfoType.TopRight - ? globalStore.scale_info.scale_item_info.y + move_length_y / 2 - : globalStore.scale_info.scale_item_info.y - move_length_y / 2; + // globalStore.handle_svg_info.info.y = + // globalStore.scale_info.type === EScaleInfoType.TopLeft || + // globalStore.scale_info.type === EScaleInfoType.TopCenter || + // globalStore.scale_info.type === EScaleInfoType.TopRight + // ? globalStore.scale_info.scale_item_info.y - move_length_y / 2 + // : globalStore.scale_info.scale_item_info.y + move_length_y / 2; } } } else if (globalStore.intention === EGlobalStoreIntention.Rotate) { @@ -270,7 +364,7 @@ globalStore.rotate_info.angle + rotateDegreeAfter - rotateDegreeBefore; } }; - const onCanvasMouseUp = () => { + const onCanvasMouseUp = (e: MouseEvent) => { //如果鼠标不是按下状态 if (globalStore.mouse_info.state != EMouseInfoState.Down) { return; @@ -283,6 +377,19 @@ globalStore.setDoneJson(globalStore.done_json); globalStore.intention = EGlobalStoreIntention.Select; // globalStore.setHandleSvgInfo(undefined, 0); + } else if ( + globalStore.handle_svg_info?.info && + globalStore.intention == EGlobalStoreIntention.Zoom + ) { + //缩放完成后重置中点 + const newCenterPoint = getCenterPoint( + { x: e.clientX, y: e.clientY }, + globalStore.scale_info.symmetric_point + ); + //这里有bug 要先把移动的时候不根据中点放大 这里才好用 + console.log(newCenterPoint); + globalStore.handle_svg_info.info.client = newCenterPoint; + globalStore.intention = EGlobalStoreIntention.None; } else if (globalStore.intention != EGlobalStoreIntention.Select) { globalStore.intention = EGlobalStoreIntention.None; } diff --git a/src/components/webtopo-svgedit/components/handle-panel/index.vue b/src/components/webtopo-svgedit/components/handle-panel/index.vue index 940a080..7cbb143 100644 --- a/src/components/webtopo-svgedit/components/handle-panel/index.vue +++ b/src/components/webtopo-svgedit/components/handle-panel/index.vue @@ -163,6 +163,15 @@ scale_item_info: { x: props.itemInfo.x, y: props.itemInfo.y + }, + symmetric_point: { + x: + props.itemInfo.client.x + + Math.abs(clientX - props.itemInfo.client.x) * + (clientX < props.itemInfo.client.x ? 1 : -1), + y: + props.itemInfo.client.y + + Math.abs(clientY - props.itemInfo.client.y) * (clientY < props.itemInfo.client.y ? 1 : -1) } }); }; diff --git a/src/store/global/index.ts b/src/store/global/index.ts index d4add30..94b5c71 100644 --- a/src/store/global/index.ts +++ b/src/store/global/index.ts @@ -41,6 +41,10 @@ export const useGlobalStore = defineStore('global-store', { scale_item_info: { x: 0, y: 0 + }, + symmetric_point: { + x: 0, + y: 0 } }, rotate_info: { diff --git a/src/store/global/types.ts b/src/store/global/types.ts index 1307908..f17e113 100644 --- a/src/store/global/types.ts +++ b/src/store/global/types.ts @@ -68,6 +68,11 @@ export interface IScaleInfo { x: number; y: number; }; + symmetric_point: { + //缩放前缩放手柄对应组件中心坐标的对称点坐标 + x: number; + y: number; + }; } /** * 旋转信息 diff --git a/src/utils/index.ts b/src/utils/index.ts index 608e188..3e62507 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -26,3 +26,49 @@ export const isOfType = (target: unknown, prop: keyof T): target is T => { export const getCoordinateOffset = (length: number, scale: number) => { return (length / 2) * (scale - 1); }; +// 角度转弧度 +// Math.PI = 180 度 +export const angleToRadian = (angle: number) => { + return (angle * Math.PI) / 180; +}; +/** + * 计算根据圆心旋转后的点的坐标 + * @param {Object} point 旋转前的点坐标 + * @param {Object} center 旋转中心 + * @param {Number} rotate 旋转的角度 + * @return {Object} 旋转后的坐标 + * https://www.zhihu.com/question/67425734/answer/252724399 旋转矩阵公式 + */ +export const calculateRotatedPointCoordinate = ( + point: { x: number; y: number }, + center: { x: number; y: number }, + rotate: number +) => { + /** + * 旋转公式: + * 点a(x, y) + * 旋转中心c(x, y) + * 旋转后点n(x, y) + * 旋转角度θ tan ?? + * nx = cosθ * (ax - cx) - sinθ * (ay - cy) + cx + * ny = sinθ * (ax - cx) + cosθ * (ay - cy) + cy + */ + + return { + x: + (point.x - center.x) * Math.cos(angleToRadian(rotate)) - + (point.y - center.y) * Math.sin(angleToRadian(rotate)) + + center.x, + y: + (point.x - center.x) * Math.sin(angleToRadian(rotate)) + + (point.y - center.y) * Math.cos(angleToRadian(rotate)) + + center.y + }; +}; +// 求两点之间的中点坐标 +export const getCenterPoint = (p1: { x: number; y: number }, p2: { x: number; y: number }) => { + return { + x: p1.x + (p2.x - p1.x) / 2, + y: p1.y + (p2.y - p1.y) / 2 + }; +}; diff --git a/src/utils/scale-core.ts b/src/utils/scale-core.ts new file mode 100644 index 0000000..ae93d23 --- /dev/null +++ b/src/utils/scale-core.ts @@ -0,0 +1,225 @@ +import { calculateRotatedPointCoordinate, getCenterPoint } from '.'; +import { IScalePoint } from './types'; +/** + * 左上角缩放 + * @param curPositon 按住的缩放按钮的坐标 + * @param symmetricPoint 缩放前对称点的坐标 + * @param rotate 旋转角度 + * @returns + */ +export const calculateLeftTop = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number +) => { + //新的中心点坐标 + const newCenterPoint = getCenterPoint(curPositon, symmetricPoint); + console.log('left中心坐标', newCenterPoint); + const newTopLeftPoint = calculateRotatedPointCoordinate(curPositon, newCenterPoint, -rotate); + const newBottomRightPoint = calculateRotatedPointCoordinate( + symmetricPoint, + newCenterPoint, + -rotate + ); + + return { + width: newBottomRightPoint.x - newTopLeftPoint.x, + height: newBottomRightPoint.y - newTopLeftPoint.y, + is_old_width: false, + is_old_height: false + }; +}; +/** + * 右上角缩放 + * @param curPositon 按住的缩放按钮的坐标 + * @param symmetricPoint 缩放前对称点的坐标 + * @param rotate 旋转角度 + * @returns + */ +export const calculateRightTop = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number +) => { + const newCenterPoint = getCenterPoint(curPositon, symmetricPoint); + const newTopRightPoint = calculateRotatedPointCoordinate(curPositon, newCenterPoint, -rotate); + const newBottomLeftPoint = calculateRotatedPointCoordinate( + symmetricPoint, + newCenterPoint, + -rotate + ); + + return { + width: newTopRightPoint.x - newBottomLeftPoint.x, + height: newBottomLeftPoint.y - newTopRightPoint.y, + is_old_width: false, + is_old_height: false + }; +}; +/** + * 右下角缩放 + * @param curPositon 按住的缩放按钮的坐标 + * @param symmetricPoint 缩放前对称点的坐标 + * @param rotate 旋转角度 + * @returns + */ +export const calculateRightBottom = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number +) => { + const newCenterPoint = getCenterPoint(curPositon, symmetricPoint); + const newTopLeftPoint = calculateRotatedPointCoordinate(symmetricPoint, newCenterPoint, -rotate); + const newBottomRightPoint = calculateRotatedPointCoordinate(curPositon, newCenterPoint, -rotate); + + return { + width: newBottomRightPoint.x - newTopLeftPoint.x, + height: newBottomRightPoint.y - newTopLeftPoint.y, + is_old_width: false, + is_old_height: false + }; +}; +/** + * 左下角缩放 + * @param curPositon 按住的缩放按钮的坐标 + * @param symmetricPoint 缩放前对称点的坐标 + * @param rotate 旋转角度 + * @returns + */ +export const calculateLeftBottom = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number +) => { + const newCenterPoint = getCenterPoint(curPositon, symmetricPoint); + const newTopRightPoint = calculateRotatedPointCoordinate(symmetricPoint, newCenterPoint, -rotate); + const newBottomLeftPoint = calculateRotatedPointCoordinate(curPositon, newCenterPoint, -rotate); + + return { + width: newTopRightPoint.x - newBottomLeftPoint.x, + height: newBottomLeftPoint.y - newTopRightPoint.y, + is_old_width: false, + is_old_height: false + }; +}; + +export const calculateTop = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number, + curPoint: IScalePoint +) => { + // 由于用户拉伸时是以任意角度拉伸的,所以在求得旋转前的坐标时,只取 y 坐标(这里的 x 坐标可能是任意值),x 坐标用 curPoint 的。 + // 这个中心点(第二个参数)用 curPoint, center, symmetricPoint 都可以,只要他们在一条直线上就行 + const rotatedcurPositon = calculateRotatedPointCoordinate(curPositon, curPoint, -rotate); + console.log('top中心坐标', rotatedcurPositon); + + // 算出旋转前 y 坐标,再用 curPoint 的 x 坐标,重新计算它们旋转后对应的坐标 + const rotatedTopMiddlePoint = calculateRotatedPointCoordinate( + { + x: curPoint.x, + y: rotatedcurPositon.y + }, + curPoint, + rotate + ); + + // 用旋转后的坐标和对称点算出新的高度(勾股定理) + const newHeight = Math.sqrt( + (rotatedTopMiddlePoint.x - symmetricPoint.x) ** 2 + + (rotatedTopMiddlePoint.y - symmetricPoint.y) ** 2 + ); + return { + width: 1, + height: Math.round(newHeight), + is_old_width: true, + is_old_height: false + }; +}; + +export const calculateRight = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number, + curPoint: IScalePoint +) => { + const rotatedcurPositon = calculateRotatedPointCoordinate(curPositon, curPoint, -rotate); + const rotatedRightMiddlePoint = calculateRotatedPointCoordinate( + { + x: rotatedcurPositon.x, + y: curPoint.y + }, + curPoint, + rotate + ); + + const newWidth = Math.sqrt( + (rotatedRightMiddlePoint.x - symmetricPoint.x) ** 2 + + (rotatedRightMiddlePoint.y - symmetricPoint.y) ** 2 + ); + + return { + width: Math.round(newWidth), + height: 1, + is_old_width: false, + is_old_height: true + }; +}; + +export const calculateBottom = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number, + curPoint: IScalePoint +) => { + const rotatedcurPositon = calculateRotatedPointCoordinate(curPositon, curPoint, -rotate); + const rotatedBottomMiddlePoint = calculateRotatedPointCoordinate( + { + x: curPoint.x, + y: rotatedcurPositon.y + }, + curPoint, + rotate + ); + + const newHeight = Math.sqrt( + (rotatedBottomMiddlePoint.x - symmetricPoint.x) ** 2 + + (rotatedBottomMiddlePoint.y - symmetricPoint.y) ** 2 + ); + + return { + width: 1, + height: Math.round(newHeight), + is_old_width: true, + is_old_height: false + }; +}; + +export const calculateLeft = ( + curPositon: IScalePoint, + symmetricPoint: IScalePoint, + rotate: number, + curPoint: IScalePoint +) => { + const rotatedcurPositon = calculateRotatedPointCoordinate(curPositon, curPoint, -rotate); + const rotatedLeftMiddlePoint = calculateRotatedPointCoordinate( + { + x: rotatedcurPositon.x, + y: curPoint.y + }, + curPoint, + rotate + ); + + const newWidth = Math.sqrt( + (rotatedLeftMiddlePoint.x - symmetricPoint.x) ** 2 + + (rotatedLeftMiddlePoint.y - symmetricPoint.y) ** 2 + ); + + return { + width: Math.round(newWidth), + height: 1, + is_old_width: false, + is_old_height: true + }; +}; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..03b15d7 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,4 @@ +export interface IScalePoint { + x: number; + y: number; +}