• (开源)给图片编辑器添加了辅助线


    前言

    上篇我们介绍了做的图片编辑器,大部分工具类的软件都有辅助线,方便拖拽元素的时候对齐,能让我们快速的做出漂亮的图片。 这两天给编辑器加上了辅助线, 辅助线实现过程稍微有些复杂,我们一步步说下实现过程。

    演示

    演示地址

    实现流程

    原理讲解

    • 左侧辅助线出现
      image.png
      我们以节点2为移动的元素,通过上面的图观察我们可以看出,当左侧辅助线出现的时候,节点1x坐标和节点2x坐标相等的时候辅助线就会出现,我们移动节点2的时候动态去判断。

    • 右侧辅助线出现
      image.png

    我们以节点2为移动的元素,通过上面的图观察我们可以看出,当右侧辅助线出现的时候,节点1x+width(坐标x+节点的宽度)和节点2x坐标相等的时候辅助线就会出现,我们移动节点2的时候动态去判断。

    辅助线规则

    • 左侧辅助线 x1(x) = x2(x)
    • 右侧辅助线 x1(x+width) = x2(x)
    • 水平中间辅助线 x1(x+width/2) = x2(x+ width / 2)
    • 顶部辅助线 x1(y) = x2(y)
    • 底部辅助线 x1(y+height) = x(y)
    • 垂直中间辅助线 x1(y+height/2) = x2(+height/2)
      上面的公式我们已节点2拖动的元素节点1目标元素。当我们以节点1拖动元素节点2目标元素,公式会有变化,大家可以自行尝试一下。留在评论区

    代码实现

    上面我们分析出了一个节点的对比规则,画布上可能会有很多节点,让当前移动的节点去和剩下的元素去做比较。

    找出画布上的所有元素,记住位置

    通过Knova的layer下的children获取所有元素,并记录位置,代码如下

    // 获取单个节点的位置信息
    export const getLocationItem = (shapeObject: Konva.Shape) => {
      const id = shapeObject.id();
      const width = shapeObject.width();
      const height = shapeObject.height();
      const x = shapeObject.x();
      const y = shapeObject.y();
    
      const locationItem: LocationItem = {
        id,
        w: width,
        h: height,
        x, // x坐标
        y, // y坐标
        l: x, // 左侧方向                      
        r: x + width, // 右侧方向
        t: y,  // 顶部方向
        b: y + height, // 底部方向
        lc: x + (width / 2), // 水平居中
        tc: y + (height / 2) // 垂直居中
      }
      return locationItem;
      // console.log('locationItem=>', locationItem);
    }
    
    // 设置所有节点的信息
    export const setLocationItems = (layer: Konva.Layer) => {
      locationItems = [];
      layer.children?.forEach(item => {
        if (item.className !== 'Transformer') {
          locationItems.push(getLocationItem(item));
        }
      });
    }
    
    

    节点拖动,根据计算规则画线

    在拖动节点的时候,调用detectionToLine该方法。

    /**
     * 拖动节点,shape代表当前拖动的节点
     */
    export const detectionToLine = (layer: Konva.Layer, shape: Konva.Shape) => {
      const locationItem = getLocationItem(shape); // 当前节点的位置信息
      // 过滤当前节点,和剩下的节点做比较
      const compareLocations = locationItems.filter((item: LocationItem) => item.id !== locationItem.id);
      removeLines(layer); // 移除之前划过的线
      compareLocations.forEach((item: LocationItem) => {
        if ((Math.abs(locationItem.x - item.x) <= threshold)) { // 处理左侧方向
          shape.setPosition({ x: item.x, y: locationItem.y })
          addLine(layer, locationItem, item, DIRECTION.left)
        }
        if ((Math.abs(locationItem.x - item.r) <= threshold)) { // 处理右侧
          shape.setPosition({ x: item.r, y: locationItem.y })
          addLine(layer, locationItem, item, DIRECTION.right);
        }
    
        if ((Math.abs(locationItem.lc - item.lc) <= threshold)) { // 处理水平居中
          shape.setPosition({ x: item.lc - (locationItem.w / 2), y: locationItem.y })
          addLine(layer, locationItem, item, DIRECTION.leftCenter);
        }
    
        // 拖动节点和目标节点互换的判断条件
        if ((Math.abs(locationItem.r - item.x) <= threshold)) {
          shape.setPosition({ x: item.l - locationItem.w, y: locationItem.t })
          addLine(layer,item,locationItem, DIRECTION.right)
        }
        if ((Math.abs(locationItem.r - item.r) <= threshold)) { // 右侧相等
          shape.setPosition({ x: item.r - locationItem.w, y: locationItem.t })
          addLine(layer,item,locationItem, DIRECTION.right)
        }
    
    
        if ((Math.abs(locationItem.y - item.y) <= threshold)) { // 处理垂直方向顶部
          shape.setPosition({ x: locationItem.x, y: item.y })
          addLine(layer, locationItem, item, DIRECTION.top);
        }
    
        if ((Math.abs(locationItem.y - item.b) <= threshold)) { // 处理底部
          shape.setPosition({ x: locationItem.x, y: item.b })
          addLine(layer, locationItem, item, DIRECTION.bottom);
        }
    
        if ((Math.abs(locationItem.tc - item.tc) <= threshold)) { // 处理垂直顶部居中
          shape.setPosition({ x: locationItem.x, y: item.tc - (locationItem.h /2 ) })
          addLine(layer, locationItem, item, DIRECTION.topCenter);
        }
    
         // 拖动节点和目标节点互换的判断条件
        if ((Math.abs(locationItem.b - item.t) <= threshold)) { // 处理垂底部方向
          shape.setPosition({ x: locationItem.l, y: item.t - locationItem.h })
          addLine(layer,item,locationItem, DIRECTION.bottom)
        }
    
        if ((Math.abs(locationItem.b - item.b) <= threshold)) { // 右侧相等
          shape.setPosition({ x: locationItem.l, y: item.b - locationItem.h })
          addLine(layer,item,locationItem, DIRECTION.bottom)
        }
      });
    }
    
    

    达到阈值,添加辅助线

    我们可以看到在对比的时候是这样的代码

    Math.abs(locationItem.b - item.b) <= threshold)
    

    这块主要是用来判断两个节点之间的距离小于设定的阈值,触发添加辅助线。

    还有一段设置当前节点位置的代码,如下

     shape.setPosition({ x: locationItem.l, y: item.t - locationItem.h })
    

    这块的主要作用是辅助线出现的是,节点移动的位置不超过阈值,节点不会动。

    添加辅助线

    添加辅助线会传入拖动的元素和目标元素,以及哪个方向要出现辅助线

     addLine(layer, locationItem, item, DIRECTION.left)
    

    根据拖动的元素和目标元素以及方向计算出辅助线出现的位置

    /**
     *
     * @param sourceItem 拖动的图形
     * @param targetItem 目标图形
     * @param targetItem 方向
     */
    const getPoints = (sourceItem: LocationItem, targetItem: LocationItem, direction: DIRECTION) => {
    
      let minItem: LocationItem, maxItem: LocationItem;
      let points: any = [];
    
      let po = {
        [DIRECTION.left]: [
          [targetItem.l, sourceItem.b, targetItem.l, targetItem.t],
          [targetItem.l, targetItem.b, targetItem.l, sourceItem.t]
        ],
        [DIRECTION.right]: [
          [targetItem.r, sourceItem.b, targetItem.r, targetItem.t],
          [targetItem.r, targetItem.b, targetItem.r, sourceItem.t]
        ],
        [DIRECTION.leftCenter]: [
          [targetItem.lc, sourceItem.b, targetItem.lc, targetItem.t],
          [targetItem.lc, targetItem.b, targetItem.lc, sourceItem.t]
        ],
        [DIRECTION.top]: [
          [sourceItem.r, targetItem.t, targetItem.l, targetItem.t],
          [targetItem.r, targetItem.t, sourceItem.l, targetItem.t]
        ],
        [DIRECTION.bottom]: [
          [sourceItem.r, targetItem.b, targetItem.l, targetItem.b],
          [targetItem.r, targetItem.b, sourceItem.l, targetItem.b]
        ],
        [DIRECTION.topCenter]: [
          [sourceItem.r, targetItem.tc, targetItem.l, targetItem.tc],
          [targetItem.r, targetItem.tc, sourceItem.l, targetItem.tc]
        ]
      }
    
      switch (direction) {
        case DIRECTION.left:
          return sourceItem.y < targetItem.y ? po[DIRECTION.left][0] : po[DIRECTION.left][1];
    
        case DIRECTION.right:
          // 目标图形是否在上边
          return sourceItem.y < targetItem.y ? po[DIRECTION.right][0] : po[DIRECTION.right][1];
    
        case DIRECTION.leftCenter:
          return sourceItem.y < targetItem.y ? po[DIRECTION.leftCenter][0] : po[DIRECTION.leftCenter][1];
    
        case DIRECTION.top:
          return sourceItem.x < targetItem.x ? po[DIRECTION.top][0] : po[DIRECTION.top][1];
    
        case DIRECTION.bottom:
          return sourceItem.x < targetItem.x ? po[DIRECTION.bottom][0] : po[DIRECTION.bottom][1];
    
        case DIRECTION.topCenter:
          return sourceItem.x < targetItem.x ? po[DIRECTION.topCenter][0] : po[DIRECTION.topCenter][1];
        default:
          break;
      }
      return points;
    }
    
    

    添加辅助线方法,比较简单

    export const addLine = (layer: Konva.Layer, sourceItem: LocationItem, targetItem: LocationItem, direction: DIRECTION) => {
    // 计算出辅助线的位置新新
      const points = getPoints(sourceItem, targetItem, direction);
      var greenLine = new Konva.Line({
        points: points,
        stroke: 'green',
        strokeWidth: 1,
        lineJoin: 'round',
        dash: [10, 10]
      })
      // greenLine.direction = direction
    
      lines.push(greenLine);
      layer.add(greenLine);
      layer.draw();
    }
    
    

    地址

    参考

    交流沟通

    建立了一个微信交流群,如需沟通讨论,请加入。

    image.png

    二维码过期,请添加微信号q1454763497,备注image editor,我会拉你进群

    总结

    复制先的实现还是稍微有点复杂,主要是弄明白原理和计算公式,也就简单了。大家可以把公式补全,留在评论区,锻炼下自己的分析能力。部分代码上面已经描述出来,如需要查看更详细的内容,请移步fast-image-editor
    大家觉得有帮忙,请在github帮忙star一下。

    历史文章

  • 相关阅读:
    Mybatis批处理
    Mybatis兼容C3P0连接池
    一对多,多对一查询
    缓存
    动态sql
    mybatis 日志记录
    python学习day07-encode和decode
    python学习day07---三级目录优化
    python学习day06练习---三级目录
    python学习day06--02字典增删差改以及字符串的一些方法
  • 原文地址:https://www.cnblogs.com/qiaojie/p/15161445.html
Copyright © 2020-2023  润新知