原文鏈接:http://my.oschina.net/codespring/blog/397464
概要:用js控制convas模仿windows上的多選、單選、拖動控制
功能包括:鼠標點擊單選、拖動多選、ctrl+單擊組合效果、對選中的單個或多個canvas圖層通過鼠標拖動、方向鍵移動、delete刪除圖層等。
html頁面代碼:
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!--[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>
非人類的科技!!!!! 已收藏,誰都別攔我!!!