实现旋转、拖动、参考线、吸附等功能(最简单方法)
效果如下:
我使用的是konva框架画的图形,
点我去Konva
如果你会使用canvas,这个框架对于你来说很简单的。我们想画一个圆一个矩形或者其他的图像直接,定一个对象即可。我个人比较喜欢这个,因为我工作中用canvas用的比较多,又不想写原生的。有兴趣的可以看一看,了解一下。
我们简单的介绍了Konva。我们来唠正事。先上代码,在慢慢剖析:
代码如下: 可以直接运行出效果
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva@4.0.18/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Snapping of shapes Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var GUIDELINE_OFFSET = 5;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
stage.add(layer);
// Generate random rectangles
for (var i = 0; i < 5; i++) {
var rect = new Konva.Rect({
x: Math.random() * stage.width(),
y: Math.random() * stage.height(),
width: 50 + Math.random() * 50,
height: 50 + Math.random() * 50,
fill: Konva.Util.getRandomColor(),
draggable: true,
name: 'object'
});
layer.add(rect);
// Add a transformer to each rectangle
var tr = new Konva.Transformer({
node: rect,
centeredScaling: true,
rotationSnaps: [0, 90, 180, 270]
});
layer.add(tr);
}
// Create a group containing a triangle and a square
var group = new Konva.Group({
x: stage.width() / 2,
y: stage.height() / 2,
draggable: true,
name: 'object'
});
var triangle = new Konva.Line({
points: [-50, 50, 50, 50, 0, -50],
fill: 'red',
closed: true
});
var square = new Konva.Rect({
x: -25,
y: 10,
width: 20,
height: 20,
fill: 'blue'
});
group.add(triangle);
group.add(square);
layer.add(group);
var groupTransformer = new Konva.Transformer({
node: group,
centeredScaling: true,
rotationSnaps: [0, 90, 180, 270]
});
layer.add(groupTransformer);
layer.draw();
function getLineGuideStops(skipShape) {
var vertical = [0, stage.width() / 2, stage.width()];
var horizontal = [0, stage.height() / 2, stage.height()];
stage.find('.object').forEach(guideItem => {
if (guideItem === skipShape) {
return;
}
var box = guideItem.getClientRect();
console.log(box);
vertical.push([box.x, box.x + box.width, box.x + box.width / 2]);
horizontal.push([box.y, box.y + box.height, box.y + box.height / 2]);
});
return {
vertical: vertical.flat(),
horizontal: horizontal.flat()
};
}
function getObjectSnappingEdges(node) {
var box = node.getClientRect();
return {
vertical: [
{
guide: Math.round(box.x),
offset: Math.round(node.x() - box.x),
snap: 'start'
},
{
guide: Math.round(box.x + box.width / 2),
offset: Math.round(node.x() - box.x - box.width / 2),
snap: 'center'
},
{
guide: Math.round(box.x + box.width),
offset: Math.round(node.x() - box.x - box.width),
snap: 'end'
}
],
horizontal: [
{
guide: Math.round(box.y),
offset: Math.round(node.y() - box.y),
snap: 'start'
},
{
guide: Math.round(box.y + box.height / 2),
offset: Math.round(node.y() - box.y - box.height / 2),
snap: 'center'
},
{
guide: Math.round(box.y + box.height),
offset: Math.round(node.y() - box.y - box.height),
snap: 'end'
}
]
};
}
function getGuides(lineGuideStops, itemBounds) {
var resultV = [];
var resultH = [];
lineGuideStops.vertical.forEach(lineGuide => {
itemBounds.vertical.forEach(itemBound => {
var diff = Math.abs(lineGuide - itemBound.guide);
if (diff < GUIDELINE_OFFSET) {
resultV.push({
lineGuide: lineGuide,
diff: diff,
snap: itemBound.snap,
offset: itemBound.offset
});
}
});
});
lineGuideStops.horizontal.forEach(lineGuide => {
itemBounds.horizontal.forEach(itemBound => {
var diff = Math.abs(lineGuide - itemBound.guide);
if (diff < GUIDELINE_OFFSET) {
resultH.push({
lineGuide: lineGuide,
diff: diff,
snap: itemBound.snap,
offset: itemBound.offset
});
}
});
});
var guides = [];
var minV = resultV.sort((a, b) => a.diff - b.diff)[0];
var minH = resultH.sort((a, b) => a.diff - b.diff)[0];
if (minV) {
guides.push({
lineGuide: minV.lineGuide,
offset: minV.offset,
orientation: 'V',
snap: minV.snap
});
}
if (minH) {
guides.push({
lineGuide: minH.lineGuide,
offset: minH.offset,
orientation: 'H',
snap: minH.snap
});
}
return guides;
}
function drawGuides(guides) {
guides.forEach(lg => {
if (lg.orientation === 'H') {
var line = new Konva.Line({
points: [-6000, lg.lineGuide, 6000, lg.lineGuide],
stroke: 'rgb(0, 161, 255)',
strokeWidth: 1,
name: 'guid-line',
dash: [4, 6]
});
layer.add(line);
layer.batchDraw();
} else if (lg.orientation === 'V') {
var line = new Konva.Line({
points: [lg.lineGuide, -6000, lg.lineGuide, 6000],
stroke: 'rgb(0, 161, 255)',
strokeWidth: 1,
name: 'guid-line',
dash: [4, 6]
});
layer.add(line);
layer.batchDraw();
}
});
}
function handleTransform(e) {
layer.find('.guid-line').destroy();
var lineGuideStops = getLineGuideStops(e.target);
var itemBounds = getObjectSnappingEdges(e.target);
var guides = getGuides(lineGuideStops, itemBounds);
if (!guides.length) {
return;
}
drawGuides(guides);
guides.forEach(lg => {
switch (lg.snap) {
case 'start': {
switch (lg.orientation) {
case 'V': {
e.target.x(lg.lineGuide + lg.offset);
break;
}
case 'H': {
e.target.y(lg.lineGuide + lg.offset);
break;
}
}
break;
}
case 'center': {
switch (lg.orientation) {
case 'V': {
e.target.x(lg.lineGuide + lg.offset);
break;
}
case 'H': {
e.target.y(lg.lineGuide + lg.offset);
break;
}
}
break;
}
case 'end': {
switch (lg.orientation) {
case 'V': {
e.target.x(lg.lineGuide + lg.offset);
break;
}
case 'H': {
e.target.y(lg.lineGuide + lg.offset);
break;
}
}
break;
}
}
});
}
layer.on('dragmove', handleTransform);
layer.on('transform', handleTransform);
layer.on('dragend', function(e) {
layer.find('.guid-line').destroy();
layer.batchDraw();
});
layer.on('transformend', function(e) {
layer.find('.guid-line').destroy();
layer.batchDraw();
});
layer.draw();
</script>
</body>
</html>
我们主要看拖动事件:
layer.on('dragmove', handleTransform);
layer.on('transform', handleTransform);
handleTransform 方法干了哪些事件:
/**
* 处理元素变换时的辅助线引导。
* 此函数用于在拖动或调整元素时,计算并显示辅助线,以及根据辅助线对元素进行对齐。
* @param {Object} e - 事件对象,包含目标元素的信息。
*/
function handleTransform(e) {
// 销毁当前存在的辅助线,以更新新的辅助线。
layer.find('.guid-line').destroy();
// 获取当前操作元素的辅助线停靠点。
var lineGuideStops = getLineGuideStops(e.target);
// 获取当前操作元素的边界信息,用于辅助线的计算。
var itemBounds = getObjectSnappingEdges(e.target);
// 根据停靠点和边界信息计算辅助线。
var guides = getGuides(lineGuideStops, itemBounds);
// 如果没有计算出辅助线,则不进行后续操作。
if (!guides.length) {
return;
}
// 绘制计算出的辅助线。
drawGuides(guides);
// 遍历所有辅助线,根据不同的对齐类型和方向,调整元素的位置。
guides.forEach(lg => {
switch (lg.snap) {
case 'start': {
// 根据辅助线的方向,调整元素的起点位置。
switch (lg.orientation) {
case 'V': {
e.target.x(lg.lineGuide + lg.offset);
break;
}
case 'H': {
e.target.y(lg.lineGuide + lg.offset);
break;
}
}
break;
}
case 'center': {
// 根据辅助线的方向,调整元素的中心位置。
switch (lg.orientation) {
case 'V': {
e.target.x(lg.lineGuide + lg.offset);
break;
}
case 'H': {
e.target.y(lg.lineGuide + lg.offset);
break;
}
}
break;
}
case 'end': {
// 根据辅助线的方向,调整元素的终点位置。
switch (lg.orientation) {
case 'V': {
e.target.x(lg.lineGuide + lg.offset);
break;
}
case 'H': {
e.target.y(lg.lineGuide + lg.offset);
break;
}
}
break;
}
}
});
**如果文章对你有帮助,点个赞再走吧
。
有不懂的,或者说有问题的,欢迎留言,批评指正
.**