用js控制canvas实现的模仿windows上单选、多选及拖动控制的toy program
用js控制canvas实现的模仿windows上单选、多选及拖动控制的toy program
码上有春天 发表于3年前
用js控制canvas实现的模仿windows上单选、多选及拖动控制的toy program
  • 发表于 3年前
  • 阅读 1155
  • 收藏 4
  • 点赞 1
  • 评论 1

腾讯云 新注册用户 域名抢购1元起>>>   

摘要: 用js控制canvas实现的模仿windows上单选、多选及拖动控制的toy program。

功能包括:鼠标点击单选、拖动多选、ctrl+单击组合效果、对选中的单个或多个canvas图层通过鼠标拖动、方向键移动、delete删除图层等。

感觉复杂的地方主要在控制逻辑上,下面是全部代码(带注释哦),可以直接复制保存为html文件在浏览器里查看效果

<!DOCTYPE>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<!--[if lt IE 9]><script language="javascript" type="text/javascript" src="/jqplot/excanvas.min.js"></script><![endif]-->
<script type="text/javascript">
	var list=[];
	var currentC;
	var selected=[];
	var _e={};
	var showTimer;
	var keyBoardTimer;
	var move=false;
	var hit=false;
	var inZoom=false;
	var exist=false;
	var ctrlDown=false;
	var directDown='';
    var cricle = function(x,y,r){
    	this.x=x;
    	this.y=y;
    	this.r=r;
		this.angle=10;
		this.posB={};

    	this.isCurrent=false;
		/*如果传递了flag参数,表示正在执行鼠标拖选操作,必须忽略是否有图层被选中的判断*/
    	this.drawC=function(ctx,x,y,posA){
    		ctx.save();
    		ctx.beginPath();
    		ctx.moveTo(this.x,this.y-this.r);
			ctx.arc(this.x,this.y,this.r,2*Math.PI,0,true);
			/*判断元素是否在选区中*/
			for(var i=selected.length;i--;){
				if(this===selected[i]){
					exist=true;
					console.log('exits');
				}
			}
			console.log('exitfor');
			/*1.非鼠标拖选,此时posA为空,
			*2.页面加载时x,y没有传值.(鼠标抬起) 
			*/
			if (!posA && x && y && this.isCurrent) {
					ctx.fillStyle = '#ff0000';
					currentC=this;
					this.isCurrent=true;
					if(selected.length>0){
						console.log(selected.length);
						
						if(!exist){
							console.log('notexits');
							selected.push(this);
						}
						exist=false;/*判断过后,重置元素是否在选区中的状态*/
					}else{
						selected.push(this);
					}
			}else{
				
				if(posA){/*拖选过程中,碰撞检测*/
					//console.log('1');
					/*如果是圆posB:{正对角线两点坐标,中心点坐标,旋转角度}*/
					this.posB={'x1':(this.x-this.r),'y1':(this.y-this.r),'x2':(this.x+this.r),'y2':(this.y+this.r),'x3':(this.x+this.r),'y3':(this.y-this.r),'x4':(this.x-this.r),'y4':(this.y+this.r),'x':this.x,'y':this.y,'angle':this.angle};
					/*如果是矩形posB:{正对角线两点坐标,副对角线两点坐标,中心点坐标,旋转角度}*/
					//this.posB={x1,y1,x2,y2,x3,y3,x4,y4,x,y,angle}
					var flag=testCollision(posA,this.posB);
					if(flag){
						//console.log('2');
						
						if(selected.length>0){
							
							if(!exist){
								console.log('notexits');
								selected.push(this);
							}
							exist=false;/*判断过后,重置元素是否在选区中的状态*/
						}else{
							selected.push(this);
						}
						
						console.log('selected',selected.length);
						ctx.fillStyle = '#ff0000';
					}else{
						//console.log('3');
						ctx.fillStyle = '#999999';
					}
				}else{/*既没拖选,也没选中。(页面初始加载,鼠标抬起)*/
					if(exist){
						ctx.fillStyle = '#ff0000';
					}else{
						ctx.fillStyle = '#999999';
					}
					exist=false;/*判断过后,重置元素是否在选区中的状态*/
				}

			}
			ctx.fill();
			ctx.restore();
    	}
		/*判断是否点中元素,点中了就改变当前元素*/
		this.testHit=function(ctx,x,y){
			/*先清空上一次的当前元素*/
			if(currentC){
				currentC.isCurrent=false;
				currentC=null;
			}
			ctx.save();
			ctx.beginPath();
    		ctx.moveTo(this.x,this.y-this.r);
			ctx.arc(this.x,this.y,this.r,2*Math.PI,0,true);
			if (ctx.isPointInPath(x, y)){
				hit=true;
				currentC=this;
				this.isCurrent=true;

			}
			ctx.restore();
		}
    
    }
	function draw(action){
		console.log('draw func');
		var canvas = document.getElementById('tutorial');
		canvas.height = canvas.height;//这是个什么技巧,可以清空画布
		if (canvas.getContext){
			var ctx = canvas.getContext('2d');
			console.log(['directDown',directDown]);
			if(action===undefined){/*如果是页面第一次加载*/
				for(var i=0;i<10;i++){
					var c=new cricle(20*i,20*i,5*i);
					c.drawC(ctx);//在一张画布上反复画
					list.push(c);
				}
			}else{/*如果是键盘控制移动*/
				console.log('delete');
				for(var i=0;i<list.length;i++){
					var c=list[i];
					c.drawC(ctx);
				}
			}
		}
	}
	
	function reDraw(e,posA){//有flag参数表示拖放选择操作
		console.log('reDraw func');
		if(e){
			e=e||event;
			var canvas = document.getElementById('tutorial');
			var x = e.clientX - canvas.offsetLeft;
			var y = e.clientY - canvas.offsetTop;
		}
		/*在每次拖选移动前重置selected,以保证拿到实时的选中元素*/
		if(posA){
			selected=[];
		}
		canvas.height = canvas.height;//这是个什么技巧,可以清空画布
		//var ctx = canvas.getContext('2d');
		//ctx.clearRect(0,0,canvas.width,canvas.height);
		if (canvas.getContext){
			var ctx = canvas.getContext('2d');
			for(var i=0;i<list.length;i++){
				var c=list[i];
				if(posA){
					c.drawC(ctx,x,y,posA);//拖选时
				}else{
					console.log('ininin');
					c.drawC(ctx,x,y);//非拖选时:拖动选区、元素、重画以显示当前元素
				}
			}
		}
	}
	
	function show(e){
		console.log('show func');
		
		move=true;
		e=e||event;
		var canvas = document.getElementById('tutorial');
		var ctx = canvas.getContext('2d');
		var x = e.clientX - canvas.offsetLeft;
		var y = e.clientY - canvas.offsetTop;
		var _x = _e.clientX - canvas.offsetLeft;
		var _y = _e.clientY - canvas.offsetTop;
		//showTimer=setInterval(function(e){reDraw(e);console.log('showTimer');},10,_e);//在鼠标移动时不断重画
		/*如果有选中的图层,则只改变图层中心点的坐标,由定时器showTimer控制重画所有图层
		*如果没有选中的图层,则清除showTimer定时器,执行鼠标画出跟随矩形效果
		*/

		if(!hit){
			console.log('not hit');
			console.log('dragrect');
			//clearInterval(showTimer);
			dragRect(e,_e,__e);
		}else{
			if(selected.length>1){/*如果有选区*/
				if(inZoom){
					/*拖动当前选区*/
					console.log('length>1 inzoom');
					for(var i=selected.length;i--;){
						var c=selected[i];
						c.x=c.x+(x-_x);
						c.y=c.y+(y-_y);
					}
					reDraw(e);
				}else{
					/*清空选区,拖动当前元素*/
					console.log('length>1 notinzoom');
					selected=[];
					if(currentC){
						currentC.x=currentC.x+(x-_x);
						currentC.y=currentC.y+(y-_y);
						reDraw(e)
					}
				}
			}else{/*如果没有选区*/
				/*拖动当前元素*/
				console.log('length<=1 inzoom');
				if(currentC){
					currentC.x=currentC.x+(x-_x);
					currentC.y=currentC.y+(y-_y);
					reDraw(e)
				}
			}
		}
		_e=e;
	}
	function dragRect(e,_e,__e){
		console.log('dragRect func');
		var pos={};
		var canvas = document.getElementById('tutorial');
		canvas.style.zIndex='100';
		//鼠标当前位置
		var x = e.clientX - canvas.offsetLeft;
		var y = e.clientY - canvas.offsetTop;
		//鼠标移动的前一步位置
		var _x = _e.clientX - canvas.offsetLeft;
		var _y = _e.clientY - canvas.offsetTop;
		//鼠标按下时的位置
		var __x = __e.clientX - canvas.offsetLeft;
		var __y = __e.clientY - canvas.offsetTop;
		pos.x1=x;
		pos.y1=y;
		pos.x2=__x;
		pos.y2=__y;
		reDraw(e,pos);
		var context=canvas.getContext("2d");
		//context.save();

//		context.clearRect(__x,__y,(_x-__x),(_y-__y));
//		reDraw(e,pos);
//		context.fillStyle="rgba(10%,25%,10%,0.1)";  //填充的颜色
//		context.strokeStyle="000";  //边框颜色
//		context.linewidth=1;  //边框宽
//		context.fillRect(x,y,(__x-x),(__y-y));  //填充颜色 x y坐标 宽 高
		//context.strokeRect(x,y,(__x-x),(__y-y));  //填充边框 x y坐标 宽 高
		//context.restore();
		
		context.beginPath();
        context.setLineDash([5,2]);
        context.rect(__x,__y,(_x-__x),(_y-__y));
		context.stroke();

	}

	window.onload=function(){
		
		var canvas = document.getElementById('tutorial');
		draw();//页面载入画出每个圆时,(x,y)都是在路径上,非路径内
		
		canvas.onmousedown=function(e){
			move=false;
			e=e||event;
			var x = e.clientX - canvas.offsetLeft;
			var y = e.clientY - canvas.offsetTop;
			//if(currentC)	currentC.isCurrent=false;//路径不能保存,对象还是存在的
			//currentC=null;//清空选中状态
			/*判断是否点中,点中则改变了当前元素*/
			if (canvas.getContext){
				var ctx = canvas.getContext('2d');
				for(var i=list.length;i--;){
					var c=list[i];
					c.testHit(ctx,x,y);
					if(hit) break;
				}
			}
			/*判断点中元素是否在选区中,只有selected长度大于0时才有选区概念*/
			if(selected.length>0){
				for(var i=selected.length;i--;){
					var c=selected[i];
					if(currentC===selected[i]){
						inZoom=true;
						break;
					}
				}
			}
			/*如果点中了并且没有按下ctrl键*/
			if(hit && !ctrlDown){
				
				if(inZoom){/*如果点中元素在选区中*/
					console.log(selected.length);
					console.log('inZoom');
					/*按下鼠标,只改变当前选中元素(在检测hit时已经做了),抬起鼠标时重画:清空选区,显示当前元素*/

				}else{/*如果点中元素不在选区中*/
					/*按下鼠标立刻重画:清空选区,显示当前元素*/
					console.log('notinZoom');
					selected=[];
					reDraw(e);
				}
			}else{
				/*没点中,或者按下了ctrl键,就什么都不做,留给鼠标抬起事件*/
				//selected=[];
				
			}
			
			_e=e;
			__e=e;
			
			//showTimer=setInterval(function(e){reDraw(e);},10,_e);//在鼠标移动时不断重画
			canvas.onmousemove=show;//根据鼠标移动不断改变圆心位置

			document.onmouseup=function(){
				/*如果没有移动鼠标,就清除选区,最后的重画只需画出当前元素*/
				if(!move){
					if(!hit){/*如果点空了,清除当前元素、清除选区*/
						if(currentC){
							currentC.isCurrent=false;
							currentC=null;
						}
						selected=[];
					}else{/*如果点中了*/
						hit=false;/*重置点中状态*/
						if(ctrlDown){/*如果按下了ctrl*/
							if(!inZoom){/*如果点中元素不在selected里,就添加,否则从selected里删除,并清空当前元素*/
								selected.push(currentC);
							}else{
								if(currentC){
									currentC.isCurrent=false;
									currentC=null;
								}
								console.log(['splice',selected.length]);
								selected.splice(i,1);
								console.log(['splice',selected.length]);
							}
						}else{/*如果没有按下ctrl,直接清空selected,保留当前元素*/
							selected=[];
						}
					}
					inZoom=false;
					
					reDraw(e);
				}else{
					move=false;/*如果移动了,重置移动状态*/
					hit=false;/*如果移动了,重置点中状态*/
					inZoom=false;/*如果移动了,重置是否在选区中的状态*/
					
				}
				//如果有移动,则不会清除选中状态
				//不管是否移动,解除鼠标移动的监听事件
				canvas.onmousemove=null;//只允许在鼠标按下后监听鼠标移动事件
				//reDraw(e);
				clearInterval(showTimer);//鼠标抬起后停止重画
				console.log('up');
			}

		}
		document.onkeydown=function(e){
			var ev = window.event || e;
			
			console.log(['keydown',ev.keyCode]);
			switch(ev.keyCode){
				case 17:
					
					ctrlDown=true;
					break;
				case 37:
					ev.preventDefault();
					directDown=true;
					keyboardMove(-1,0);
					break;
				case 38:
					directDown=true;
					ev.preventDefault();
					keyboardMove(0,-1);
					break;
				case 39:
					directDown=true;
					ev.preventDefault();
					keyboardMove(1,0);
					break;
				case 40:
					directDown=true;
					ev.preventDefault();
					keyboardMove(0,1);
					break;
				case 46:
					keyboardDelete();
					break;
			}
		}
		document.onkeyup=function(e){
			var ev=window.event || e;
			//ev.preventDefault();
			console.log(['keyup',ev.keyCode]);
			switch(ev.keyCode){
				case 17:
					ctrlDown=false;
					break;
				case 37:
				case 38:
				case 39:
				case 40:
					directDown=false;
					break;
				case 46:

			}
		}
	}
	/*键盘方向键控制移动*/
	function keyboardMove(x,y){
		if(selected.length>0){
			for(var i=selected.length;i--;){
				var c=selected[i];
				c.x+=x;
				c.y+=y;
			}
		}else if(currentC){
			currentC.x+=x;
			currentC.y+=y;
		}
		draw('direct');
	}
	/*delete键删除元素*/
	function keyboardDelete(){
		/*删除选区中的元素*/
		var tempList=[];
		if(selected.length>0){
			for(var j=list.length;j--;){
				var flag=true;
				for(var i=selected.length;i--;){
					if(list[j]===selected[i]){
						flag=false;
						break
					}					
				}
				if(flag){
					tempList.push(list[j]);
				}
			}
			list=tempList;
		}else if(currentC){
			for(var j=list.length;j--;){
				if(list[j]===currentC){
					list.splice(j,1);
					break
				}
			}
		}
		draw('delete');
	}
	/*碰撞检测*/
	function testCollision(posA,posB){
		//console.log(['posA',posA]);
		//console.log(['posB',posB]);
		var crossA=lineSpace(posA.x1,posA.y1,posA.x2,posA.y2);
		var crossB=lineSpace(posB.x1,posB.y1,posB.x2,posB.y2);
		var centerB={};
		var crossPos={};
		var centerA={};
		var max={};
		var min={};
		var sh={};
		var flag=false;
		centerA.x=((posA.x1-posA.x2)>0)?(posA.x1-(posA.x1-posA.x2)/2):(posA.x2-(posA.x2-posA.x1)/2);
		centerA.y=((posA.y1-posA.y2)>0)?(posA.y1-(posA.y1-posA.y2)/2):(posA.y2-(posA.y2-posA.y1)/2);
		var centerAToB=lineSpace(centerA.x,centerA.y,posB.x,posB.y);
		if(centerAToB>((crossA+crossB)/2)){
			//console.log('4');
			return flag;
		}
		if(posB.angle===0){
			//console.log('5');
			/*只需比较两矩形的对角点*/
			/*crossPos:{目标矩形:左上点,右下点,鼠标矩形:左上点,右下点}*/
			if(posA.x1>posA.x2 && posA.y1>posA.y2){
				/*左上到右下*/
				crossPos={'x1':posB.x1,'y1':posB.y1,'x2':posB.x2,'y2':posB.y2,'x3':posA.x2,'y3':posA.y2,'x4':posA.x1,'y4':posA.y1};
				flag=simpleTest(crossPos);
				return flag;
			}else if(posA.x1<posA.x2 && posA.y1<posA.y2){
				/*右下到左上*/
				//console.log('6');
				crossPos={'x1':posB.x1,'y1':posB.y1,'x2':posB.x2,'y2':posB.y2,'x3':posA.x1,'y3':posA.y1,'x4':posA.x2,'y4':posA.y2};
				flag=simpleTest(crossPos);
				//console.log(flag);
				return flag;
			}else if(posA.x1<posA.x2 && posA.y1>posA.y2){
				/*右上到左下*/
				var x1=posA.x1;
				var y1=posA.y2;
				var x2=posA.x2;
				var y2=posA.y1;
				crossPos={'x1':posB.x1,'y1':posB.y1,'x2':posB.x2,'y2':posB.y2,'x3':x1,'y3':y1,'x4':x2,'y4':y2};
				flag=simpleTest(crossPos);
				return flag;

			}else if(posA.x1>posA.x2 && posA.y1<posA.y2){
				/*左下到右上*/
				var x1=posA.x2;
				var y1=posA.y1;
				var x2=posA.x1;
				var y2=posA.y2;
				crossPos={'x1':posB.x1,'y1':posB.y1,'x2':posB.x2,'y2':posB.y2,'x3':x1,'y3':y1,'x4':x2,'y4':y2};
				flag=simpleTest(crossPos);
				return flag;
			}

		}else{
			//console.log('分离定轴定理');
			/*使用分离定轴定理解决*/
			/*目标矩形四个点到x、y轴的距离,取最大值*/
			max.Bx1=Math.max(posB.x1,posB.x2,posB.x3,posB.x4);
			max.By1=Math.max(posB.y1,posB.y2,posB.y3,posB.y4);
			min.Bx1=Math.min(posB.x1,posB.x2,posB.x3,posB.x4);
			min.By1=Math.min(posB.y1,posB.y2,posB.y3,posB.y4);
			max.Ax1=Math.max(posA.x1,posA.x2);
			max.Ay1=Math.max(posA.y1,posA.y2);
			min.Ax1=Math.min(posA.x1,posA.x2);
			min.Ay1=Math.min(posA.y1,posA.y2);


			if(max.Bx1<min.Ax1 || max.Ax1<min.Bx1 || max.By1<min.Ay1 || max.Ay1<min.By1){
				return false;
			}

			
			/*鼠标拖拽矩形四个点到目标矩形两条垂直边的距离*/
			/*使用B14的法向量*/
			sh.x=posB.y4-posB.y1;
			sh.y=posB.x1-posB.x4;
			sh.x1=posA.x1-posA.x3;
			sh.y1=posA.y1-posA.y3;
			var lineA1=getShadow(sh);
			sh.x1=posA.x1-posA.x4;
			sh.y1=posA.y1-posA.y4;
			var lineA2=getShadow(sh);
			sh.x1=posA.x2-posA.x3;
			sh.y1=posA.y2-posA.y3;
			var lineA3=getShadow(sh);
			sh.x1=posA.x2-posA.x4;
			sh.y1=posA.y2-posA.y4;
			var lineA4=getShadow(sh);
			var maxB=lineSpace(posB.x1,posB.y1,posB.x3,posB.y3);
			var maxA=Math.max(lineA1,lineA2,lineA3,lineA4);
			var minA=Math.min(lineA1,lineA2,lineA3,lineA4);
			if(maxB<minA || maxA<0){
				return false;
			}
			/*使用B13的法向量*/
			sh.x=posB.y3-posB.y1;
			sh.y=posB.x1-posB.x3;
			sh.x1=posA.x1-posA.x3;
			sh.y1=posA.y1-posA.y3;
			var lineA1=getShadow(sh);
			sh.x1=posA.x1-posA.x4;
			sh.y1=posA.y1-posA.y4;
			var lineA2=getShadow(sh);
			sh.x1=posA.x2-posA.x3;
			sh.y1=posA.y2-posA.y3;
			var lineA3=getShadow(sh);
			sh.x1=posA.x2-posA.x4;
			sh.y1=posA.y2-posA.y4;
			var lineA4=getShadow(sh);
			var maxB=lineSpace(posB.x1,posB.y1,posB.x4,posB.y4);
			var maxA=Math.max(lineA1,lineA2,lineA3,lineA4);
			var minA=Math.min(lineA1,lineA2,lineA3,lineA4);
			if(maxB<minA || maxA<0){
				return false;
			}


			return true;
		}


	}
	/*shadow:{法向量:x,y,投影向量:x1,y1}*/
	function getShadow(sh){
		var temp1,temp2,length;
		temp1=sh.x*sh.y1+sh.y*sh.x1;
		temp2=Math.sqrt(sh.x*sh.x+sh.y*sh.y);
		length=temp1/temp2;
		return length;
	}
	function simpleTest(crossPos){
		//console.log(['crossPos',crossPos]);
		/*左上点*/
		var maxx=Math.max(crossPos.x1,crossPos.x3);
		var maxy=Math.max(crossPos.y1,crossPos.y3);
		/*右下点*/
		var minx=Math.min(crossPos.x2,crossPos.x4);
		var miny=Math.min(crossPos.y2,crossPos.y4);
		if(maxx>minx || maxy>miny){
			return false;
		}else{
			return true;
		}
	}

	function pointToLine(x0,y0,x1,y1,x2,y2){
		var space=0;
		var a=lineSpace(x1,y1,x2,y2);//线段长度
		var b=lineSpace(x0,y0,x1,y1);//(x1,y1)到点的距离
		var c=lineSpace(x0,y0,x2,y2);//(x2,y2)到点的距离
		
		if(c+b===a){
			space=0;
			return space;
		}
		if(a<=0.000001){
			alert('线段太短了');
			return;
		}
		var p=(a+b+c)/2;
		var s=Math.sqrt(p*(p-a)*(p-b)*(p-c));
		space=2*s/a;
		return space;
	}
	function lineSpace(x1,y1,x2,y2){
		var lineLength=Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
		return lineLength;
	}
</script>
<style type="text/css">
  canvas { border: 1px solid black; }
</style>
</head>
<body style="background:#eeeeee;">
    <canvas id="tutorial" width="1000" height="550" style="z-index:100;display:block;position:absolute;"></canvas>
</body>
</html>


共有 人打赏支持
粉丝 6
博文 75
码字总数 54169
评论 (1)
merq
可以 就是看不太懂
×
码上有春天
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: