jsx
复制代码
import { useKeyPress } from 'ahooks'
import Konva from 'konva'
import React, { useState } from 'react'
import { Group, Layer, Rect, Stage, Transformer } from 'react-konva'
import { useShapeSelection } from './useShapeSelection'
import { useShapeTransformer } from './useShapeTransformer'
const INIT_SHAPES = [
{
id: 'rect',
type: 'Rect',
fill: 'red',
width: 100,
height: 100,
x: 100,
y: 100,
scaleX: 1,
scaleY: 1,
skewX: 0,
skewY: 0,
rotation: 0
},
{
id: 'circle',
type: 'Circle',
fill: 'blue',
radius: 50,
x: 300,
y: 100,
scaleX: 1,
scaleY: 1,
skewX: 0,
skewY: 0,
rotation: 0
},
{
id: 'star',
type: 'Star',
fill: 'yellow',
numPoints: 5,
innerRadius: 30,
outerRadius: 70,
x: 200,
y: 100,
scaleX: 1,
scaleY: 1,
skewX: 0,
skewY: 0,
rotation: 0
}
]
export default function View() {
const [shapes, setShapes] = useState(INIT_SHAPES)
// 使用自定义Hook处理选择逻辑
const { selectedIds, handleShapeClick, handleStageClick } = useShapeSelection()
// 使用自定义Hook处理变换逻辑
const {
transformerMode,
stageRef,
layerRef,
groupRef,
transformerRef,
enableTransformerMode,
confirmTransform,
cancelTransform,
handleGroupDragStart,
handleTransformEnd,
handleShapeDragEnd
} = useShapeTransformer(shapes, setShapes, selectedIds)
// 快捷键绑定
useKeyPress(['ctrl.alt.t'], enableTransformerMode)
useKeyPress(['enter'], confirmTransform)
useKeyPress(['esc'], cancelTransform)
const shapesElements = () => {
return shapes.map((shape, index) => {
const { id, type, ...props } = shape
const isSelected = selectedIds.includes(id)
return React.createElement(type, {
key: id,
id,
draggable: !transformerMode,
stroke: isSelected ? '#000' : undefined,
strokeWidth: isSelected ? 4 : undefined,
onClick: e => handleShapeClick(e, transformerMode),
onDragEnd: e => handleShapeDragEnd(e, index),
onTransformEnd: e => handleTransformEnd(e, index),
onDragStart: (evt: Konva.KonvaEventObject<DragEvent>) => {
// 取消冒泡
evt.cancelBubble = true
},
...props
}) as React.ReactElement
})
}
return (
<div className='view-wrapper'>
<div>selectedId: {selectedIds.join(', ')}</div>
<div>Mode: {transformerMode ? '变形模式' : '普通模式'}</div>
<div>快捷键: Ctrl+Alt+T(变形) | Enter(确认) | Esc(取消) | Shift+点击(多选)</div>
<Stage ref={stageRef} width={800} height={600} onClick={handleStageClick}>
<Layer ref={layerRef}>
<Group ref={groupRef} draggable onDragStart={handleGroupDragStart}>
<Rect width={800} height={600} fill='rgba(0, 0, 0, 0.1)' />
{shapesElements()}
</Group>
<Transformer
visible={transformerMode}
ref={transformerRef}
boundBoxFunc={(oldBox, newBox) => {
// 限制最小尺寸
if (newBox.width < 5 || newBox.height < 5) {
return oldBox
}
return newBox
}}
/>
</Layer>
</Stage>
</div>
)
}
jsx
复制代码
import Konva from 'konva'
import cloneDeep from 'lodash.clonedeep'
import { useRef, useState } from 'react'
// 自定义Hook:处理图形变换逻辑
export const useShapeTransformer = (shapes, setShapes, selectedIds) => {
const [transformerMode, setTransformerMode] = useState(false)
const beforeTransformShapeRef = useRef([])
const stageRef = useRef<Konva.Stage>(null)
const layerRef = useRef<Konva.Layer>(null)
const groupRef = useRef<Konva.Group>(null)
const transformerRef = useRef<Konva.Transformer>(null)
// 开启变形模式
const enableTransformerMode = () => {
if (selectedIds.length === 0) return
// 记录当前shapes, 用于撤销
beforeTransformShapeRef.current = cloneDeep(shapes)
setTransformerMode(true)
transformerRef.current.nodes(selectedIds.map(id => stageRef.current.findOne(`#${id}`)))
}
// 确认变形
const confirmTransform = () => {
if (!transformerMode) return
setTransformerMode(false)
transformerRef.current.nodes([])
}
// 取消变形
const cancelTransform = () => {
if (!transformerMode) return
setTransformerMode(false)
setShapes(beforeTransformShapeRef.current)
transformerRef.current.nodes([])
}
// 处理组拖拽结束事件
const handleGroupDragStart = () => {
groupRef.current.stopDrag()
if (!transformerMode) return
// 停止所有图形的拖拽,只允许选中的图形拖拽
shapes.forEach(shape => {
if (selectedIds.includes(shape.id)) {
stageRef.current.findOne(`#${shape.id}`).startDrag()
} else {
stageRef.current.findOne(`#${shape.id}`).stopDrag()
}
})
}
// 处理变换结束事件
const handleTransformEnd = (e: Konva.KonvaEventObject<DragEvent>, index: number) => {
const node = e.target as Konva.Shape
if (!node) return
const { x, y, width, height, scaleX, scaleY, rotation } = node.attrs
// 更新所有选中图形的属性
setShapes(prevShapes => {
const newShapes = cloneDeep(prevShapes)
newShapes[index] = {
...newShapes[index],
x: x,
y: y,
width,
height,
scaleX,
scaleY,
rotation: rotation
}
return newShapes
})
}
// 处理图形拖拽结束事件
const handleShapeDragEnd = (e: Konva.KonvaEventObject<DragEvent>, index: number) => {
const node = e.target as Konva.Shape
const { x, y } = node.position()
setShapes(prevShapes => {
const newShapes = cloneDeep(prevShapes)
newShapes[index] = {
...newShapes[index],
x,
y
}
return newShapes
})
}
return {
transformerMode,
stageRef,
layerRef,
groupRef,
transformerRef,
enableTransformerMode,
confirmTransform,
cancelTransform,
handleGroupDragStart,
handleTransformEnd,
handleShapeDragEnd
}
}