演示地址
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);
...
}