Canvas 使用指南

一、canvs基礎

1、繪圖環境

canvas 的能力是通過 context 對象表現出來的,context一般稱為繪圖環境。

const context = canvas.getContext(DOMString)

DOMString 為 "2d" 時,context 是CanvasRenderingContext2D對象;(本文重點)
DOMString 為 "webgl" 時,context 是WebGLRenderingContext對象;
DOMString 為 "webgl2" 時,context 是WebGL2RenderingContext對象;
DOMString 為 "bitmaprenderer" 時,context 是ImageBitmap對象;

2、canvas 尺寸問題

  • 默認大小 300*150
  • canvas 尺寸
    注意 canva 元素實際上有兩套尺寸:元素大小E與繪圖表面大小S。
    當使用 <canvas width='600' height='300' id='canvas'> 這種方式設置大小時,是同時設置了E和S為600*300;
    當使用CSS設定canvas大小時,僅僅是設置了E,不會影響S(仍是300*150);
    當S和E的大小不一致時,瀏覽器就會將S進行縮放,使之與E大小相同,這也會導致繪制在S上的內容跟著被縮放。這往往不是期待的結果,所以應該避免用CSS設置canvas大小
  • 當CSS設置的寬高小于canvas直接設置的寬高,則繪制結果會被縮小;反之放大

3、canvas 自身的API

  • 兩屬性
    width: 繪圖表面寬度,height: 繪圖表面高度
  • 三方法
    getContext
    toDataURL(type,quality),canvas 轉 data URL, 可賦值給 img 的 src
    toBlob(callback,type),canvas 轉文件對象,可以以圖片文件格式保存

4、CanvasRenderingContext2D 屬性

CanvasRenderingContext2D 對象實例 instance 共有16個屬性,只要設置了這些屬性,就會影響 instance 調用繪制方法時的表現,常用的有:

  • fillStyle & strokeStyle
    設置填充和描邊
  • font & textAlign & textBaseline
    設置字體及水平和垂直對其方式
  • lineCap & lineWidth & lineJoin & miterLimit
    設置線段端點、線段屏幕像素寬度、線段交匯等繪制方式
  • shadowBlur & shadowColor & shadowOffsetX & shadowOffsetY
    設置陰影參數
  • globalAlpha & globalCompsiteOperation
    設置全局透明度,圖像合成方式

注意:可使用 instance 的 save() 和 restore() 方法來臨時修改 instance 的屬性

5、事件處理

  • 鼠標事件中,需要注意坐標轉換
    onmousedonwn,onmousemove,onmouseup,onmouseout etc
function windowToCanvas(canvas, x, y) {
  var bbox = canvas.getBoundingClientRect();
  console.log("canvas", canvas.width, canvas.height);  // 繪圖表面大小S
  console.log("bbox", bbox.width, bbox.height);  // 元素大小E,受CSS設置影響

  return {
    x: (x - bbox.left)* (canvas.width / bbox.width),  // 如果被縮放,則坐標也跟著縮放
    y: (y - bbox.top)* (canvas.height / bbox.height)
  };
}
canvas.onmousemove = function(e) {
  var loc = windowToCanvas(canvas, e.clientX, e.clientY);
  // ...
};
  • 鍵盤事件
    由于 canvas 是不可獲焦元素,所以無法直接在 canvas 上監聽鍵盤事件。不過可以在 documen或window上監聽,通常需要處理 keydown,keypress,keyup 三種事件,一般在游戲處理中會遇到。

6、離屏 canvas

1、一般用來保存數據,不展示在瀏覽器頁面上,創建的兩種方式:a、css 方式設置為 display:none;b、JS創建 document.createElement('canvas');
2、與HTML結合使用:可以采用 CSS 定位的方式,將 HTML 元素置于 canvas 元素之上,比如:在 canva 上疊加一個 div panel 作為某個開關控制界面;選景橡皮筋;時鐘等
3、也可使用兩個 canvas,一個用來顯示,另一個用來做數據準備和處理,這種方式通常效率高,但比較耗費內存

7、Canvas 繪圖基本套路

1、準備一個繪制背景的函數,用于每次擦除上一次繪制的結果
2、繪制輔助線
3、監聽事件,做坐標轉換 windowToCanvas
4、繪制內容的保存于恢復

function saveDrawingSurface() {
   drawingSurfaceImageData = context.getImageData(0, 0,
                             canvas.width,
                             canvas.height);
}

function restoreDrawingSurface() {
   context.putImageData(drawingSurfaceImageData, 0, 0);
}

5、三事件
onmousedown:保存初始 canvas 繪制狀態
onmousemove: 更新位置信息,并不斷調用初始 canvas 繪制狀態來擦除上一個繪制
onmouseup: 調用初始 canvas 繪制狀態來擦除上一個繪制,并根據onmousemove保存的信息做最終繪制,將結果繪制在 canvas 上

二、canvas 圖形文本繪制

1、矩形繪制

  • clearReact(x,y,width,height)
  • strokeReact(x,y,width,height)
    相關屬性:strokeStyle,lineJoin,lineWidth
  • fillReact(x,y,width,height)
    相關屬性:fillStyle
  • rect(x,y,width,height)
    逆時針繪制

2、漸變色與圖案和陰影

fillStyle 和 strokeStyle 可以是任意有效的css顏色值或者漸變色以及圖像Pattern

  • 線性漸變
gradient = context.createLinearGradient(0, 0, 0, canvas.height / 2);

gradient.addColorStop(0, "blue");
gradient.addColorStop(0.25, "white");
gradient.addColorStop(0.5, "purple");
gradient.addColorStop(0.75, "red");
gradient.addColorStop(1, "yellow");

context.fillStyle = gradient;
  • 徑向漸變
gradient = context.createRadialGradient(
    canvas.width / 2,
    canvas.height,
    10,
    canvas.width / 2,
    0,
    100
  );

gradient.addColorStop(0, "blue");
gradient.addColorStop(0.25, "white");
gradient.addColorStop(0.5, "purple");
gradient.addColorStop(0.75, "red");
gradient.addColorStop(1, "yellow");

context.fillStyle = gradient;
  • 圖像 Pattern
var image = new Image();
var pattern = context.createPattern(image, repeatString);
  context.fillStyle = pattern;
  • 陰影
    1、繪制陰影的條件:a、shadowColor 不是全透明;b、shadowBlur,shadowOffsetX,shadowOffsetY 有一個不為0
    2、shadowOffsetX,shadowOffsetY 為負值時,可繪制內嵌陰影

3、路徑

canvas 某一時刻只能有一條路徑存在,這條路徑可以包含多條子路徑。用 beginPath 來開始一條新路徑或清除上一次子路徑

  • 子路徑
    當畫完一個圖形時,如果不調用 beginPath 就開始畫另一個圖形,那么相當于當前路徑里擁有兩條子路徑(這種情況會導致第一個圖形再被繪制一遍);如果調用了 beginPath,則當前路徑里只有一條子路徑。
  • 路徑方向
    繪制圖形時,以逆時針順時針方向作為路徑方向,如 rect 永遠是逆時針,arc 最后一個參數為true則順時針
  • 非零環繞規則
    如果當前路徑是循環的或者子路徑有交叉,則在調用 fill 方法填充時,會進行非零環繞判斷,這個跟路徑方向有關:從填充區任意一點向外畫一條線,這條線會與路徑相交,順時針加1,逆時針減一,最后值如果不為0,則 fill 會生效。

4、線段

  • 1px 線段
    調用 lineTo,moveTo 方法時坐標要加個0.5,否則繪制1px會變成2px。因為當在兩個像素中間開始繪制1px時,繪制系統會各占用兩個像素中的0.5px,而0.5px不會單獨繪制,被占據0.5px的那個1px像素會被完整繪制,從而變成了2px
  • 線段
    1、lineCap
    默認butt,無任何效果;round 增加一個半圓;square 增加一個半正方形
    2、lineJoin
    控制兩條線段接合處,默認bevel,直線連接;miter 變為矩形;round 圓弧連接
  • moveTo(x,y)
  • lineTo(x,y)

5、圓弧

  • arc
    arc(radius,x,y,start,end,ccw)
  • arcTo
    arcTo(x1,y1,x2,y2,radius),適合畫圓角那種弧

6、貝塞爾曲線

  • 二階貝塞爾
    quadraticCurveTo(cx,cy,x,y) 一個控制點和一個錨點,另一個錨點是當前路徑中最后一個點
  • 三階貝塞爾
    bezierCurveTo(c1x,c1y,c2x,c2y,x,y) 二個控制點和一個錨點,另一個錨點是當前路徑中最后一個點

7、文本

textAlign: left,center,right
textBaseline: top,middle,bottom
1、三屬性 font,textAlign,textBaseline
2、三方法 strokeText, fillText, measureText
strokeText(text,x,y,maxWidth) 指定文本超過maxWidth會被縮放
measureText(text).width 返回指定文本寬度
3、水平垂直居中

context.textAlign = 'center'
context.textBaseline = 'middle'
function drawText(text){
      context.fillStyle = 'blue'
      context.fillText(text,canvas.width/2,canvas.height/2)
}

8、坐標變換

注意每次變換前,用 save 和 restore 來保存原來繪制上下文

  • 平移
    context.translate(x,y)
  • 旋轉
    context.rotate(angle)
  • 縮放
    context.scale(x,y)
  • transform & setTransform
    結合了以上三種變換
x1=ax+cy+e
y1=bx+dy+f

9、剪輯區域

1、由路徑定義的一片區域,如一個三角形,矩形,圓形,然后調用 clip 即可得到剪輯區域
2、默認和 canvas 大小一致
3、設置剪輯區域后,瀏覽器將只對該區域進行繪制
4、調用clip會把剪輯區域設為當前剪輯區域與當前路徑定義的區域的交集,故clip 的調用經常在 save 和 restore 之間,這是為了防止剪輯區域越來越小

三、canvas 圖像與視頻繪制

主要是 drawImage,getImageData,putImageData,createImageData 四個 API

  • drawImage,受全局設置影響(globalAlpha,globalCompositeOperation)
    將一幅圖片,一個canvas內容,視頻某一幀繪制到當前 canvas 中
    1、drawImage(imageOrCanvas,dx,dy) 不縮放,從 (dx,dy) 處開始繪制
    2、drawImage(imageOrCanvas,dx,dy,dw,dh) 縮放,從 (dx,dy) 處開始,繪制大小(dw,dh)
    3、drawImage(imageOrCanvas,sx,sy,sw,sh,dx,dy,dw,dh),指定部分繪制到指定位置
  • getImageData(left,top,width,height)
    獲取圖像像素數據,返回值{width,height,data},注意是設備像素,不是CSS像素
  • putImageData(imageData,x,y,dx,dy,dw,dh)
    以給定的圖像數據重新繪制到(x,y)處,大小為(dx,dy,dw,dh),注意: x,y 是 CSS 像素,dx,dy,dw,dh 是設備像素。此外 putImageData 不受全局設置的影響
  • createImageData()
    創建一個空 ImageData 對象
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容