您好,登錄后才能下訂單哦!
怎么在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實現一個拖拽繪圖功能問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。