原生JS使用Canvas实现拖拽式绘图功能
程序员文章站
2023-11-27 21:39:58
一、实现的功能
1、基于oop思想构建,支持坐标点、线条(由坐标点组成,包含方向)、多边形(由多个坐标点组成)、圆形(包含圆心坐标点和半径)等实体
2、原生javasc...
一、实现的功能
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);
总结
以上所述是小编给大家介绍的原生js使用canvas实现拖拽式绘图功能,希望对大家有所帮助
上一篇: 原生js通过一行代码实现简易轮播图
下一篇: 详解python中各种文件打开模式