最新源码地址

index.tsx
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)
const { selectedIds, handleShapeClick, handleStageClick } = useShapeSelection()
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>
)
}
useShapeSelection.ts
import Konva from 'konva'
import cloneDeep from 'lodash.clonedeep'
import { useState } from 'react'
export const useShapeSelection = () => {
const [selectedIds, setSelectedIds] = useState<string[]>([])
const handleShapeClick = (e: Konva.KonvaEventObject<MouseEvent>, isTransformerMode: boolean) => {
if (isTransformerMode) return
const node = e.target as Konva.Shape
const id = node.id()
const isShiftPressed = e.evt.shiftKey
setSelectedIds(prev => {
const newSelectedIds = cloneDeep(prev)
const index = newSelectedIds.indexOf(id)
if (index === -1) {
return isShiftPressed ? [...newSelectedIds, id] : [id]
} else {
newSelectedIds.splice(index, 1)
return newSelectedIds
}
})
}
const handleStageClick = (e: Konva.KonvaEventObject<MouseEvent>) => {
if (e.target === e.target.getStage()) {
setSelectedIds([])
}
}
return { selectedIds, setSelectedIds, handleShapeClick, handleStageClick }
}
useShapeTransformer.ts
import Konva from 'konva'
import cloneDeep from 'lodash.clonedeep'
import { useRef, useState } from 'react'
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
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
}
}