实现旋转、拖动、参考线、吸附等功能(最简单方法)

实现旋转、拖动、参考线、吸附等功能(最简单方法)

效果如下:在这里插入图片描述
我使用的是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;
            }
          }
        });
      

**如果文章对你有帮助,点个赞再走吧

有不懂的,或者说有问题的,欢迎留言,批评指正.**

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值