svg编辑器——新建路径

1,126 阅读2分钟

演示地址

adjlyadv.github.io/svg-editor/

拖拽锚点的问题

使用mousedown、mouseup、mousemove来实现锚点的拖拽功能,这里需要注意的是要把mousemove事件绑定在父元素上。

如果绑定在需要移动的元素上,鼠标移动过快的时候就会移出元素的范围,造成元素的停滞。

在项目中使用了mobx作为全局状态管理,而且会有多个需要拖动的锚点,所以在store中设置了一个dragging对象,来记录当前鼠标点击的是属于哪个path上的哪个node。

示例代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>测试</title>
    <style>
        #container{
            margin-top:100px;
            margin-left:200px;
        }
        svg{
            width:400px;
            height:400px;
            background-color: black;
        }
        circle{
            cx:10;
            cy:10;
            fill: #0FF;
            stroke: #55f;
            r: 10;
        }
    </style>
</head>
<body>
    <div id="container">
        <svg id="bg" width="400" height="400">
            <circle id="ci"/>
        </svg>
    </div>

    <script>
        let container = document.getElementById('container')
        let svg = document.getElementById('bg');
        let circle = document.getElementById('ci');
        let dragged = false;

        circle.onmousedown = (event) =>{
            dragged = true;
            console.log('down');
        }

        circle.onmouseup = (event) =>{
            dragged = false;
            console.log('up');
        }
        svg.onmousemove = (event) =>{
            if(dragged){
                let x = event.clientX 
                let y = event.clientY
                circle.style.cx = x - container.offsetLeft;
                circle.style.cy = y - container.offsetTop;
            }
        }

    </script>
</body>
</html>

新建路径时的渲染

通过观察PS的钢笔工具以及其他类似产品,总结新建路径的流程如图

渲染

在选择控制点的时候(鼠标按下移动的过程)需要渲染锚点和鼠标连线的直线。

<circle className="point-control" cx={newNode.posX} cy={newNode.posY} stroke="#55f" r="10" />
<line x1={newNode.posX} y1={newNode.posY} x2={newNode.ctrPosX} y2={newNode.ctrPosY} stroke="#555" strokeWidth={width} />
<circle className="point-control" cx={newNode.ctrPosX} cy={newNode.ctrPosY} stroke="#000" r="10" />

在选择新的锚点的时候(鼠标起来之后移动的过程),需要渲染鼠标和上一个锚点的path。

let getD = `M ${lastNode.posX} ${lastNode.posY} C ${mockCtrX} ${mockCtrY} ${newNode.ctrPosX} ${newNode.ctrPosY}`;

if(lastNode.posX !== newNode.posX && lastNode.posY !== newNode.posY){
   getD += ` ${newNode.posX} ${newNode.posY}`;
}else{
   getD += ` ${newNode.ctrPosX} ${newNode.ctrPosY}`;//当没有确定新的锚点时
}

<path d = {getD}  fill="none" stroke="#000" strokeWidth="1"/>

上面的代码如果去掉else就会变成这样(newnode和oldnode的pos重合):

判断起点

在path中起点只有一个控制点,所以当起点为lastnode的时候需要改变一下控制点。使用了一个startnode的变量来判断是否是路径上的初始点。

              if(startNode){// 处理第一个节点的渲染
                setLastnode({
                  ...newNode,
                  ctrPosX: mockCtrX,
                  ctrPosY: mockCtrY
                })
                setStartNode(false);
              }else{//是其他节点
                setLastnode({
                  ...newNode
                })
              }

区分双击事件和mousedown mouseup

在鼠标事件中的顺序是 mousedown mouseup click mousedown mouseup click doubleclick所以如果是双击事件就会触发两次mouseup,为了区分mouseup和doubleclick,在mouseup上加了一个定时器,然后在doublecklick处清除定时器,这样就可以在双击时完成路径最后一个锚点的添加。

const handleMouseUp = (event: any) => {
    event.stopPropagation();
    clearTimeout(mouseUpTimeChange);
    mouseUpTimeChange = setTimeout(()=>{},250);
}
  const pathDoubleClick:any = () => {
    clearTimeout(mouseUpTimeChange);
    ...
  }