赞
踩
提示:canvas画图,画矩形,圆形,直线,曲线可拖拽移动
test.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>canvas跟随鼠标移动画透明线</title> <style> div,canvas,img{ user-select: none; } .my_canvas,.bg_img{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); } .cf{ content: ''; display: block; overflow: hidden; clear: both; } .fl{ float: left; } .fr{ float: right; } .bg_img{ width: 674px; height: 495px; background: #ddd; } .img_tools{ position: absolute; top: 20px; left: 50%; transform: translateX(-50%); border: 1px solid #eee; border-radius: 64px; height: 64px; line-height: 64px; box-sizing: border-box; padding: 15px 20px 0; } .img_tool{ height: 32px; line-height: 32px; color: #000; font-size: 14px; text-align: center; width: 80px; border: 1px solid #ddd; border-radius: 32px; margin-right: 10px; cursor: pointer; position: relative; } .img_tool_active{ color: #409EFF; border: 1px solid #409EFF; } .show_history{ position: absolute; bottom:0; left: 50%; transform: translateX(-50%); } .show_history>img{ width: 120px; margin-right: 10px; border: 1px solid #eee; border-radius: 4px; } .canvas_text{ width: 120px; height: 32px; line-height: 32px; position: absolute; top: 0; left: 0; border: 1px solid #c0c0c0; border-radius: 4px; font-size: 16px; outline: none; background: none; display: none; font-family: Arial, Helvetica, sans-serif; padding-left: 0; letter-spacing: 0; } </style> </head> <body> <div class="bg_img"></div> <canvas id="myCanvasBot" class="my_canvas" width="674" height="495"></canvas> <canvas id="myCanvasTop" class="my_canvas" width="674" height="495"></canvas> <div class="img_tools cf"> <div class="img_tool fl" onclick="changeType('curve',this)">涂鸦</div> <div class="img_tool fl" onclick="changeType('line',this)">直线</div> <div class="img_tool fl img_tool_active" onclick="changeType('rect',this)">矩形</div> <div class="img_tool fl" onclick="changeType('ellipse',this)">圆形</div> <!-- <div class="img_tool fl" onclick="changeType('eraser',this)">橡皮擦</div> --> <!-- <div class="img_tool fl" onclick="changeType('text',this)">文字</div> --> <!-- <div class="img_tool fl" onclick="changeType('revoke',this)">撤销</div> --> <!-- <div class="img_tool fl" onclick="changeType('restore',this)">恢复</div> --> </div> <input id="canvasText" autofocus class="canvas_text" type="text"> <div id="showHistory" class="show_history"></div> <script> const canvasWidth = 674; const canvasHeight = 495; //底层canvas const botCan = document.getElementById('myCanvasBot'); //顶层canvas const topCan = document.getElementById('myCanvasTop'); //底层画布 const botCtx = botCan.getContext('2d'); //顶层画布 const topCtx = topCan.getContext('2d'); //鼠标是否按下 是否移动 let isDown = false,isMove = false; //鼠标是否在canvas上抬起 let isCanUp = false; //需要画图的轨迹 let drawPoints = []; //起始点x,y let startPoint = { x:0, y:0 }; //图片历史 let historyList = []; //空历史 historyList.push(new Image()) //当前绘画历史index let historyIndex = -1; //icon历史 // let partHistory = []; //操作类型 let drawType = 'rect'; //画线宽度 const lineWidth = 10; //文字大小 const fontSize = 16; //画线颜色 let strokeStyle = 'rgba(255,0,0,0.6)'; //path2D图形列表 let pathList = []; //path2D单个图形 let pathObj = null; //path2D的唯一标识 let pathId = 0; //当前被激活的path2D let activePath = null; //是否为拖拽行为 let isDrag = false; //拖拽是否移动 let isDragMove = false; //是否为改变尺寸行为 isResize = false; //改变尺寸点list let pointsList = []; //拖拽修改尺寸的点 let activePoint = null; //resize是否移动 let isResizeMove = false; //改变尺寸点 let resizePath = null; //改变尺寸点List let resizePointList = []; //文字输入框init const canvasText = document.getElementById('canvasText'); canvasText.style.display = 'none'; canvasText.style.lineHeight = '32px'; canvasText.style.height = '32px'; canvasText.style.display = 'none'; canvasText.style.color = 'none'; canvasText.addEventListener('blur',()=>{ topCtx.font = fontSize + 'px Arial, Helvetica, sans-serif'; let h = parseFloat(canvasText.style.height); topCtx.fillText(canvasText.value, startPoint.x+1, startPoint.y+h/2+fontSize/2-1); canvasText.style.display = 'none'; canvasText.value = ''; topToBot(); }) //起始点x,y let textPoint = { x:0, y:0 }; //鼠标按下 const mousedown = (e)=>{ isDown = true; let x = (e||window.event).offsetX; let y = (e||window.event).offsetY; if(canvasText.style.display == 'none')startPoint = {x,y}; //检查是否点击到resize的点 activePoint = isResizePointInPath(x,y); if(activePoint){ isResize = true; activePath = activePoint.activePath;//原有path,需要清除 //可resize,清除resize点 topCtx.clearRect(0,0,canvasWidth,canvasHeight); switch (activePoint.type){ case 'rect': makePathActive(); break; case 'ellipse': makePathActive(); break; case 'line': makePathActive(); break; } return } //检测是否点击到图形 activePath = isPointInPath(x,y); if(activePath){ createResizePoint(activePath); //只有点击到图形的时候,才添加图形resize的点 isDrag = true; topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = activePath.strokeStyle||strokeStyle; topCtx.lineWidth = botCtx.lineWidth = activePath.lineWidth||lineWidth; topCtx.clearRect(0,0,canvasWidth,canvasHeight); switch (activePath.type){ case 'rect': makePathActive(); break; case 'ellipse': makePathActive(); break; case 'line': makePathActive(); break; case 'curve': makePathActive(); break; } return; } if(drawType == 'text'){ textPoint = { x:x+topCan.offsetLeft-canvasWidth/2, y:y+topCan.offsetTop-canvasHeight/2 }; // canvasText.style.height = 32 + 'px'; canvasText.style.top = textPoint.y+'px'; canvasText.style.left = textPoint.x+'px'; canvasText.style.display = 'block'; canvasText.style.fontSize = fontSize + 'px'; canvasText.style.color = strokeStyle; setTimeout(()=>{ canvasText.focus(); },100) } if(drawType == 'curve'){ drawPoints = []; drawPoints.push({x,y}); } topCtx.strokeStyle = topCtx.fillStyle = botCtx.strokeStyle = botCtx.fillStyle = strokeStyle; topCtx.lineWidth = botCtx.lineWidth = lineWidth; topCtx.lineCap = topCtx.lineJoin = botCtx.lineCap = botCtx.lineJoin = 'round'; } //鼠标移动 const mousemove = (e)=>{ let x = (e||window.event).offsetX; let y = (e||window.event).offsetY; let distanceX = 0; let distanceY = 0; if(isDown){ isMove = true; if(isResize){ isResizeMove = true; if(activePoint&&activePoint.resizeFun){ activePoint.resizeFun(x,y); } return } if(isDrag){ isDragMove = true; switch(activePath.type){ case 'curve': distanceX = x - startPoint.x; distanceY = y - startPoint.y; let newPoints = []; for(let i=0;i<activePath.drawPoints.length;i++){ let drawPoint = activePath.drawPoints[i]; newPoints.push({x:drawPoint.x + distanceX,y:drawPoint.y + distanceY}); } drawCurve(newPoints); break; case 'line': distanceX = x - startPoint.x; distanceY = y - startPoint.y; drawLine(activePath.startX + distanceX,activePath.startY + distanceY,activePath.x + distanceX,activePath.y + distanceY,); break; case 'eraser': // drawEraser(x,y); break; case 'rect': // xy 为当前point的坐标 // startPoint为点击的矩形上点 查看当前point.x点距离startPoint.x移动了多少 point.y点距离startPoint.y移动了多少 drawRect(activePath.x + (x - startPoint.x),activePath.y + (y - startPoint.y),activePath.width,activePath.height); break; case 'ellipse': // drawEllipse(x,y); drawEllipse(activePath.x + (x - startPoint.x),activePath.y + (y - startPoint.y),activePath.radiusX,activePath.radiusY); break; } return; } switch(drawType){ case 'curve': drawPoints.push({x,y}); drawCurve(drawPoints); break; case 'line': drawLine(startPoint.x,startPoint.y,x,y); break; case 'eraser': drawEraser(x,y); break; case 'rect': // drawRect(x,y); drawRect(startPoint.x, startPoint.y, x-startPoint.x, y - startPoint.y); break; case 'ellipse': drawEllipse((x+startPoint.x)/2, (y+startPoint.y)/2, Math.abs((x-startPoint.x)/2), Math.abs((y-startPoint.y)/2),0,0, Math.PI*2,true); break; } } } //鼠标抬起 const mouseup = (e)=>{ isCanUp = true; if(isDown){ isDown = false; // topCan内容画到botCan上 if(isResize){ isResize = false; activePath = null; resizePointList = []; if(isResizeMove){ isResizeMove = false; pathList.pop(); }else{ pathObj = pathList.pop(); } topToBot(); return; } if(isDrag){ isDrag = false; activePath = null; if(isDragMove){ isDragMove = false; pathList.pop(); }else{ pathObj = pathList.pop(); } topToBot(); return } if(drawType!='text')topToBot(); } } //topCan内容画到botCan上 const topToBot = ()=>{ if(pathObj){ pathObj.id = pathId++; pathList.push(pathObj); topCtx.clearRect(0,0,canvasWidth,canvasHeight); if(isCanUp)isCanUp=false; botCtx[pathObj.shape](pathObj.path); //如果有resizePoints if(pathObj.points&&pathObj.points.length>0){ drawResizePoint(pathObj.points); } pathObj.points = []; pathObj = null; } drawPoints = []; isDown = false; isMove = false; } //判断是否点击到图形 const isPointInPath = (x,y)=>{ let PointInPath = null; for(let i=0;i<pathList.length;i++){ let path = pathList[i]; if(botCtx.isPointInStroke(path.path,x,y)){ PointInPath = path; break; } } return PointInPath; } //判断是否点击到图形 const isResizePointInPath = (x,y)=>{ let point = null; for(let i=0;i<resizePointList.length;i++){ let resizePoint = resizePointList[i]; if(botCtx.isPointInStroke(resizePoint.path,x,y)){ point = resizePoint; break; } } return point; } //激活rect图形轮廓 botCtx删除activePath topCtx添加activePath const makePathActive = ()=>{ botCtx.clearRect(0,0,canvasWidth,canvasHeight); let arr = []; for(let i=0;i<pathList.length;i++){ let path = pathList[i] if(activePath.id != path.id){ botCtx[path.shape](path.path); arr.push(path); }else{ topCtx.strokeStyle = path.strokeStyle; topCtx[path.shape](path.path); //创建可拖动的点 } } arr.push(activePath); pathList = arr; } //创建resize的点 const createResizePoint = ()=>{ activePath.points = []; let ponitFillStyle = 'rgba(0,0,0,0.8)'; let pointWidth = 10; let pointHeight = 10; switch(activePath.type){ case 'rect': activePath.points = [ { x:activePath.x-pointWidth/2, y:activePath.y-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, //用于删除原activePath resizeFun:(x,y)=>{ drawRect(x,y,activePath.x + activePath.width-x,activePath.y + activePath.height-y); }, }, { x:activePath.x+activePath.width/2-pointWidth/2, y:activePath.y-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:1,activePath:activePath, resizeFun:(x,y)=>{ drawRect(activePath.x,y,activePath.width,activePath.height+activePath.y-y); } }, { x:activePath.x+activePath.width-pointWidth/2, y:activePath.y-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:2,activePath:activePath, resizeFun:(x,y)=>{drawRect(activePath.x, y ,x-activePath.x, activePath.height+ activePath.y - y);} }, { x:activePath.x+activePath.width-pointWidth/2, y:activePath.y+activePath.height/2-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:3,activePath:activePath, resizeFun:(x,y)=>{ drawRect(activePath.x, activePath.y ,x-activePath.x, activePath.height); } }, { x:activePath.x+activePath.width-pointWidth/2, y:activePath.y+activePath.height-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:4,activePath:activePath, resizeFun:(x,y)=>{ drawRect(activePath.x, activePath.y ,x-activePath.x, y-activePath.y); } }, { x:activePath.x+activePath.width/2-pointWidth/2, y:activePath.y+activePath.height-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:5,activePath:activePath, resizeFun:(x,y)=>{ drawRect(activePath.x, activePath.y ,activePath.width, y-activePath.y); } }, { x:activePath.x-pointWidth/2, y:activePath.y+activePath.height-pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:6,activePath:activePath, resizeFun:(x,y)=>{ drawRect(x, activePath.y ,activePath.x-x+activePath.width, y-activePath.y); } }, { x:activePath.x-pointWidth/2, y:activePath.y+activePath.height/2-pointHeight/2, width:10,height:10,ponitFillStyle, type:activePath.type,id:7,activePath:activePath, resizeFun:(x,y)=>{ drawRect(x, activePath.y ,activePath.x-x+activePath.width, activePath.height); } }, ]; // drawResizePoint( activePath.points); break; case 'ellipse': activePath.points = [ { x:activePath.x - pointWidth/2, y:activePath.y - activePath.radiusY - pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,activePath.radiusX,Math.abs(y-activePath.y));}, }, { x:activePath.x + activePath.radiusX - pointWidth/2, y:activePath.y - pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,Math.abs(x - activePath.x),activePath.radiusY);}, }, { x:activePath.x - pointWidth/2, y:activePath.y + activePath.radiusY - pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,activePath.radiusX,Math.abs(y-activePath.y));}, }, { x:activePath.x - activePath.radiusX - pointWidth/2, y:activePath.y - pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, resizeFun:(x,y)=>{drawEllipse(activePath.x,activePath.y,Math.abs(x - activePath.x),activePath.radiusY);}, }, ] // drawResizePoint( activePath.points); break; case 'line': activePath.points = [ { x:activePath.startX - pointWidth/2, y:activePath.startY - pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, resizeFun:(x,y)=>{drawLine(x,y,activePath.x,activePath.y);}, }, { x:activePath.x - pointWidth/2, y:activePath.y - pointHeight/2, width:10,height:10,ponitFillStyle,type:activePath.type,id:0,activePath:activePath, resizeFun:(x,y)=>{drawLine(activePath.startX,activePath.startY,x,y);}, } ] // drawResizePoint( activePath.points); break; } drawResizePoint( activePath.points); } //画resize的点 const drawResizePoint = (points)=>{ resizePointList = []; for(let i=0;i<points.length;i++){ let resizePoint = points[i]; let path = new Path2D(); topCtx.beginPath(); topCtx.moveTo(resizePoint.x,resizePoint.y); path.rect(resizePoint.x,resizePoint.y,resizePoint.width,resizePoint.height); topCtx.strokeStyle = resizePoint.ponitFillStyle; topCtx.stroke(path); resizePoint.path = path; resizePointList.push(resizePoint) } } //画椭圆形 const drawEllipse = (x,y,radiusX,radiusY)=>{ pathObj = null; //清除topCtx画布 topCtx.clearRect(0,0,canvasWidth,canvasHeight); topCtx.beginPath(); let path = new Path2D(); // 椭圆 path.ellipse(x,y,radiusX,radiusY,0,0, Math.PI*2,true); topCtx.strokeStyle = strokeStyle; topCtx.stroke(path); pathObj = { type:'ellipse', shape:'stroke', path, x, y, radiusX, radiusY, lineWidth:topCtx.lineWidth||lineWidth, strokeStyle:topCtx.strokeStyle||strokeStyle, points:[], }; } //画矩形 const drawRect = (x,y,width,height)=>{ pathObj = null; //清除topCtx画布 topCtx.clearRect(0,0,canvasWidth,canvasHeight); topCtx.beginPath(); let path = new Path2D(); // 矩形 //这里需要确认起始点和结束点 //即 不管画出的矩形是向上,向下 左上角点为起始点,做下角点为结束点 let beginX = width<0?x+width:x; let beginY = height<0?y+height:y path.rect(beginX,beginY,Math.abs(width),Math.abs(height)); topCtx.strokeStyle = strokeStyle; topCtx.stroke(path); pathObj = { type:'rect', shape:'stroke', path, x:beginX, y:beginY, width:Math.abs(width), height:Math.abs(height), lineWidth:topCtx.lineWidth||lineWidth, strokeStyle:topCtx.strokeStyle||strokeStyle, points:[], }; } //橡皮擦 const drawEraser = (x,y)=>{ //橡皮擦圆形半径 const radius = lineWidth/2; botCtx.beginPath(); for(let i=0;i<radius*2;i++){ //勾股定理高h let h = Math.abs( radius - i); //i>radius h = i-radius; i<radius h = radius - i //勾股定理l let l = Math.sqrt(radius*radius -h*h); //矩形高度 let rectHeight = 1; //矩形宽度 let rectWidth = 2*l; //矩形X let rectX = x-l; //矩形Y let rectY = y-radius + i; botCtx.clearRect(rectX, rectY, rectWidth, rectHeight); } } //画透明度直线 const drawLine = (startX,startY,x,y)=>{ if(!isDown)return; pathObj = null; //清空当前画布内容 topCtx.clearRect(0,0,canvasWidth,canvasHeight); //必须每次都beginPath 不然会卡 topCtx.beginPath(); let path = new Path2D(); path.moveTo(startX,startY); path.lineTo(x,y); topCtx.strokeStyle = strokeStyle; topCtx.stroke(path); pathObj = { type:'line', shape:'stroke', path, x, y, startX, startY, lineWidth:topCtx.lineWidth||lineWidth, strokeStyle:topCtx.strokeStyle||strokeStyle, points:[], }; } //画带透明度涂鸦 const drawCurve = (drawPointsParams)=>{ // drawPoints.push({x,y}); if(!drawPointsParams||drawPointsParams.length<1)return pathObj = null; //清空当前画布内容 topCtx.clearRect(0,0,canvasWidth,canvasHeight); //必须每次都beginPath 不然会卡 topCtx.beginPath(); let path = new Path2D(); path.moveTo(drawPointsParams[0].x,drawPointsParams[0].y); for(let i=1;i<drawPointsParams.length;i++){ path.lineTo(drawPointsParams[i].x,drawPointsParams[i].y); } topCtx.strokeStyle = strokeStyle; topCtx.stroke(path); pathObj = { type:'curve', shape:'stroke', path, drawPoints:drawPointsParams, lineWidth:topCtx.lineWidth||lineWidth, strokeStyle:topCtx.strokeStyle||strokeStyle }; } //切换操作 const changeType = (type,that)=>{ // if(drawType == type) return; let tools = document.getElementsByClassName('img_tool'); for(let i=0;i<tools.length;i++){ let ele = tools[i]; if(ele.classList.contains('img_tool_active'))ele.classList.remove('img_tool_active'); } that.classList.add('img_tool_active'); drawType = type; //撤销 if(drawType == 'revoke'){ if(historyIndex>0){ historyIndex--; drawImage(historyList[historyIndex]); } //恢复 }else if(drawType == 'restore'){ if(historyIndex<historyList.length - 1){ historyIndex++; drawImage(historyList[historyIndex]); } } } const drawImage = (img)=>{ botCtx.clearRect(0,0,canvasWidth,canvasHeight); botCtx.drawImage(img,0,0); } //canvas添加鼠标事件 topCan.addEventListener('mousedown',mousedown); topCan.addEventListener('mousemove',mousemove); topCan.addEventListener('mouseup',mouseup); //全局添加鼠标抬起事件 document.addEventListener('mouseup',(e)=>{ let x = (e||window.event).offsetX; let y = (e||window.event).offsetY; let classList = (e.target || {}).classList || []; if(classList.contains('img_tool'))return; if(!isCanUp){ isDown = false; // topCan内容画到botCan上 if(isDrag){ isDrag = false; activePath = null; if(isDragMove){ isDragMove = false; pathList.pop(); }else{ pathObj = pathList.pop(); } topToBot(); return } if(drawType == 'line'&&!isDrag){ let clientX = topCan.getBoundingClientRect().x; let clientY = topCan.getBoundingClientRect().y; drawLine(startPoint.x,startPoint.y,x-clientX,y-clientY); } // topCan内容画到botCan上 topToBot(); } }); //全局添加鼠标移动事件 document.addEventListener('mousemove',(e)=>{ if(isMove)return isMove = false; let x = (e||window.event).offsetX; let y = (e||window.event).offsetY; if(drawType == 'line'&&!isDrag){ let clientX = topCan.getBoundingClientRect().x; let clientY = topCan.getBoundingClientRect().y; drawLine(startPoint.x,startPoint.y,x-clientX,y-clientY); } }); </script> </body> </html>
踩坑路漫漫长@~@
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。