承上啓下:上篇我們學過了canvas中繪圖的顏色陰影以及漸變操作,這些有時候是給我們繪圖錦上添花有時候是審美需要,無論如何 都是canvas不可獲取的一部分,
canvas中還有一些操作可以爲你實現比較複雜的效果例如文本書寫、剪裁縮放、以及圖像合成、濾鏡通道(後續)等。這篇文章主要爲大家介紹一下文本以及剪裁縮放。
文本
文本的描邊與填充在實際操作時候跟其他基本圖形一樣,只是調用方法有所不同,文本有自己一套方法以及屬性,例如我們上一篇說到的font屬性以及fillText方法。但是這些遠遠不夠我們需求,
大部分情況下我們需要文字有顏色而且位置根據需求來定,顯示效果也有所不同。我們來看看這些都是怎麼實現的。
1.文本描邊與填充漸變:
context.strokeStyle='rgba(100,100,100,0.3)';
context.font='25pt 微軟雅黑';
context.lineWidth=1;
context.strokeText("hello Canvas",200,canvas.height/2);
以上是給文本描邊,描邊顏色我們選擇透明的,字體屬性選擇25號字體微軟雅黑 字體邊1像素,然後用strokeText方法在200,二分之一高度爲起始點寫下hello Canvas幾個字母
填充也是類似:
var gra=context.createLinearGradient(200,0,canvas.width,0);
gra.addColorStop(0,'blue');
gra.addColorStop(0.25,'white');
gra.addColorStop(0.5,'purple');
gra.addColorStop(0.75,'red');
gra.addColorStop(1,'yellow');
context.fillStyle=gra;
context.font='25pt 微軟雅黑';
context.fillText('hello Canvas',200,canvas.height/2);
在填充時候我們利用漸變效果進行填充,並把漸變效果賦值給fillStyle屬性,代替顏色填充,填充時候可以用顏色漸變甚至圖案,這裏就不再贅敘了,同理在描邊時候也可以用漸變來進行描邊,大家可以也試試。
2.設置字型屬性:
我們可以通過font屬性來設置canvas中的字體所採用字形,該屬性是一個css3格式的字符串,接受各個分量如下所示,開發時候設置屬性值時候需要按照由上至下順序來依次指定這些分量值:
字形屬性分量 | 有效取值 |
font-style | normal、italic.oblque |
font-variant | normal、small-caps |
font-weight | 決定字符筆畫粗細 可取normal(400)bold、bolder、lighter或者數字100、200... |
font-size | 字形大小:xx-small、xsmall、medium、large、x-large、xx-large、smaller、larger、length(px等單位固定量)或% |
line-height | 強制默認normal |
font-family | 仿照css3取值:如palatino |
Canvas默認字型是10px sans-seif。font-style、font-variant與font-weight默認值均爲normal
我們寫一段代碼多測試幾種樣式如下:
var leftArr=['2em palatino','bolder 2em palatino','xx-large palatino','oblique 1.5em lucida console','x-large fantasy','28px tahoma'];
var y=50;
leftArr.forEach(function(font){
context.font=font;
context.fillText(font,25,y+=50)
});
各種效果如下圖:
各種組合不能一下子都組合完,大家可以根據需要去查一下文檔 或者根據業務自己定製字形,如果font屬性值無效或者弄反分量順序,瀏覽器不會報錯而是恢復默認值,賦值作廢。
3.文本定位:
我們學會了如何對文本進行填充描邊以及如何設置字形,然後我們來看看canvas如何對文本進行定位:
在使用fillText與strokeText繪製文本時候,需要指定文本xy座標,然而具體會將文本繪製在什麼地方要看textAlign與textBaseline兩個屬性的值。
textAlign定義水平方向上對齊方式可取值有start、center、end、left、right.默認值爲start,其中start與left和right與end效果是一樣的,如果我們在canvas元素定義dir屬性是ltr那麼就是從左往右如果該屬性值爲rtl那麼將是按照從右向左計算起始點。
textBaseline定義垂直方向上對齊方式屬性值可以取值爲:top、bottom、middle、alphabetic、ideographic、hanging默認值爲alphabetic(該值跟小學用三格本寫字母一樣規則),
ideographic用於日文或者中文規則,hanging用於印度語言,top與bottom以及middle定義文本週圍邊界框的某個位置。例如top的話我們文字則在定義座標的下邊。也就是定義座標在文字top方向。
我們來像設置文本屬性一樣遍歷數組來看幾種定位文本:
var fontHeight=25,alignVal=['start','center','end'],baseVal=["top",'middle','bottom','alphabetic','ideographic'],x,y;
context.font="oblique normal bold 15px palatino";
for(var i=0;i<alignVal.length;++i){
for(var j=0;j<baseVal.length;++j){
x=20+fontHeight*i*10;
y=20+fontHeight*j*3;
drawText(alignVal[i]+"/"+baseVal[j],alignVal[i],baseVal[j]);
drawTextMarker();
drawTextLine();
}
}
function drawText(text,align,base){
if(align){context.textAlign=align}
if(base){context.textBaseline=base}
context.fillStyle='cornflowerblue';
context.fillText(text,x,y)
}
function drawTextMarker(){
context.fillStyle='red';
context.fillRect(x,y,5,5);
context.strokeRect(x,y,5,5);
}
function drawTextLine(){
context.strokeStyle='gray';
context.beginPath();
context.moveTo(x,y);
context.lineTo(x+400,y);
context.stroke();
}效果如下:
可以想象一下我們如果想把文本居中在某一點 例如canvas中心,應該怎麼做?很簡單 只要fillText(text,canvas.width/2,canvas.height/2);指定對齊方式爲context.textAlign='center';context.textBaseline='middle'即可
在文字操作中我們有時候需要獲得一段文本或者某個字母的寬度,於是乎就有了一個方法:measureText(),該方法接受一個參數,要檢測的字符串或者字符,然後返回一個包含width屬性的對象。
於是我們可以在上面檢測每段text的寬度:context.measureText(text).width;
我們可以試着用以上方法文本屬性結合js做兩個練習,一個是畫座標軸,第二個在圓弧周圍繪製文字:
第一個大家自己試着做吧,我們來一起看看第二個是如何實現的:
這段代碼利用大量數學中三角函數計算,忘了的同學可以複習一下.ha
var fillstyle="(100,100,100,0.8)",strokestyle='rgba(200,100,100,0.5)',textsize=64,circle={x:canvas.width/2,y:canvas.height/2,radius:150};
function drawCircleText(string,start,end){
var radius=circle.radius,
angleCre=(start-end)/(string.length-1),
angle=parseFloat(start),
character;
context.save();
context.fillStyle=fillstyle;
context.strokeStyle=strokestyle;
context.font=textsize+'px palatino';
for(var index=0;index<string.length;index++){
character=string.charAt(index);
context.save();
context.beginPath();
context.translate(circle.x+Math.cos(angle)*radius,circle.y-Math.sin(angle)*radius);
context.rotate(Math.PI/2-angle);
context.fillText(character,0,0);
context.strokeText(character,0,0);
angle-=angleCre;
context.restore();
}
context.restore();
}
context.textAlign='center';
context.textBaseline='middle';
drawCircleText("hello yuchao Canvas",Math.PI*2,Math.PI/8);
效果如下:
在這段程序中我們又看到兩個陌生的方法:translate以及rotate。這兩個方法本來是跟文本這篇沒有直屬關係,不過要實現我們環繞效果還是得講一下:
roate(angle):旋轉當前繪圖,參數旋轉角度:例如旋轉5度,5*Math.PI/180;
translate(x,y):重新映射當前繪圖,座標(0,0)爲參照,配合fillRect等使用生效,並fillRect基礎上進行復制映射到新位置。
例如我們畫一個矩形(10,10,100,100)然後translate(90,90);然後再同樣畫矩形就以(90.90)這個點作爲0,0原點,也就是實際開始點爲100,100。
如果大家記起來三角函數的話 我們可以很簡單的繪製一個時鐘。不妨試一下。這是我的截圖如下:
ok,文本就講到這,大家可以發散思維想象一下以現有知識如何製作一個簡單文本編輯器。。ok下面說說剪裁縮放
裁剪縮放
要說到剪裁縮放就有了一個新的方法:drawImage
drawImage會將一副圖像繪製到canvas中,所繪製的圖像叫做“源圖像”,而要繪製到的地方叫做“目標canvas”,該方法接受以下三套參數:
第一套
drawImage(image,x,y):將目標圖像整個繪製到指定座標點的位置上。
第二套
drawImage(image,dx,dy,dw,dh):會將圖像完整繪製到canvas上,不過在繪製時候會根據目標區域的dw以及dh設置對寬高進行縮放。
第三套
drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh):這個方法就可以將整幅圖像或一部分繪製到canvas指定位置,並且在繪製時候根據設置寬高進行縮放。
這三套方式的第一個參數可以是我們的image也可以是一個HTMLCanvasElement類型的canvas對象。或者video對象,所以我們也可以將canvas或者視頻對象當圖像來使用。
於是我們看明白了,操作圖像方法有這三種,第三種接受的參數最多,功能也最強大,就是它作爲我們剪裁縮放的中心方法。
我們來做一個例子,利用頁面中的html元素配合canvas完成一個支持手動的剪裁操作。
先上段代碼:
<style>
body{
background:rgba(10,145,50,0.5);
}
#canvas{
margin-left:20px;
margin-right:0;
margin-bottom:20px;
border:thin solid #aaaaaa;
cursor:crosshair;
padding:0;
}
#controls{
margin:20px 0px 20px 20px;
}
#bindDiv{
position:absolute;
border:3px solid blue;
cursor:crosshair;
display:none;
}
</style>
<body>
<div id="controls">
<input type="button" id="resetButton" value="Reset">
</div>
<div id="bindDiv"></div>
<canvas id="canvas" width="600" height="400" style="border:1px solid #d3d3d3;">
<p>Your browser does not support the canvas element.</p>
</canvas>
</body>
我們借用在html中定義的一個bindDiv來作爲我們剪裁的參照矩形框,所剪裁的圖像就在這個輪廓之內,並且定義一個button按鈕使之恢復。
var canvas=document.getElementById("canvas"),
context=canvas.getContext("2d"),
bindDiv=document.getElementById("bindDiv"),
button=document.getElementById('resetButton'),
image=new Image(),
mousedown={},//鼠標按下時候座標
bandRectangle={},//定義矩形的左上角以及寬高
dragging=false;//是否開始裁剪
image.src="gg.jpg";
//按鈕按下復位
button.οnclick=function(){
context.clearRect(0,0,canvas.width,canvas.height);//擦掉整個畫布,忘了說沒說過了
context.drawImage(image,0,0,canvas.width,canvas.height);
}
//鼠標按下開始定義起始點
canvas.οnmοusedοwn=function(e){
var x=e.clientX,
y=e.clientY;
e.preventDefault();
bandStart(x,y);
};
//鼠標移動選區
window.οnmοusemοve=function(e){
var x=e.clientX,
y=e.clientY;
e.preventDefault();
if(dragging){
bandStretch(x,y);
}
};
//擡起鼠標完成選區
window.οnmοuseup=function(e){
e.preventDefault();
bandEnd();
}
//加載圖片後
image.οnlοad=function(){
context.drawImage(image,0,0,canvas.width,canvas.height);
}
整理一下思路:我們先定義幾個動作以及圖片加載,在按下鼠標時候記錄下當前鼠標座標,因爲用戶不一定拉到左右方,所以再移動時候要重新定位起始點在所截取的圖片位置(可能是按下點的左邊呢)。
同時在拖動時候顯示我們的div元素在圖片之上,並且動態的根據鼠標改變大小。當放下鼠標時候說明選區結束,可以裁剪了。ok我們來看看代碼如何實現://選區起始點
function bandStart(x,y){
mousedown.x=x;
mousedown.y=y;
bandRectangle.left=mousedown.x;
bandRectangle.top=mousedown.y;
moveBandDiv();
showBandDiv();dragging=true;
}//選區左上角
function moveBandDiv(){
bindDiv.style.left=bandRectangle.left+'px';
bindDiv.style.top=bandRectangle.top+'px';
}
//顯示選區框
function showBandDiv(){
bindDiv.style.display='inline';
}
//鼠標移動選區變化
function bandStretch(x,y){
bandRectangle.left=x<mousedown.x?x:mousedown.x;
bandRectangle.top=y<mousedown.y?y:mousedown.y;
bandRectangle.width=Math.abs(x-mousedown.x);
bandRectangle.height=Math.abs(y-mousedown.y);
moveBandDiv();
resizeBandDiv();
}
function resizeBandDiv(){
bindDiv.style.width=bandRectangle.width+'px';
bindDiv.style.height=bandRectangle.height+"px";
}
//選區結束後裁剪選區內圖像並填滿整個畫布
function bandEnd(){
var bbox=canvas.getBoundingClientRect();
try{
//debugger;
context.drawImage(canvas,bandRectangle.left-bbox.left,bandRectangle.top-bbox.top,
bandRectangle.width,bandRectangle.height,0,0,canvas.width,canvas.height);
}
catch(e){
//當鼠標移除canvas時候會報錯
}
resetBandRectangle();
bindDiv.style.width=0;
bindDiv.style.height=0;
hideBandDiv();
dragging=false;
}
//重置選區
function resetBandRectangle(){
bandRectangle={top:0,left:0,width:0,height:0};
}
//隱藏選區
function hideBandDiv(){
bindDiv.style.display="none";
}
我們實現的效果是將圖片剪裁後立即填充滿整個畫布,看看效果吧:
第一個圖片我們選擇選區 鬆開鼠標後選區填充整個canvas,點擊按鈕就可以恢復原圖。
ok,我們的文本以及裁剪縮放講到這,在過程中遇到一些輔助函數例如:translate以及roate等,只有將這些綜合起來才能更好的滿足我們實際需求。
前幾篇在canvas中算是基本用法,下一篇開始我們來一起學習些比較高級點的東西。例如:貝塞爾曲線、多邊形、圖像合成離屏canvas以及圖像與視頻動畫等。。。挺誘人的吧。敬請期待哦!