今天要講的內容是canvas的轉換功能,前面的內容沒用看的同學可以出門右轉,先看看前面的基礎知識,廢話不多說,開始進入正題吧!
何為轉換功能?熟悉css3的同學都知道,css3裡面有transform,translate,scale,rotate,animation等等,這就是css3的轉換功能,同樣,canvas也支持,但是只是支持部分,那我們來看看,都支持哪些,和css3相比,有什麼區別?
1、scale
scale(scaleWidth,scaleHeight) 縮放當前繪圖
參數:scaleWidth 表示縮放當前繪圖的寬度,取值如0.5 = 50% ,1 = 100% , 2 = 200%以此類推 ; scaleHeight 表示縮放當前繪圖的高度,取值如0.5 = 50% ,1 = 100% , 2 = 200%以此類推
我們可以先看看css3是什麼表現:
css3 scale(sx,sy) sx,sy分別表示向橫坐標和縱坐標的縮放向量,取值如0.5 = 50% ,1 = 100% , 2 = 200%以此類推 基本上跟canvas是一樣,只是說法不一樣而已,那既然用法是一樣的,我們就來試一下:
ctx.strokeStyle = 'red'; ctx.strokeRect(5,5,50,50); ctx.scale(2,2); ctx.strokeRect(5,5,50,50); ctx.scale(2,2); ctx.strokeRect(5,5,50,50);
咦,你會看到一個很奇怪的現象,它不是從定位的原點開始縮放的,而是偏移了原點,實際上是不僅僅是圖形縮放了,就連圖形的邊距也縮放了,且縮放的倍數與圖形倍數一致,我們看一下css3的scale會有什麼樣的表現:
.box{ width:50px; height:50px; border:1px solid #000; margin:20px; } .box:hover{ -webkit-transform:scale(2,2); }
css3的表現是以圖形的中心點為原點,然後向四周縮放,由此我們得到canvas 的scale與css3的scale的不同點之一;
不同點之二就是,css3的scale,如果x,y軸的縮放倍數一樣的話,是可以縮寫成一個參數的,如:
.box:hover{ -webkit-transform:scale(2); }
效果是一樣一樣的,但是canvas的2個參數即使縮放倍數一樣,也是不能進行簡寫的,必須2個參數都寫才能運行;
說到這裡,我想起來在canvasAPI開篇的時候留了一個懸念,就是canvas畫布的寬高設置必須在canvas標簽屬性上設置,而不能在css裡設置,這是為什麼呢?下面大家來看一個例子:
第一組,我們用css來定義canvas的寬高,標簽屬性上不設樣式:
canvas{ background:#fff; width:300px; height:300px; }
ctx.strokeStyle = 'red'; ctx.strokeRect(5,5,50,50);
咦,這是什麼鬼?
第二組,我們用canvas標簽屬性來設寬高,不用css設置:
<canvas width="300" height="300" id="canvas"> <span>親,您的浏覽器不支持canvas,換個浏覽器試試吧!</span> </canvas>
明顯是在標簽屬性上設置的寬高是正常的,為什麼css設置寬高會出現這種詭異的狀況,原因是canvas本身是有默認寬高的(寬300,高150),如果在css中設置寬高,會讓canvas認為,現在canvas的寬度倍自動縮放了,縮放比例為css設置的寬度/300,高的也一樣,那麼就可以理解了,現在css設置的寬度是300,高的是300,那麼就會縮放寬=300/300,高縮放=300/150,高的自然就被拉高了一倍,所以這才是必須在canvas的屬性上設置寬高的原因
我們回到scale,我們來給給一個動態圖,來看看scale的變化過程:
var timer = null; ctx.strokeStyle = 'red'; timer = setInterval(function(){ ctx.beginPath(); ctx.scale(1.5,1.5); ctx.strokeRect(5,5,50,50); ctx.closePath(); },500)
可以從這個gif圖中可以看出,scale的變化是在前面的一次繪圖的基礎上再次縮放,然後再縮放,有人說,你這個定時器是本來就是原來的基礎上再縮放一次,理所應該就是這樣,但是這個效果不好看,能不能我設一個參數,然後讓它累加,慢慢的縮放,且只有一個圖形呢?
嗯,這裡就需要解釋一個方法叫clearRect(),表示在指定的范圍內清除樣式,這裡如果需要只有一個圖形,那麼就必須在下一次繪制圖形之前清除掉前面的一次繪圖,因為中間的時間極短,就感覺是連續的,我們先介紹一下這個clearRect()方法吧:
clearRect(x,y,w,h) 參數:w,y表示需要清除的矩形的左上角坐標,w,h表示需要清除的矩形的寬高
從參數可以看出,它是可以清除局部區域的像素的,如果區域設為畫布,則是清除整個畫布了,好了,讓我們一起來寫一下你想要的那種效果:
var timer = null; var num = 1; ctx.strokeStyle = 'red'; timer = setInterval(function(){ if(parseInt(num) >=5){ clearInterval(timer); num =5; }else{ num +=0.1; } ctx.clearRect(0,0,canvas.clientWidth,canvas.clientHeight); ctx.save(); ctx.beginPath(); ctx.scale(num,num); ctx.strokeRect(5,5,50,50); ctx.closePath(); ctx.restore(); },500)
看上圖,現在就可以安安靜靜看它是怎麼縮放的了,邊距和圖形一起縮放,比例也是一樣的,這裡的效果之所以沒有和上面的gif圖一樣,在上一次縮放的基礎上縮放,是因為這一對活寶:save()和restore(),這對活寶上一篇已經講過了,如果還是不熟悉的同學出門右轉,找到API的第3篇,這裡的這一對主要功能是保存當前的路徑,不被其他的路徑污染,這對活寶和clearRect()在做運動的時候是非常有用的,這裡終點提示一下!
2、rotate
rotate(angle) 旋轉當前繪圖 參數:angle表示旋轉角度,這裡需要填寫弧度(弧度和角度的關系,在前面就已經講過了,不熟悉的同學可以找到API的第2篇)
同樣我們看一下css3 rotate的表現:
.box{ width:50px; height:50px; border:1px solid #000; margin:20px; } .box:hover{ -webkit-transform:rotate(30deg); }
可以看到css3的旋轉是以中心為原點進行旋轉,切接受的參數直接就是角度,而不是弧度,那canvas的rotate的表現是什麼呢?
ctx.fillStyle = 'red'; ctx.fillRect(0,0,150,50); ctx.beginPath(); ctx.rotate(30*Math.PI/180); ctx.strokeRect(0,0,150,50); ctx.closePath();
紅色為初始圖形,黑色為旋轉圖形,這是將圖形坐標設置畫布左上角的地方的
ctx.fillStyle = 'red'; ctx.fillRect(50,50,150,50); ctx.beginPath(); ctx.rotate(30*Math.PI/180); ctx.strokeRect(50,50,150,50); ctx.closePath();
圖形坐標設置50,50處
ctx.fillStyle = 'red'; ctx.fillRect(100,100,150,50); ctx.beginPath(); ctx.rotate(30*Math.PI/180); ctx.strokeRect(100,100,150,50); ctx.closePath();
圖形坐標設在100,100處
從這個3組效果中,我們可以得出這樣的結論:
1、canvas的旋轉原點並不是以自身的中心為原點,而是以畫布的左上角為原點,3張圖的比較可以看出來
2、圖形的旋轉原點也不是其自身的中心,而是其左上角為原點
這裡說了2個原點,可能不好理解哈,幾個例子,比如地球,它即繞太陽轉,自己本身也轉,那麼它讓太陽轉就是我們說的第一點,圖形繞畫布旋轉,准確的來說,也是圖形的左上角繞畫布左上角旋轉,太陽的自轉就是我們說的第2點,它自己本身的旋轉,只不過canvas圖形中的自轉不是以中心為原點的旋轉,其中心在左上角,這應該就明白看吧!
3、translate
translate(x,y) 重新映射畫布上的 (0,0) 位置,這怎麼理解?通俗的將,就是重新定義坐標原點,默認原點是(0,0),用此方法會將原點改成(x,y)
參數:x 添加到水平坐標(x)上的值 y添加到垂直坐標(y)上的值
定義不好理解,那我們就用例子來理解:
ctx.fillRect(10,10,100,100); //設置新原點 ctx.translate(110,110); ctx.fillRect(10,10,100,100);
首先我們畫了一個100*100的矩形,圖形坐標(10,10),因為默認原點是畫布左上角,所以此圖形在距離左上角(10,10)的位置,理論上說,我們再畫一個一模一樣的矩形,坐標也一樣,2圖形是會覆蓋的,但是我們現在重新設置原點(110,110),剛好在第一個圖形的右下角,這樣方便觀察,然後再畫一個坐標和大小一模一樣的矩形,我們來看看效果:
第二個矩形就剛好是以(110,110)為新的原點,然後距離新原點(10,10)的距離畫了一個矩形,恩,這就是translate的作用
css3也是有translate的,我們不妨也來對比一下,下面我寫一個css3的translate的例子:
.box{ width:150px; height:150px; border:1px solid #000; margin:20px; } .box:hover{ -webkit-transform: translate(100px,0); }
從gif圖可以看出,css3的translate是以自身中心為原點進行平移,但是不會改變原點坐標,所以,canvas的translate跟css3的translate又不一樣
4、transform
transform(a,b,c,d,e,f) 替換當前的變換矩陣
參數:
a:水平縮放繪圖
b:水平傾斜繪圖
c:垂直傾斜繪圖
d:垂直縮放繪圖
e:水平移動繪圖
f:垂直移動繪圖
參數很多,但是看這參數的解釋,還是很好理解,我們都知道css3的transform是一個集合,其中包含:scale,rotate,translate,skew和matrix,並且其中的matrix(矩陣)是可以轉換成前面的任何效果的,換句話說,就是matrix(矩陣)可以包含前面的任何效果,包括自身,而canvas中的transform就是扮演css3的matrix的角色,只是跟css3的效果不一樣而已,前面已經對比過了,具體的原理我們在這裡就不說了,如果不清楚的,可以看一下css3的matrix是什麼個原理,canvas的transform跟他的原理差不多!css3 matrix看這裡
scale轉成transform公式可得:
context.scale(sx, sy)
縮放我直接用公式來解釋:
x’=sx*x
y’=sy*y
(其中,sx 和sy分別表示在x軸和y軸上的縮放倍數,x和y默認為1)
matrix(sx*x,0,0,sy*y,0,0) --> context.transform(sx*x,0,0,sy*y,0,0) -->context.transform(sx,0,0,sy,0,0)
ctx.fillRect(10,10,100,100); //縮放 ctx.transform(2,0,0,2,0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100);
rotate轉化成transform
rotate(a*Math.PI/180)
公式推導就不推了,直接拿過來了
context.transform(cos(a),sin(a),-sin(a),cos(a),0,0) (a為角度)
--> context.transform(Math.cos(a*Math.PI/180),Math.sin(a*Math.PI/180),-Math.sin(a*Math.PI/180),Math.cos(a*Math.PI/180),0,0)
ctx.fillRect(10,10,100,100); //旋轉 ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100);
translate轉化成transform
translate(tx,ty)
context.transform(1,0,0,1,tx,ty)
ctx.fillRect(10,10,100,100); //平移 ctx.transform(1,0,0,1,110,110); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100);
skew轉化成transform
雖然canvas沒有skew方法,但是transform依然可以做出來
context.transform(1,tan(ay),tan(ax),1,0,0) (ax,ay表示x方向,y方向的傾斜角度)
-->context.transform(1,Math.tan(ay*Math.PI/180),Math.tan(ax*Math.PI/180),1,0,0)
ctx.fillRect(10,10,100,100); //傾斜 ctx.transform(1,Math.tan(30*Math.PI/180),Math.tan(30*Math.PI/180),1,0,0) ctx.fillStyle="red"; ctx.fillRect(10,10,100,100);
那麼,如果我想實現平移,旋轉,傾斜加放大呢,怎麼做?那就分開寫呗:
ctx.fillRect(10,10,100,100); //綜合 ctx.transform(1,0,0,1,110,110);//平移 ctx.transform(Math.cos(10*Math.PI/180),Math.sin(10*Math.PI/180),-Math.sin(10*Math.PI/180),Math.cos(30*Math.PI/180),0,0);//旋轉 ctx.transform(0.5,0,0,0.5,0,0);//縮放 ctx.transform(1,Math.tan(30*Math.PI/180),Math.tan(30*Math.PI/180),1,0,0);//傾斜 ctx.fillStyle="red"; ctx.fillRect(10,10,100,100);
5、setTransform
setTransform(a,b,c,d,e,f) 當前的變換矩陣重置為單位矩陣,用法與transform相同
參數:
a:水平縮放繪圖
b:水平傾斜繪圖
c:垂直傾斜繪圖
d:垂直縮放繪圖
e:水平移動繪圖
f:垂直移動繪圖
怎麼理解這個方法呢?
當我們用transform時,前面的變換方法會影響到後面的變換方法,我們俗稱污染,比如:
//縮放 ctx.transform(2,0,0,2,0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100); ctx.beginPath(); //旋轉 ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0); ctx.fillStyle="green"; ctx.fillRect(10,10,100,100);
前面的一個圖形我想讓它放大2倍,後面的我不想讓它放大,而是想讓它旋轉30度,結果:
後面的圖形也放大了2倍,這不是我們想要的結果,有人會說,我用save()和restore()不就可以了嗎?
//縮放 ctx.save(); ctx.transform(2,0,0,2,0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100); ctx.restore(); ctx.beginPath(); //旋轉 ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0); ctx.fillStyle="green"; ctx.fillRect(10,10,100,100);
如果你用這2個方法,我就不得不給你贊一個,說明前面的你看進去了
但是我想說的是,我們有更好的方法,就是我們現在要講的這個--setTransform
//縮放 ctx.setTransform(2,0,0,2,0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100); ctx.beginPath(); //旋轉 ctx.setTransform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0); ctx.fillStyle="green"; ctx.fillRect(10,10,100,100);
效果跟上面的一樣,官方解釋是該變換只會影響 setTransform() 方法調用之後的繪圖,當然,如果你把transform和setTransform一起混用,那也是會污染的:
//縮放 ctx.setTransform(2,0,0,2,0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100); ctx.beginPath(); //旋轉 ctx.transform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0); ctx.fillStyle="green"; ctx.fillRect(10,10,100,100);
要是把這2方法調個個看看:
//縮放 ctx.transform(2,0,0,2,0,0); ctx.fillStyle="red"; ctx.fillRect(10,10,100,100); ctx.beginPath(); //旋轉
ctx.setTransform(Math.cos(30*Math.PI/180),Math.sin(30*Math.PI/180),-Math.sin(30*Math.PI/180),Math.cos(30*Math.PI/180),0,0); ctx.fillStyle="green"; ctx.fillRect(10,10,100,100);
看看,效果就又不一樣了,所以,在用這些變換方法的時候,必須要弄清楚他們的作用范圍和順序,才能做出我們想要的效果,也不會污染其他的效果,這點,需謹記了!
好了,變換部分就講完了,感謝大家的關注,如有將的不對的地方,希望能踴躍指正,不甚感謝!