feat: 旋转 有bug 旋转后不能正确缩放

Re-1.0
咬轮猫 3 years ago
parent afd91e80e3
commit 473b947c73

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44C30.9566 44 37.0836 40.4483 40.6667 35.0593" stroke="#333" stroke-width="4" stroke-linecap="round"/><path d="M44 24H30" stroke="#333" stroke-width="4" stroke-linecap="round"/><circle cx="24" cy="24" r="6" fill="#333" stroke="#333" stroke-width="4"/></svg>

After

Width:  |  Height:  |  Size: 481 B

@ -26,6 +26,14 @@
v-for="(item, index) in globalStore.done_json" v-for="(item, index) in globalStore.done_json"
:key="item.id" :key="item.id"
:transform="`translate(${item.x},${item.y})rotate(0)scale(1)`" :transform="`translate(${item.x},${item.y})rotate(0)scale(1)`"
>
<g
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
item.actual_bound.y + item.actual_bound.height / 2
})rotate(${item.rotate}) scale(1) translate(${-(
item.actual_bound.x +
item.actual_bound.width / 2
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
> >
<use <use
:xlink:href="`#svg-${item.name}`" :xlink:href="`#svg-${item.name}`"
@ -74,17 +82,20 @@
<handle-panel <handle-panel
v-if=" v-if="
globalStore.handle_svg_info?.info.id == item.id && globalStore.handle_svg_info?.info.id == item.id &&
(globalStore.intention == EGlobalStoreIntention.Select || (globalStore.intention === EGlobalStoreIntention.Select ||
globalStore.intention == EGlobalStoreIntention.Zoom) globalStore.intention === EGlobalStoreIntention.Zoom ||
globalStore.intention === EGlobalStoreIntention.Rotate)
" "
:item-info="item" :item-info="item"
></handle-panel> ></handle-panel>
</g> </g>
</g> </g>
</g>
</svg> </svg>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { useConfigStore } from '../../../../store/config'; import { useConfigStore } from '../../../../store/config';
import { useGlobalStore } from '../../../../store/global'; import { useGlobalStore } from '../../../../store/global';
import { import {
@ -100,6 +111,13 @@
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
const configStore = useConfigStore(); const configStore = useConfigStore();
const svgEditLayoutStore = useSvgEditLayoutStore(); const svgEditLayoutStore = useSvgEditLayoutStore();
const cursor_style = computed(() =>
globalStore.intention == EGlobalStoreIntention.MoveCanvas
? 'grab'
: globalStore.intention == EGlobalStoreIntention.Rotate
? "url('/src/assets/icons/rotate.svg') 12 12, auto"
: 'default'
);
const dropEvent = (e: DragEvent) => { const dropEvent = (e: DragEvent) => {
if (globalStore.intention == EGlobalStoreIntention.None) { if (globalStore.intention == EGlobalStoreIntention.None) {
return; return;
@ -112,8 +130,13 @@
id: globalStore.create_svg_info.name + randomString(), id: globalStore.create_svg_info.name + randomString(),
x: e.offsetX - svgEditLayoutStore.center_offset.x, x: e.offsetX - svgEditLayoutStore.center_offset.x,
y: e.offsetY - svgEditLayoutStore.center_offset.y, y: e.offsetY - svgEditLayoutStore.center_offset.y,
client: {
x: e.clientX,
y: e.clientY
},
scale_x: 1, scale_x: 1,
scale_y: 1, scale_y: 1,
rotate: 0,
actual_bound: { actual_bound: {
x: 0, x: 0,
y: 0, y: 0,
@ -174,7 +197,7 @@
// //
svgEditLayoutStore.center_offset.x = globalStore.mouse_info.new_position_x; svgEditLayoutStore.center_offset.x = globalStore.mouse_info.new_position_x;
svgEditLayoutStore.center_offset.y = globalStore.mouse_info.new_position_y; svgEditLayoutStore.center_offset.y = globalStore.mouse_info.new_position_y;
} else if (globalStore.intention == EGlobalStoreIntention.Zoom) { } else if (globalStore.intention === EGlobalStoreIntention.Zoom) {
// //
const move_length_x = const move_length_x =
globalStore.scale_info.type === EScaleInfoType.TopLeft || globalStore.scale_info.type === EScaleInfoType.TopLeft ||
@ -227,6 +250,24 @@
: 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) {
if (!globalStore.handle_svg_info) {
return;
}
const rotateDegreeBefore =
Math.atan2(
globalStore.mouse_info.position_y - globalStore.handle_svg_info.info.client.y,
globalStore.mouse_info.position_x - globalStore.handle_svg_info.info.client.x
) /
(Math.PI / 180);
const rotateDegreeAfter =
Math.atan2(
clientY - globalStore.handle_svg_info.info.client.y,
clientX - globalStore.handle_svg_info.info.client.x
) /
(Math.PI / 180);
globalStore.handle_svg_info.info.rotate =
globalStore.rotate_info.angle + rotateDegreeAfter - rotateDegreeBefore;
} }
}; };
const onCanvasMouseUp = () => { const onCanvasMouseUp = () => {
@ -274,7 +315,7 @@
.canvas { .canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
cursor: v-bind('globalStore.intention == EGlobalStoreIntention.MoveCanvas?"grab":"default"'); cursor: v-bind('cursor_style');
} }
.svg-item-none { .svg-item-none {

@ -7,19 +7,31 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: nw-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(0).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x-offset-getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)" :x="props.itemInfo.actual_bound.x-offset-getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)"
:y="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
stroke="rgba(0,0,0,0)" stroke="rgba(0,0,0,0)"
@mousedown="onHandleMouseDown(EScaleInfoType.TopLeft,$event)" @mousedown="onHandleMouseDown(EScaleInfoType.TopLeft,$event)">
></rect </rect>
><rect <circle
:cx="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width/2"
:cy="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)-24"
:r="4"
class="rotate-circle"
@mousedown="onRotateCircleMouseDown"/>
<line
:x1="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width/2"
:y1="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
:x2="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width/2"
:y2="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)-20"
:style="{stroke:fill,'stroke-width':2}" />
<rect
id="resize_tc" id="resize_tc"
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: n-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(45).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width/2-offset" :x="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width/2-offset"
:y="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -31,7 +43,7 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: ne-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(90).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width-offset+getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)" :x="props.itemInfo.actual_bound.x+props.itemInfo.actual_bound.width-offset+getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)"
:y="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -43,7 +55,7 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: e-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(315).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x-offset-getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)" :x="props.itemInfo.actual_bound.x-offset-getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)"
:y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height*props.itemInfo.scale_y/2-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height*props.itemInfo.scale_y/2-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -55,7 +67,7 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: w-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(135).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x-offset+props.itemInfo.actual_bound.width+getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)" :x="props.itemInfo.actual_bound.x-offset+props.itemInfo.actual_bound.width+getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)"
:y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height*props.itemInfo.scale_y/2-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height*props.itemInfo.scale_y/2-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -67,7 +79,7 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: sw-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(270).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x-offset-getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)" :x="props.itemInfo.actual_bound.x-offset-getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)"
:y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height*props.itemInfo.scale_y-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height*props.itemInfo.scale_y-getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -79,7 +91,7 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: s-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(225).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x-offset+props.itemInfo.actual_bound.width/2" :x="props.itemInfo.actual_bound.x-offset+props.itemInfo.actual_bound.width/2"
:y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height+getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height+getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -91,7 +103,7 @@
width="8" width="8"
height="8" height="8"
:fill="fill" :fill="fill"
style="cursor: se-resize; vector-effect: non-scaling-stroke" :style="{cursor: getCursor(180).cursor, 'vector-effect': 'non-scaling-stroke'}"
pointer-events="all" pointer-events="all"
:x="props.itemInfo.actual_bound.x-offset+props.itemInfo.actual_bound.width+getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)" :x="props.itemInfo.actual_bound.x-offset+props.itemInfo.actual_bound.width+getCoordinateOffset(props.itemInfo.actual_bound.width,props.itemInfo.scale_x)"
:y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height+getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)" :y="props.itemInfo.actual_bound.y-offset+props.itemInfo.actual_bound.height+getCoordinateOffset(props.itemInfo.actual_bound.height,props.itemInfo.scale_y)"
@ -117,8 +129,18 @@
} }
}); });
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
const offset = ref(5); const offset = ref(4);
const fill = ref('#4F80FF'); const fill = ref('#4F80FF');
const angle_to_cursor = [
{ start: 338, end: 23, cursor: 'nw', type: EScaleInfoType.TopLeft },
{ start: 23, end: 68, cursor: 'n', type: EScaleInfoType.TopCenter },
{ start: 68, end: 113, cursor: 'ne', type: EScaleInfoType.TopRight },
{ start: 293, end: 338, cursor: 'w', type: EScaleInfoType.Left },
{ start: 113, end: 158, cursor: 'e', type: EScaleInfoType.Right },
{ start: 248, end: 293, cursor: 'sw', type: EScaleInfoType.BottomLeft },
{ start: 203, end: 248, cursor: 's', type: EScaleInfoType.BottomCenter },
{ start: 158, end: 203, cursor: 'se', type: EScaleInfoType.BottomRight }
];
const onHandleMouseDown = (type: EScaleInfoType, e: MouseEvent) => { const onHandleMouseDown = (type: EScaleInfoType, e: MouseEvent) => {
const { clientX, clientY } = e; const { clientX, clientY } = e;
e.cancelBubble = true; e.cancelBubble = true;
@ -144,5 +166,49 @@
} }
}); });
}; };
const onRotateCircleMouseDown = (e: MouseEvent) => {
const { clientX, clientY } = e;
e.cancelBubble = true;
globalStore.intention = EGlobalStoreIntention.Rotate;
globalStore.rotate_info = {
angle: props.itemInfo.rotate
};
globalStore.setMouseInfo({
state: EMouseInfoState.Down,
position_x: clientX,
position_y: clientY,
now_position_x: clientX,
now_position_y: clientY,
new_position_x: 0,
new_position_y: 0
});
};
/**
* 获取旋转之后的光标样式
* @param init_angle 初始角度 360/8=45
*/
const getCursor = (init_angle: number) => {
const now_init_angle = (init_angle + props.itemInfo.rotate) % 360;
const find_cursor = angle_to_cursor.find(
(f) => f.start <= now_init_angle && f.end > now_init_angle
);
if (!find_cursor) {
return {
cursor: 'nw-resize',
type: EScaleInfoType.TopLeft
};
}
return {
cursor: find_cursor.cursor + '-resize',
type: find_cursor.type
};
};
</script> </script>
<style scoped></style> <style scoped>
.rotate-circle {
stroke: v-bind('fill');
stroke-width: 1;
fill-opacity: 0;
cursor: url('@/assets/icons/rotate.svg') 12 12, auto;
}
</style>

