原生JS使用Canvas实现拖拽式绘图功能
一、实现的功能
1、基于oop思想构建,支持坐标点、线条(由坐标点组成,包含方向)、多边形(由多个坐标点组成)、圆形(包含圆心坐标点和半径)等实体
2、原生JavaScript实现,不依赖任何第三方js库和插件
3、多图形绘制(支持画笔、线条、箭头、三角形、矩形、平行四边形、梯形以及多边形和圆形绘制)
4、拖拽式绘制(鼠标移动过程中不断进行canvas重绘)
5、图片绘制(作为背景图片时重绘会发生闪烁现象,暂时有点问题,后面继续完善)
5、清空绘制功能
6、新版本优化绘制性能(使用共享坐标变量数组,减少了大量的对象创建操作)
7、新版本支持箭头绘制功能
二、完整实现代码
DrawingTools=(function(){ //公共方法 vargetDom=function(id){returndocument.getElementById(id)}; varisNull=function(s){returns==undefined||typeof(s)=='undefined'||s==null||s=='null'||s==''||s.length<1}; varhideDefRM=function(){document.oncontextmenu=function(){returnfalse}};//屏蔽浏览器默认鼠标事件 /**绘图容器*/ varcbtCanvas; /**绘图对象*/ varcxt; /**绘制的图形列表*/ varshapes=newArray(); vargraphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42}; //背景图片绘制配置 varbgPictureConfig={ pic:null,//背景图片地址或路径 repaint:true,//是否作为永久背景图,每次清除时会进行重绘 }; //加载并绘制图片(src:图片路径或地址),默认重绘背景图 varloadPicture=function(src){ if(isNull(bgPictureConfig.repaint)||bgPictureConfig.repaint){bgPictureConfig.pic=src} varimg=newImage(); img.onload=function(){cxt.drawImage(img,0,0)} img.src=src; } //绘图基础配置 varpaintConfig={lineWidth:1,//线条宽度,默认1 strokeStyle:'red',//画笔颜色,默认红色 fillStyle:'red',//填充色 lineJoin:"round",//线条交角样式,默认圆角 lineCap:"round",//线条结束样式,默认圆角 }; //重新载入绘制样式 varresetStyle=function(){ cxt.strokeStyle=paintConfig.strokeStyle; cxt.lineWidth=paintConfig.lineWidth; cxt.lineJoin=paintConfig.lineJoin; cxt.lineCap=paintConfig.lineCap; cxt.fillStyle=paintConfig.fillStyle; } //鼠标图形 varcursors=['crosshair','pointer']; /**切换鼠标样式*/ varswitchCorser=function(b){ cbtCanvas.style.cursor=((isNull(b)?isDrawing():b)?cursors[0]:cursors[1]); } //操作控制变量组 varctrlConfig={ kind:0,//当前绘画分类 isPainting:false,//是否开始绘制 startPoint:null,//起始点 cuGraph:null,//当前绘制的图像 cuPoint:null,//当前临时坐标点,确定一个坐标点后重新构建 cuAngle:null,//当前箭头角度 vertex:[],//坐标点 } /**获取当前坐标点*/ vargetCuPoint=function(i){ returnctrlConfig.cuPoint[i]; } /**设置当前坐标点*/ varsetCuPoint=function(p,i){ returnctrlConfig.cuPoint[i]=p; } /**设置当前临时坐标点值*/ varsetCuPointXY=function(x,y,i){ if(isNull(ctrlConfig.cuPoint)){ vararr=newArray(); arr[i]=newPoint(x,y); ctrlConfig.cuPoint=arr; }elseif(isNull(ctrlConfig.cuPoint[i])){ setCuPoint(newPoint(x,y),i); }else{ ctrlConfig.cuPoint[i].setXY(x,y); } returngetCuPoint(i); } /**是否正在绘制*/ varisDrawing=function(){ returnctrlConfig.isPainting; } /**开始绘制状态*/ varbeginDrawing=function(){ ctrlConfig.isPainting=true; } /**结束绘制状态*/ varstopDrawing=function(){ ctrlConfig.isPainting=false; } /**是否有开始坐标点*/ varhasStartPoint=function(){ return!isNull(ctrlConfig.startPoint); } /**设置当前绘制的图形*/ varsetCuGraph=function(g){ ctrlConfig.cuGraph=g; } /**获取当前绘制的图形*/ vargetCuGraph=function(){ returnctrlConfig.cuGraph; } /**设置开始坐标点(线条的起始点,三角形的顶点,圆形的圆心,四边形的左上角或右下角,多边形的起始点)*/ varsetStartPoint=function(p){ ctrlConfig.startPoint=p; } /**获取开始坐标点*/ vargetStartPoint=function(){ returnctrlConfig.startPoint; } /**清空全部*/ varclearAll=function(){ cxt.clearRect(0,0,cbtCanvas.width,cbtCanvas.height); } /**重绘*/ varrepaint=function(){ clearAll(); /* if(bgPictureConfig.repaint){ loadPicture(bgPictureConfig.pic); }*/ } /**点(坐标,绘图的基本要素,包含x,y坐标)*/ varPoint=(function(x1,y1){ varx=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(){ returnx; }, getY:function(){ returny; } } }); /**多角形(三角形、矩形、多边形),由多个点组成*/ varPoly=(function(ps1){ varps=isNull(ps1)?newArray():ps1; varsize=ps.length; return{ set:function(ps2){ ps=ps2; }, getSize:function(){ returnsize; }, setPoint:function(p,i){ if(isNull(p)&&isNaN(i)){ return; } ps[i]=p; }, setStart:function(p1){ if(isNull(ps)){ ps=newArray(); returnps.push(p1); }else{ ps[0]=p1; } }, add:function(p){ if(isNull(ps)){ ps=newArray(); } returnps.push(p); }, pop:function(){ if(isNull(ps)){ return; } returnps.pop(); }, shift:function(){ if(isNull(ps)){ return; } returnps.shift; }, get:function(){ if(isNull(ps)){ returnnull; } returnps; }, draw:function(){ cxt.beginPath(); for(iinps){ if(i==0){ cxt.moveTo(ps[i].getX(),ps[i].getY()); }else{ cxt.lineTo(ps[i].getX(),ps[i].getY()); } } cxt.closePath(); cxt.stroke(); } } }); /*线条(由两个点组成,包含方向)*/ varLine=(function(p1,p2,al){ varstart=p1,end=p2,angle=al; vardrawLine=function(){ cxt.beginPath(); cxt.moveTo(p1.getX(),p1.getY()); cxt.lineTo(p2.getX(),p2.getY()); cxt.stroke(); } //画箭头 vardrawArrow=function(){ varvertex=ctrlConfig.vertex; varx1=p1.getX(),y1=p1.getY(),x2=p2.getX(),y2=p2.getY(); varel=50,al=15; //计算箭头底边两个点(开始点,结束点,两边角度,箭头角度) vertex[0]=x1,vertex[1]=y1,vertex[6]=x2,vertex[7]=y2; //计算起点坐标与X轴之间的夹角角度值 varangle=Math.atan2(y2-y1,x2-x1)/Math.PI*180; varx=x2-x1,y=y2-y1,length=Math.sqrt(Math.pow(x,2)+Math.pow(y,2)); if(length<250){ el/=2,al/2; }elseif(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(){ returnstart; }, getEnd:function(){ returnend; }, draw:function(){ if(angle){ drawArrow(); }else{ drawLine(); } } } }); /**圆形(包含圆心点和半径)*/ varCircle=(function(arr){ //包含起始点(圆心)和结束点,以及圆半径 varstartPoint=arr.start,endPoint=arr.end,radius=arr.radius; /*绘制圆*/ vardrawCircle=function(){ cxt.beginPath(); varx=startPoint.getX(); vary=startPoint.getY(); if(isNull(radius)){ radius=calculateRadius(startPoint,endPoint); } //x,y,半径,开始点,结束点,顺时针/逆时针 cxt.arc(x,y,radius,0,Math.PI*2,false);//绘制圆 cxt.stroke(); } //计算圆半径 varcalculateRadius=function(p1,p2){ varwidth=p2.getX()-p1.getX(); varheight=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)); returnc; } return{ set:function(params){ startPoint=params.start; endPoint=params.end; radius=params.radius; }, setPoint:function(p1){ p=p1; }, getPoint:function(){ returnp; }, setRadius:function(r1){ radius=r1; }, getRadius:function(){ returnradius; }, calcRadius:calculateRadius, //绘制 draw:drawCircle, } }); /**绘制线条工具方法*/ vardrawLine=function(p){ cxt.beginPath(); cxt.moveTo(startPosition.getX(),startPosition.getY()); cxt.lineTo(p.getX(),p.getY()); cxt.stroke(); } /**绘制三角形工具方法*/ vardrawTrian=function(ps){ cxt.beginPath(); vara=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(); } /**绘制矩形工具方法*/ vardrawRect=function(p2){ varp=getStartPoint(); varwidth=p.getX()-p2.getX(); varheight=p.getY()-p2.getY(); cxt.beginPath(); cxt.strokeRect(x,y,width,height);//绘制矩形 } /*绘制多边形工具方法*/ vardrawpolygon=function(ps){ if(ps.length>1){//保证只有两个坐标点才是矩形 cxt.beginPath(); varp=ctrlConfig.startPoint; varx=p.getX(); vary=p.getY(); cxt.moveTo(x,y); for(p1inps){ cxt.lineTo(p1.getX(),p1.getY()); } cxt.stroke(); } } /*绘制圆角矩形工具方法*/ vardrawRoundedRect=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(); } /*绘制圆工具方法*/ vardrawCircle=function(c){ varp=c.getPoint();//坐标点 varx=p.getX(); vary=p.getY(); varr=c.getRadius(); cxt.beginPath(); //x,y,半径,开始点,结束点,顺时针/逆时针 cxt.arc(x,y,r,0,Math.PI*2,false);//绘制圆 cxt.stroke(); } //计算圆半径工具方法 varcalculateRadius=function(p1,p2){ varwidth=p2.getX()-p1.getX(); varheight=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)); returnc; } //鼠标按键点击(首次点击确定开始坐标点,拖动鼠标不断进行图形重绘) varmouseDown=function(e){ varbtnNum=e.button; if(btnNum==0){ console.log("选择:"+ctrlConfig.kind); //设置起始点 switch(ctrlConfig.kind){ casegraphkind.pen://画笔(不松开鼠标按键一直画) beginDrawing();//开始绘制 cxt.beginPath(); cxt.moveTo(e.offsetX,e.offsetY); break; casegraphkind.poly://多边形 varp=newPoint(e.offsetX,e.offsetY); if(isDrawing()){ getCuGraph().add(p);//添加到 }else{//第一次确定开始坐标 beginDrawing();//开始绘制 setStartPoint(p); varpoly=newPoly(); poly.add(p); setCuGraph(poly);//设置当前绘制图形 } break; casegraphkind.line://线条 casegraphkind.arrow://方向 casegraphkind.trian://三角形 casegraphkind.rect://矩形 casegraphkind.parallel://平行四边形 casegraphkind.trapezoid://梯形 beginDrawing();//开始绘制 varp=newPoint(e.offsetX,e.offsetY); setStartPoint(p); varpoly=newPoly(); poly.add(p); setCuGraph(poly);//设置当前绘制图形 break; casegraphkind.circle://圆 console.log("确定图形绘制开始坐标点:"+e.offsetX+","+e.offsetY);//点击确定图形的开始坐标点 beginDrawing();//开始绘制 varp=newPoint(e.offsetX,e.offsetY); setStartPoint(p); varcircle=newCircle({'start':p}); setCuGraph(circle); break; casectrlConfig.cursor://手型鼠标 default://默认是手型鼠标,不允许绘制 } }elseif(btnNum==2){ console.log("右键由于结束多边形绘制"); if(isDrawing()){ if(ctrlConfig.kind==graphkind.poly){ repaint(); getCuGraph().draw(); stopDrawing();//结束绘制 } } } hideDefRM();//屏蔽浏览器默认事件 } //鼠标移动(拖动,根据鼠标移动的位置不断重绘图形) varmouseMove=function(e){ if(isDrawing()&&hasStartPoint()){//检查是否开始绘制,检查是否有开始坐标点 //画笔不需要重绘 if(ctrlConfig.kind>1){ repaint();//重绘 } varp=setCuPointXY(e.offsetX,e.offsetY,0);//设置共享的临时坐标点,用于防止重复创建对象 switch(ctrlConfig.kind){ casegraphkind.pen://画笔(一直画) cxt.lineTo(e.offsetX,e.offsetY); cxt.stroke(); break; casegraphkind.poly://多边形 varpoly=getCuGraph(poly); varsize=poly.getSize(); poly.setPoint(p,(size-1)); poly.draw(); break; casegraphkind.line://线条 varline=newLine(getStartPoint(),p,false); ctrlConfig.cuGraph=line; line.draw(); break; casegraphkind.arrow://方向 varline=newLine(getStartPoint(),p,true); ctrlConfig.cuGraph=line; line.draw(); break; casegraphkind.trian://三角形 varlu=getStartPoint(); varx2=p.getX(); varx1=lu.getX(); //三角形左边的点坐标计算方法:(x1-(x2-x1),y2) varx3=x1-(x2-x1); varl=setCuPointXY(x3,p.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象 varpoly=getCuGraph();//获取当前图形 poly.set([lu,p,l]); poly.draw();//即时绘制 break; casegraphkind.parallel://平行四边形 varlu=getStartPoint(); varx3=p.getX(); varx1=lu.getX(); //平行四边形两个未知坐标点计算方法:(x1-(x3-x1),y3),(x1+(x3-x1),y1) varx2=x3+(x3-x1); varx4=x1-(x3-x1); varld=setCuPointXY(x2,lu.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象 varru=setCuPointXY(x4,p.getY(),2);//设置共享的临时坐标点,用于防止重复创建对象 varpoly=getCuGraph();//获取当前图形 poly.set([lu,ru,p,ld]); poly.draw();//即时绘制 break; casegraphkind.trapezoid://梯形 varlu=getStartPoint(); varx3=p.getX(); varx1=lu.getX(); //梯形两个未知坐标点计算方法:(x3-(x3-x1)/2,y1),(x1-(x3-x1)/2,y3) varx2=x3-(x3-x1)/2; varx4=x1-(x3-x1)/2; varld=setCuPointXY(x2,lu.getY(),1); varru=setCuPointXY(x4,p.getY(),2); varpoly=getCuGraph(); poly.set([lu,ru,p,ld]); poly.draw(); break; casegraphkind.rect://矩形 varlu=getStartPoint(); //矩形右上角和左上角坐标计算方法 varld=setCuPointXY(lu.getX(),p.getY(),1); varru=setCuPointXY(p.getX(),lu.getY(),2); varpoly=getCuGraph(); poly.set([lu,ru,p,ld]); poly.draw(); break; casegraphkind.circle://圆 varcircle=getCuGraph();//获取当前图形 circle.set({'start':getStartPoint(),'end':p}); circle.draw();//即时绘制 break; } } } //鼠标按键松开 varmouseUp=function(e){ if(isDrawing()){ //console.log("松开鼠标按键:"+e.offsetX+","+e.offsetY); //画笔不需要重绘 if(ctrlConfig.kind>1){ repaint(); getCuGraph().draw(); } if(ctrlConfig.kind!=graphkind.poly){//多边形绘制鼠标按键松开不结束绘制,多边形只有右键点击才能结束绘制 stopDrawing();//结束绘制 } } } //鼠标移出 varmouseOut=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();//载入样式 returntrue; }else{ returnfalse; } }, /**设置背景图片*/ 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:梯形
vargraphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42};
2、初始化以及使用背景图片和画笔选择
vardrawUtil=newDrawingTools(); //初始化,(如果浏览器不支持H5,会初始化失败,返回false) if(drawUtil.init({'id':'calibrationCanvas'})){ //加载图片 varimgsrc='图片地址'; if(!drawUtil.isNull(imgsrc)){ drawUtil.setBgPic(imgsrc,true);//设置背景图片(异步加载图片) } } drawUtil.begin(1);//选择画笔
2、绘制箭头
drawUtil.begin(21);
总结
以上所述是小编给大家介绍的原生JS使用Canvas实现拖拽式绘图功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!