HTML5 Canvas 复杂拖放边界
如需使用 Konva 将正在拖放的节点限制在指定区域内,
可以使用 dragmove 事件定义节点无法越过的边界。
提示:你可以使用 shape.absolutePosition() 方法获取或设置节点的绝对位置,而不是使用相对的 x 和 y。
操作说明: 拖放浅蓝色矩形,并观察它 被限制在 y = 50 的假想边界下方。拖放黄色 矩形,并观察它被限制在假想圆形内。
- Vanilla
- React
- Vue
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const blueGroup = new Konva.Group({
x: 30,
y: 70,
draggable: true,
});
// bound below y=50
blueGroup.on('dragmove', () => {
blueGroup.y(Math.max(blueGroup.y(), 50));
});
// bound inside a circle
const yellowGroup = new Konva.Group({
x: stage.width() / 2,
y: 70,
draggable: true,
});
yellowGroup.on('dragmove', () => {
const x = stage.width() / 2;
const y = 70;
const radius = 50;
const pos = yellowGroup.absolutePosition();
const scale = radius / Math.sqrt(Math.pow(pos.x - x, 2) + Math.pow(pos.y - y, 2));
if (scale < 1) {
yellowGroup.x(Math.round((pos.x - x) * scale + x));
yellowGroup.y(Math.round((pos.y - y) * scale + y));
}
});
const blueText = new Konva.Text({
fontSize: 26,
fontFamily: 'Calibri',
text: 'bound below',
fill: 'black',
padding: 10,
width: 150,
align: 'center',
});
const blueRect = new Konva.Rect({
width: 150,
height: 72,
fill: '#aaf',
stroke: 'black',
strokeWidth: 4,
});
const yellowText = new Konva.Text({
fontSize: 26,
fontFamily: 'Calibri',
text: 'bound in circle',
fill: 'black',
padding: 10,
width: 150,
align: 'center',
});
const yellowRect = new Konva.Rect({
width: 150,
height:72,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
});
blueGroup.add(blueRect).add(blueText);
yellowGroup.add(yellowRect).add(yellowText);
layer.add(blueGroup);
layer.add(yellowGroup);
stage.add(layer);
import { Stage, Layer, Group, Rect, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [bluePosition, setBluePosition] = useState({ x: 30, y: 70 });
const [yellowPosition, setYellowPosition] = useState({
x: window.innerWidth / 2,
y: 70,
});
const handleBlueDragMove = (e) => {
setBluePosition({
x: e.target.x(),
y: Math.max(e.target.y(), 50),
});
};
const handleYellowDragMove = (e) => {
const x = window.innerWidth / 2;
const y = 70;
const radius = 50;
const position = e.target.absolutePosition();
const scale =
radius /
Math.sqrt(
Math.pow(position.x - x, 2) + Math.pow(position.y - y, 2)
);
if (scale < 1) {
setYellowPosition({
x: Math.round((position.x - x) * scale + x),
y: Math.round((position.y - y) * scale + y),
});
} else {
setYellowPosition(position);
}
};
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Group
x={bluePosition.x}
y={bluePosition.y}
draggable
onDragMove={handleBlueDragMove}
>
<Rect
width={150}
height={72}
fill="#aaf"
stroke="black"
strokeWidth={4}
/>
<Text
text="bound below"
fontSize={26}
fontFamily="Calibri"
fill="black"
padding={10}
width={150}
align="center"
/>
</Group>
<Group
x={yellowPosition.x}
y={yellowPosition.y}
draggable
onDragMove={handleYellowDragMove}
>
<Rect
width={150}
height={72}
fill="yellow"
stroke="black"
strokeWidth={4}
/>
<Text
text="bound in circle"
fontSize={26}
fontFamily="Calibri"
fill="black"
padding={10}
width={150}
align="center"
/>
</Group>
</Layer>
</Stage>
);
};
export default App;
<template>
<v-stage :config="stageSize">
<v-layer>
<v-group
:config="blueGroupConfig"
@dragmove="handleBlueDragMove"
>
<v-rect :config="blueRectConfig" />
<v-text :config="blueTextConfig" />
</v-group>
<v-group
:config="yellowGroupConfig"
@dragmove="handleYellowDragMove"
>
<v-rect :config="yellowRectConfig" />
<v-text :config="yellowTextConfig" />
</v-group>
</v-layer>
</v-stage>
</template>
<script setup>
const stageSize = {
width: window.innerWidth,
height: window.innerHeight
};
const blueGroupConfig = {
x: 30,
y: 70,
draggable: true
};
const yellowGroupConfig = {
x: window.innerWidth / 2,
y: 70,
draggable: true
};
const blueRectConfig = {
width: 150,
height: 72,
fill: '#aaf',
stroke: 'black',
strokeWidth: 4
};
const yellowRectConfig = {
width: 150,
height: 72,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4
};
const blueTextConfig = {
text: 'bound below',
fontSize: 26,
fontFamily: 'Calibri',
fill: 'black',
padding: 10,
width: 150,
align: 'center'
};
const yellowTextConfig = {
text: 'bound in circle',
fontSize: 26,
fontFamily: 'Calibri',
fill: 'black',
padding: 10,
width: 150,
align: 'center'
};
const handleBlueDragMove = (e) => {
e.target.y(Math.max(e.target.y(), 50));
};
const handleYellowDragMove = (e) => {
const x = window.innerWidth / 2;
const y = 70;
const radius = 50;
const pos = e.target.absolutePosition();
const scale = radius / Math.sqrt(Math.pow(pos.x - x, 2) + Math.pow(pos.y - y, 2));
if (scale < 1) {
e.target.x(Math.round((pos.x - x) * scale + x));
e.target.y(Math.round((pos.y - y) * scale + y));
}
};
</script>