@ -42,6 +42,9 @@ export const useGlobalStore = defineStore('global-store', {
x: 0, x: 0,
y: 0 y: 0
} }
},
rotate_info: {
angle: 0
} }
}; };
}, },

@ -8,13 +8,19 @@ export interface IGlobalStore {
mouse_info: IMouseInfo; mouse_info: IMouseInfo;
handle_svg_info: IHandleSvgInfo | undefined; handle_svg_info: IHandleSvgInfo | undefined;
scale_info: IScaleInfo; scale_info: IScaleInfo;
rotate_info: IRotateInfo;
} }
export interface IDoneJson extends IConfigItem { export interface IDoneJson extends IConfigItem {
id: string; id: string;
x: number; x: number;
y: number; y: number;
client: {
x: number;
y: number;
};
scale_x: number; scale_x: number;
scale_y: number; scale_y: number;
rotate: number;
actual_bound: { actual_bound: {
x: number; x: number;
y: number; y: number;
@ -28,7 +34,8 @@ export enum EGlobalStoreIntention {
Move = 'Move', Move = 'Move',
MoveCanvas = 'MoveCanvas', MoveCanvas = 'MoveCanvas',
Select = 'Select', Select = 'Select',
Zoom = 'Zoom' Zoom = 'Zoom',
Rotate = 'Rotate'
} }
export interface IMouseInfo { export interface IMouseInfo {
state: EMouseInfoState; state: EMouseInfoState;
@ -62,6 +69,12 @@ export interface IScaleInfo {
y: number; y: number;
}; };
} }
/**
*
*/
export interface IRotateInfo {
angle: number;
}
export enum EScaleInfoType { export enum EScaleInfoType {
None = '', None = '',
TopLeft = 'TopLeft', TopLeft = 'TopLeft',

@ -24,5 +24,10 @@ export default defineConfig({
// 禁用压缩 否则想要修改无状态组件的stroke或者fill会影响到预设样式 例如stroke-width // 禁用压缩 否则想要修改无状态组件的stroke或者fill会影响到预设样式 例如stroke-width
svgoOptions: false svgoOptions: false
}) })
] ],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
}
}); });

Loading…
Cancel
Save