原生js实现Canvas实现拖拽式绘图,支持画笔、线条、箭头、三角形和圆形等等图形绘制功能,有实例Demo

  • 2019 年 11 月 1 日
  • 筆記

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/eguid_1/article/details/78473617

前言

需要用到图形绘制,没有找到完整的图形绘制实现,所以自己实现了一个 – –

演示地址:查看演示DEMO

新版本支持IE5+(你没看错,就是某软的IE浏览器)以上任意浏览器的Canvas绘图:http://blog.csdn.net/eguid_1/article/details/79310269

项目维护于github:https://github.com/eguid/graffitiCanvas

一、实现的功能

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.οncοntextmenu=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);

四、演示demo

点击这里跳转demo