扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
怎么在JavaScript中使用Canvas实现一个拖拽绘图功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
成都创新互联提供做网站、网站制作、网页设计,高端网站设计,广告投放平台等致力于企业网站建设与公司网站制作,十载的网站开发和建站经验,助力企业信息化建设,成功案例突破数千家,是您实现网站建设的好选择.
一、实现的功能
1、基于oop思想构建,支持坐标点、线条(由坐标点组成,包含方向)、多边形(由多个坐标点组成)、圆形(包含圆心坐标点和半径)等实体
2、原生JavaScript实现,不依赖任何第三方js库和插件
3、多图形绘制(支持画笔、线条、箭头、三角形、矩形、平行四边形、梯形以及多边形和圆形绘制)
4、拖拽式绘制(鼠标移动过程中不断进行canvas重绘)
5、图片绘制(作为背景图片时重绘会发生闪烁现象,暂时有点问题,后面继续完善)
5、清空绘制功能
6、新版本优化绘制性能(使用共享坐标变量数组,减少了大量的对象创建操作)
7、新版本支持箭头绘制功能
二、完整实现代码
DrawingTools =(function(){ //公共方法 var getDom=function(id){return document.getElementById(id)}; var isNull=function(s){return s==undefined||typeof(s)=='undefined'||s==null||s=='null'||s==''||s.length<1}; var hideDefRM=function(){document.oncontextmenu=function(){return false}};//屏蔽浏览器默认鼠标事件 /**绘图容器*/ var cbtCanvas; /**绘图对象*/ var cxt; /**绘制的图形列表*/ var shapes=new Array(); var graphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42}; //背景图片绘制配置 var bgPictureConfig={ pic:null,//背景图片地址或路径 repaint:true,//是否作为永久背景图,每次清除时会进行重绘 }; //加载并绘制图片(src:图片路径或地址),默认重绘背景图 var loadPicture=function(src){ if(isNull(bgPictureConfig.repaint)||bgPictureConfig.repaint){bgPictureConfig.pic=src} var img = new Image(); img.onload = function(){cxt.drawImage(img,0,0)} img.src =src; } //绘图基础配置 var paintConfig={lineWidth:1,//线条宽度,默认1 strokeStyle:'red',//画笔颜色,默认红色 fillStyle:'red',//填充色 lineJoin:"round",//线条交角样式,默认圆角 lineCap:"round",//线条结束样式,默认圆角 }; //重新载入绘制样式 var resetStyle=function(){ cxt.strokeStyle=paintConfig.strokeStyle; cxt.lineWidth=paintConfig.lineWidth; cxt.lineJoin=paintConfig.lineJoin; cxt.lineCap=paintConfig.lineCap; cxt.fillStyle=paintConfig.fillStyle; } //鼠标图形 var cursors=['crosshair','pointer']; /** 切换鼠标样式*/ var switchCorser=function(b){ cbtCanvas.style.cursor=((isNull(b)?isDrawing():b)?cursors[0]:cursors[1]); } //操作控制变量组 var ctrlConfig={ kind:0,//当前绘画分类 isPainting:false,//是否开始绘制 startPoint:null,//起始点 cuGraph:null,//当前绘制的图像 cuPoint:null,//当前临时坐标点,确定一个坐标点后重新构建 cuAngle:null,//当前箭头角度 vertex:[],//坐标点 } /**获取当前坐标点*/ var getCuPoint=function(i){ return ctrlConfig.cuPoint[i]; } /**设置当前坐标点*/ var setCuPoint=function(p,i){ return ctrlConfig.cuPoint[i]=p; } /**设置当前临时坐标点值*/ var setCuPointXY=function(x,y,i){ if(isNull(ctrlConfig.cuPoint)){ var arr=new Array(); arr[i]=new Point(x,y); ctrlConfig.cuPoint=arr; }else if(isNull(ctrlConfig.cuPoint[i])){ setCuPoint(new Point(x,y),i); }else{ ctrlConfig.cuPoint[i].setXY(x,y); } return getCuPoint(i); } /**是否正在绘制*/ var isDrawing=function (){ return ctrlConfig.isPainting; } /**开始绘制状态*/ var beginDrawing=function(){ ctrlConfig.isPainting=true; } /**结束绘制状态*/ var stopDrawing=function(){ ctrlConfig.isPainting=false; } /**是否有开始坐标点*/ var hasStartPoint=function(){ return !isNull(ctrlConfig.startPoint); } /**设置当前绘制的图形*/ var setCuGraph=function(g){ ctrlConfig.cuGraph=g; } /**获取当前绘制的图形*/ var getCuGraph=function(){ return ctrlConfig.cuGraph; } /**设置开始坐标点(线条的起始点,三角形的顶点,圆形的圆心,四边形的左上角或右下角,多边形的起始点)*/ var setStartPoint=function(p){ ctrlConfig.startPoint=p; } /**获取开始坐标点*/ var getStartPoint=function(){ return ctrlConfig.startPoint; } /**清空全部*/ var clearAll=function(){ cxt.clearRect(0,0,cbtCanvas.width,cbtCanvas.height); } /**重绘*/ var repaint=function(){ clearAll(); /* if(bgPictureConfig.repaint){ loadPicture(bgPictureConfig.pic); }*/ } /**点(坐标,绘图的基本要素,包含x,y坐标)*/ var Point=(function(x1,y1){ var x=x1,y=y1; return{ set:function(p){ x=p.x,y=p.y; }, setXY:function(x2,y2){ x=x2;y=y2; }, setX:function(x3){ x=x3; }, setY:function(y3){ y=y3; }, getX:function(){ return x; }, getY:function(){ return y; } } }); /**多角形(三角形、矩形、多边形),由多个点组成*/ var Poly=(function(ps1){ var ps=isNull(ps1)?new Array():ps1; var size=ps.length; return{ set:function(ps2){ ps=ps2; }, getSize:function(){ return size; }, setPoint:function(p,i){ if(isNull(p)&&isNaN(i)){ return; } ps[i]=p; }, setStart:function(p1){ if(isNull(ps)){ ps=new Array(); return ps.push(p1); }else{ ps[0]=p1; } }, add:function(p){ if(isNull(ps)){ ps=new Array(); } return ps.push(p); }, pop:function(){ if(isNull(ps)){ return; } return ps.pop(); }, shift:function(){ if(isNull(ps)){ return; } return ps.shift; }, get:function(){ if(isNull(ps)){ return null; } return ps; }, draw:function(){ cxt.beginPath(); for(i in ps){ if(i==0){ cxt.moveTo(ps[i].getX(),ps[i].getY()); }else{ cxt.lineTo(ps[i].getX(),ps[i].getY()); } } cxt.closePath(); cxt.stroke(); } } }); /*线条(由两个点组成,包含方向)*/ var Line=(function(p1,p2,al){ var start=p1,end=p2,angle=al; var drawLine=function(){ cxt.beginPath(); cxt.moveTo(p1.getX(),p1.getY()); cxt.lineTo(p2.getX(),p2.getY()); cxt.stroke(); } //画箭头 var drawArrow=function() { var vertex =ctrlConfig.vertex; var x1=p1.getX(),y1=p1.getY(),x2=p2.getX(),y2=p2.getY(); var el=50,al=15; //计算箭头底边两个点(开始点,结束点,两边角度,箭头角度) vertex[0] = x1,vertex[1] = y1, vertex[6] = x2,vertex[7] = y2; //计算起点坐标与X轴之间的夹角角度值 var angle = Math.atan2(y2 - y1, x2 - x1) / Math.PI * 180; var x = x2 - x1,y = y2 - y1,length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); if (length < 250) { el/=2,al/2; }else if(length<500){ el*=length/500,al*=length/500; } vertex[8] = x2 - el * Math.cos(Math.PI / 180 * (angle + al)); vertex[9] = y2- el * Math.sin(Math.PI / 180 * (angle + al)); vertex[4] = x2- el* Math.cos(Math.PI / 180 * (angle - al)); vertex[5] = y2 - el * Math.sin(Math.PI / 180 * (angle - al)); //获取另外两个顶点坐标 x=(vertex[4]+vertex[8])/2,y=(vertex[5]+vertex[9])/2; vertex[2] = (vertex[4] + x) / 2; vertex[3] = (vertex[5] + y) / 2; vertex[10] = (vertex[8] +x) / 2; vertex[11] = (vertex[9] +y) / 2; //计算完成,开始绘制 cxt.beginPath(); cxt.moveTo(vertex[0], vertex[1]); cxt.lineTo(vertex[2], vertex[3]); cxt.lineTo(vertex[4], vertex[5]); cxt.lineTo(vertex[6], vertex[7]); cxt.lineTo(vertex[8], vertex[9]); cxt.lineTo(vertex[10], vertex[11]); cxt.closePath(); cxt.fill(); cxt.stroke(); } return{ setStart:function(s){ start=s; }, setEnd:function(e){ end=e; }, getStart:function(){ return start; }, getEnd:function(){ return end; }, draw:function(){ if(angle){ drawArrow(); }else{ drawLine(); } } } }); /**圆形(包含圆心点和半径)*/ var Circle=(function(arr){ //包含起始点(圆心)和结束点,以及圆半径 var startPoint=arr.start,endPoint=arr.end,radius=arr.radius; /*绘制圆*/ var drawCircle=function(){ cxt.beginPath(); var x=startPoint.getX(); var y=startPoint.getY(); if(isNull(radius)){ radius=calculateRadius(startPoint,endPoint); } //x,y,半径,开始点,结束点,顺时针/逆时针 cxt.arc(x,y,radius,0,Math.PI*2,false); // 绘制圆 cxt.stroke(); } //计算圆半径 var calculateRadius=function(p1,p2){ var width=p2.getX()-p1.getX(); var height=p2.getY()-p1.getY(); //如果是负数 if(width<0||height<0){ width=Math.abs(width); } //计算两点距离=平方根(width^2+height^2) c=Math.sqrt(Math.pow(width,2)+Math.pow(height,2)); return c; } return{ set:function(params){ startPoint=params.start; endPoint=params.end; radius=params.radius; }, setPoint:function(p1){ p=p1; }, getPoint:function(){ return p; }, setRadius:function(r1){ radius=r1; }, getRadius:function(){ return radius; }, calcRadius:calculateRadius, //绘制 draw:drawCircle, } }); /**绘制线条工具方法*/ var drawLine=function(p){ cxt.beginPath(); cxt.moveTo(startPosition.getX(),startPosition.getY()); cxt.lineTo(p.getX(),p.getY()); cxt.stroke(); } /**绘制三角形工具方法*/ var drawTrian=function(ps){ cxt.beginPath(); var a=ps.get(); cxt.moveTo(a[0].getX(),a[0].getY()); cxt.lineTo(a[1].getX(),a[1].getY()); cxt.lineTo(a[2].getX(),a[2].getY()); cxt.closePath(); cxt.stroke(); } /**绘制矩形工具方法*/ var drawRect=function(p2){ var p=getStartPoint(); var width=p.getX()-p2.getX(); var height=p.getY()-p2.getY(); cxt.beginPath(); cxt.strokeRect(x,y,width,height);//绘制矩形 } /*绘制多边形工具方法*/ var drawpolygon=function(ps){ if(ps.length>1){//保证只有两个坐标点才是矩形 cxt.beginPath(); var p=ctrlConfig.startPoint; var x=p.getX(); var y=p.getY(); cxt.moveTo(x,y); for(p1 in ps){ cxt.lineTo(p1.getX(),p1.getY()); } cxt.stroke(); } } /*绘制圆角矩形工具方法*/ var drawRoundedRect=function(x,y,width,height,radius){ cxt.beginPath(); cxt.moveTo(x,y+radius); cxt.lineTo(x,y+height-radius); cxt.quadraticCurveTo(x,y+height,x+radius,y+height); cxt.lineTo(x+width-radius,y+height); cxt.quadraticCurveTo(x+width,y+height,x+width,y+height-radius); cxt.lineTo(x+width,y+radius); cxt.quadraticCurveTo(x+width,y,x+width-radius,y); cxt.lineTo(x+radius,y); cxt.quadraticCurveTo(x,y,x,y+radius); cxt.stroke(); } /*绘制圆工具方法*/ var drawCircle=function(c){ var p=c.getPoint();//坐标点 var x=p.getX(); var y=p.getY(); var r=c.getRadius(); cxt.beginPath(); //x,y,半径,开始点,结束点,顺时针/逆时针 cxt.arc(x,y,r,0,Math.PI*2,false); // 绘制圆 cxt.stroke(); } //计算圆半径工具方法 var calculateRadius=function(p1,p2){ var width=p2.getX()-p1.getX(); var height=p2.getY()-p1.getY(); //如果是负数 if(width<0||height<0){ width=Math.abs(width); } //计算两点距离=平方根(width^2+height^2) c=Math.sqrt(Math.pow(width,2)+Math.pow(height,2)); return c; } //鼠标按键点击(首次点击确定开始坐标点,拖动鼠标不断进行图形重绘) var mouseDown = function(e){ var btnNum = e.button; if(btnNum==0){ console.log("选择:"+ctrlConfig.kind); //设置起始点 switch(ctrlConfig.kind){ case graphkind.pen://画笔(不松开鼠标按键一直画) beginDrawing();//开始绘制 cxt.beginPath(); cxt.moveTo(e.offsetX,e.offsetY); break; case graphkind.poly://多边形 var p=new Point(e.offsetX,e.offsetY); if(isDrawing()){ getCuGraph().add(p);//添加到 }else{//第一次确定开始坐标 beginDrawing();//开始绘制 setStartPoint(p); var poly=new Poly(); poly.add(p); setCuGraph(poly);//设置当前绘制图形 } break; case graphkind.line://线条 case graphkind.arrow://方向 case graphkind.trian://三角形 case graphkind.rect://矩形 case graphkind.parallel://平行四边形 case graphkind.trapezoid://梯形 beginDrawing();//开始绘制 var p=new Point(e.offsetX,e.offsetY); setStartPoint(p); var poly=new Poly(); poly.add(p); setCuGraph(poly);//设置当前绘制图形 break; case graphkind.circle://圆 console.log("确定图形绘制开始坐标点:"+e.offsetX+","+e.offsetY);//点击确定图形的开始坐标点 beginDrawing();//开始绘制 var p=new Point(e.offsetX,e.offsetY); setStartPoint(p); var circle= new Circle({'start':p}); setCuGraph(circle); break; case ctrlConfig.cursor: //手型鼠标 default://默认是手型鼠标,不允许绘制 } }else if(btnNum==2){ console.log("右键由于结束多边形绘制"); if(isDrawing()){ if(ctrlConfig.kind==graphkind.poly){ repaint(); getCuGraph().draw(); stopDrawing();//结束绘制 } } } hideDefRM();//屏蔽浏览器默认事件 } //鼠标移动(拖动,根据鼠标移动的位置不断重绘图形) var mouseMove = function(e){ if(isDrawing()&&hasStartPoint()){//检查是否开始绘制,检查是否有开始坐标点 //画笔不需要重绘 if(ctrlConfig.kind>1){ repaint();//重绘 } var p=setCuPointXY(e.offsetX,e.offsetY,0);//设置共享的临时坐标点,用于防止重复创建对象 switch(ctrlConfig.kind){ case graphkind.pen://画笔(一直画) cxt.lineTo(e.offsetX,e.offsetY); cxt.stroke(); break; case graphkind.poly://多边形 var poly=getCuGraph(poly); var size=poly.getSize(); poly.setPoint(p,(size-1)); poly.draw(); break; case graphkind.line://线条 var line=new Line(getStartPoint(),p,false); ctrlConfig.cuGraph=line; line.draw(); break; case graphkind.arrow://方向 var line=new Line(getStartPoint(),p,true); ctrlConfig.cuGraph=line; line.draw(); break; case graphkind.trian://三角形 var lu=getStartPoint(); var x2=p.getX(); var x1=lu.getX(); //三角形左边的点坐标计算方法:(x1-(x2-x1),y2) var x3=x1-(x2-x1); var l=setCuPointXY(x3,p.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象 var poly=getCuGraph();//获取当前图形 poly.set([lu,p,l]); poly.draw();//即时绘制 break; case graphkind.parallel://平行四边形 var lu=getStartPoint(); var x3=p.getX(); var x1=lu.getX(); //平行四边形两个未知坐标点计算方法:(x1-(x3-x1),y3),(x1+(x3-x1),y1) var x2=x3+(x3-x1); var x4=x1-(x3-x1); var ld=setCuPointXY(x2,lu.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象 var ru=setCuPointXY(x4,p.getY(),2);//设置共享的临时坐标点,用于防止重复创建对象 var poly=getCuGraph();//获取当前图形 poly.set([lu,ru,p,ld]); poly.draw();//即时绘制 break; case graphkind.trapezoid://梯形 var lu=getStartPoint(); var x3=p.getX(); var x1=lu.getX(); //梯形两个未知坐标点计算方法:(x3-(x3-x1)/2,y1),(x1-(x3-x1)/2,y3) var x2=x3-(x3-x1)/2; var x4=x1-(x3-x1)/2; var ld=setCuPointXY(x2,lu.getY(),1); var ru=setCuPointXY(x4,p.getY(),2); var poly=getCuGraph(); poly.set([lu,ru,p,ld]); poly.draw(); break; case graphkind.rect://矩形 var lu=getStartPoint(); //矩形右上角和左上角坐标计算方法 var ld=setCuPointXY(lu.getX(),p.getY(),1); var ru=setCuPointXY(p.getX(),lu.getY(),2); var poly=getCuGraph(); poly.set([lu,ru,p,ld]); poly.draw(); break; case graphkind.circle://圆 var circle=getCuGraph();//获取当前图形 circle.set({'start':getStartPoint(),'end':p}); circle.draw();//即时绘制 break; } } } //鼠标按键松开 var mouseUp = function(e){ if(isDrawing()){ //console.log("松开鼠标按键:"+e.offsetX+","+e.offsetY); //画笔不需要重绘 if(ctrlConfig.kind>1){ repaint(); getCuGraph().draw(); } if(ctrlConfig.kind!=graphkind.poly){//多边形绘制鼠标按键松开不结束绘制,多边形只有右键点击才能结束绘制 stopDrawing();//结束绘制 } } } //鼠标移出 var mouseOut = function(e){ console.log("鼠标移出绘制区域"+e.offsetX+","+e.offsetY); if(isDrawing()){ console.log("停止绘制"); if(ctrlConfig.kind>1){ repaint(); getCuGraph().draw(); } stopDrawing();//停止绘制 } } return{ isNull:isNull, getDom:getDom, clear:function(){ stopDrawing();//停止绘制 repaint(); }, /**初始化*/ init:function(params){ cbtCanvas=getDom(params.id); //浏览器是否支持Canvas if (cbtCanvas.getContext){ /**绘图对象*/ cxt=cbtCanvas.getContext("2d"); cbtCanvas.onmousedown = mouseDown; cbtCanvas.onmouseup = mouseUp; cbtCanvas.onmousemove = mouseMove; cbtCanvas.onmouseout = mouseOut; resetStyle();//载入样式 return true; }else{ return false; } }, /**设置背景图片*/ setBgPic:loadPicture, /**选择图形类型*/ begin:function(k){ console.log("选择绘制图形:"+k); if(isNaN(k)){//如果不是数字,先转换为对应字符 ctrlConfig.kind=kind[k]; }else{ ctrlConfig.kind=k; } switchCorser(true);//切换鼠标样式 }, /*手型,并停止绘图*/ hand:function(){ ctrlConfig.kind=0; stopDrawing();//停止绘制 switchCorser(false);//切换鼠标样式 } } })
三、使用方式
1、图形类型
0:鼠标,1:画笔,2:线条,3:三角形,4:矩形,5:多边形,6:圆形,21:箭头,41:平行四边形,42:梯形
var graphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42};
2、初始化以及使用背景图片和画笔选择
var drawUtil=new DrawingTools(); //初始化,(如果浏览器不支持H5,会初始化失败,返回false) if(drawUtil.init({'id':'calibrationCanvas'})){ //加载图片 var imgsrc='图片地址'; if(!drawUtil.isNull(imgsrc)){ drawUtil.setBgPic(imgsrc,true);//设置背景图片(异步加载图片) } } drawUtil.begin(1);//选择画笔
2、绘制箭头
drawUtil.begin(21);
关于怎么在JavaScript中使用Canvas实现一个拖拽绘图功能问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注创新互联行业资讯频道了解更多相关知识。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流