1团滥、Canvas和SVG
1)Canvas
HTML5 的 Canvas 元素使用 JavaScript 在網(wǎng)頁上繪制圖像脏嚷。
畫布是一個(gè)矩形區(qū)域赁炎,您可以控制其每一像素是尖。
Canvas 擁有多種繪制路徑意系、矩形、圓形饺汹、字符以及添加圖像的方法蛔添。
Canvas 是逐像素進(jìn)行渲染的。
在 Canvas 中兜辞,一旦圖形被繪制完成迎瞧,它就不會(huì)繼續(xù)得到瀏覽器的關(guān)注。如果其位置發(fā)生變化逸吵,那么整個(gè)場(chǎng)景也需要重新繪制凶硅,包括任何或許已被圖形覆蓋的對(duì)象。
2)SVG
SVG 基于 XML扫皱,這意味著 SVG DOM 中的每個(gè)元素都是可用的足绅。您可以為某個(gè)元素附加 JavaScript 事件處理器。
在 SVG 中韩脑,每個(gè)被繪制的圖形均被視為對(duì)象氢妈。如果 SVG 對(duì)象的屬性發(fā)生變化,那么瀏覽器能夠自動(dòng)重現(xiàn)圖形段多。
3)Canvas和SVG的優(yōu)缺點(diǎn)
Canvas
-依賴分辨率
-不支持事件處理器
-弱的文本渲染能力
-能夠以 .png 或 .jpg 格式保存結(jié)果圖像
-最適合圖像密集型的游戲首量,其中的許多對(duì)象會(huì)被頻繁重繪
SVG
-不依賴分辨率
-支持事件處理器
-最適合帶有大型渲染區(qū)域的應(yīng)用程序(比如谷歌地圖)
-復(fù)雜度高會(huì)減慢渲染速度(任何過度使用 DOM 的應(yīng)用都不快)
-不適合游戲應(yīng)用
2、創(chuàng)建 Canvas 元素
<canvas id="myCanvas" width="200" height="100"></canvas>
3、基本的canvas用法
當(dāng)我們?cè)诶L制的一個(gè)元素(文字加缘、圖形)的時(shí)候粥航,首先要對(duì)這個(gè)元素規(guī)定它的顏色,文字字體生百,然后在進(jìn)行繪制,不然不會(huì)生效柄延。
<script type="text/javascript">
var canvasEle = document.getElementById("myCanvas");
var cxt = canvasEle.getContext("2d");
cxt.fillStyle = "#FF0000";
cxt.fillRect(0,0,150,75);
</script>
4蚀浆、Canvas常用優(yōu)化點(diǎn)
1)使用多層畫布去畫一個(gè)復(fù)雜的場(chǎng)景
場(chǎng)景:假設(shè)您有一個(gè)游戲,其UI位于頂部搜吧,中間是游戲性動(dòng)作市俊,底部是靜態(tài)背景。
方法:可以將游戲分成三個(gè)<canvas>層滤奈。 UI將僅在用戶輸入時(shí)發(fā)生變化摆昧,游戲?qū)与S每個(gè)新框架發(fā)生變化,并且背景通常保持不變
<div id="stage">
<canvas id="ui-layer" width="480" height="320"></canvas>
<canvas id="game-layer" width="480" height="320"></canvas>
<canvas id="background-layer" width="480" height="320"></canvas>
</div>
<style>
#stage {
width: 480px;
height: 320px;
position: relative;
border: 2px solid black
}
canvas { position: absolute; }
#ui-layer { z-index: 3 }
#game-layer { z-index: 2 }
#background-layer { z-index: 1 }
</style>
好處:避免了固定組件的重復(fù)渲染蜒程。
2)使用window.devicePixelRatio對(duì)畫布進(jìn)行高清處理
設(shè)備屏幕上的像素(邏輯像素)绅你,我們可以當(dāng)做是正常的像素(css中設(shè)置的像素),你可以正常使用它昭躺。如果你畫一個(gè)100px的東西忌锯,他也就是一個(gè)100px的東西。但是领炫,在出現(xiàn)了一些高分辨率屏幕的手機(jī)之后偶垮,一個(gè)屬性devicePixelRatio就一起出現(xiàn)了。它允許我們?nèi)ゲ樵冊(cè)O(shè)備像素比帝洪。在這里我們需要拋出一個(gè)名詞邏輯像素似舵,也就是在css設(shè)置的100px時(shí),在iphone6/7/8(devicePixelRatio為3)上葱峡,實(shí)際渲染的是300px的物理像素砚哗。比例為3:1
但是這對(duì)于我們開發(fā)者的影響是什么呢?早些時(shí)候族沃,我們注意到當(dāng)我們向這種高分辨率的屏幕添加img的時(shí)候频祝,我們的圖形受到devicePixelRatio的影響變得非常模糊。
如何解決這個(gè)問題呢脆淹?如果我把img的寬和高分別與devicePixelRatio相乘常空,得到的大小畫進(jìn)屏幕中,在對(duì)齊進(jìn)行縮放devicePixelRatio的大小盖溺。Img就會(huì)以一種高清的方式呈現(xiàn)漓糙。
createHiDPICanvas (w, h, ratio) {
const PIXEL_RATIO = (function () {
const c = document.getElementById('posterCanvas')
const ctx = c.getContext('2d')
const dpr = window.devicePixelRatio || 1
// backingStorePixelRatio,該屬性的值決定了瀏覽器在渲染canvas之前會(huì)用幾個(gè)像素來來存儲(chǔ)畫布信息 (僅適用于chrome和safari)烘嘱,已經(jīng)廢棄昆禽。
const bsr =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1
return dpr / bsr
})()
if (!ratio) {
ratio = PIXEL_RATIO
}
const can = document.getElementById('posterCanvas')
can.width = w * ratio
can.height = h * ratio
can.style.width = w + 'px'
can.style.height = h + 'px'
can.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0)
return can
},
但是此方法有個(gè)問題就是既然同比將畫布和內(nèi)容進(jìn)行了放大蝗蛙,然后在進(jìn)行縮放,那么繪制出來的圖片大小就會(huì)相應(yīng)的增大醉鳖。建議根據(jù)需求來判斷是否需要進(jìn)行高清操作捡硅。
3)使用requestAnimationFrame執(zhí)行動(dòng)畫
canvas動(dòng)畫的本質(zhì)是不斷地擦除和重繪,再結(jié)合一些時(shí)間控制的方法達(dá)到動(dòng)畫的目的
顯示器刷新頻率是60Hz盗棵,最平滑動(dòng)畫的最佳循環(huán)間隔是1000ms/60壮韭,約等于16.6ms
而requestAnimationFrame就是根據(jù)顯示器刷新頻率來的,這是瀏覽器專門為動(dòng)畫提供的API纹因,在運(yùn)行時(shí)瀏覽器會(huì)自動(dòng)優(yōu)化方法的調(diào)用喷屋,節(jié)省系統(tǒng)資源,提高系統(tǒng)性能瞭恰,如果頁面不是激活狀態(tài)下的話屯曹,requestAnimationFrame() 會(huì)被暫停調(diào)用以提升性能和電池壽命。
window.requestAnimationFrame(callback)
執(zhí)行一個(gè)動(dòng)畫惊畏,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫恶耽。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),返回一個(gè) requestID颜启,該回調(diào)會(huì)在瀏覽器下一次重繪之前執(zhí)行
window. cancelAnimationFrame(requestID)
取消一個(gè)先前通過調(diào)用window. cancelAnimationFrame()方法添加到計(jì)劃中的動(dòng)畫幀請(qǐng)求驳棱,接受一個(gè) requestID
瀏覽器兼容
4)1px像素模糊情況
canvas每條線都有一條無限細(xì)的中線,線由中線兩個(gè)伸展农曲。
解決問題的根源起點(diǎn)應(yīng)該在0.5的地方社搅,這也是為什么x,y需要+0.5。當(dāng)x,y做過計(jì)算不一定是整數(shù)的時(shí)候可能+0.5又出現(xiàn)模糊的情況乳规。所以做一個(gè)取整可以保證不會(huì)出現(xiàn)模糊的情況
cxt.moveTo(parseInt(x)+0.5, parseInt(y)+0.5)
cxt.lineTo(parseInt(x)+0.5, parseInt(y)+0.5)
5)離屏繪制圖像
1.繪制圖像
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
2.繪制性能比較
由于我們具備「把圖片中的某一部分繪制到 Canvas 上」的能力形葬。
在平常的html中,我們會(huì)把多個(gè)對(duì)象放在一張圖片里面暮的,以減少請(qǐng)求數(shù)量笙以。這通常被稱為「精靈圖」。然而冻辩,這實(shí)際上存在著一些潛在的性能問題猖腕。使用 drawImage 繪制同樣大小的區(qū)域,數(shù)據(jù)源是一張和繪制區(qū)域尺寸相仿的圖片的情形恨闪,和比起數(shù)據(jù)源是一張較大圖片(我們只是把數(shù)據(jù)扣下來了而已)的情形倘感,前者的開銷要小一些×剩可以認(rèn)為老玛,兩者相差的開銷正是「裁剪」這一個(gè)操作的開銷。
雖然看上去開銷相差并不多,但是 drawImage 是最常用的 API 之一蜡豹,我認(rèn)為還是有必要進(jìn)行優(yōu)化的麸粮。優(yōu)化的思路是,將「裁剪」這一步驟事先做好镜廉,保存起來弄诲,每一幀中僅繪制不裁剪。
3.離屏繪制
我們可以先把待繪制的區(qū)域裁剪好娇唯,保存起來威根,這樣每次繪制時(shí)就能輕松很多
drawImage 方法的第一個(gè)參數(shù)不僅可以接收 Image 對(duì)象,也可以接收另一個(gè) Canvas 對(duì)象视乐。而且,使用 Canvas 對(duì)象繪制的開銷與使用 Image 對(duì)象的開銷幾乎完全一致敢茁。我們只需要實(shí)現(xiàn)將對(duì)象繪制在一個(gè)未插入頁面的 Canvas 中佑淀,然后每一幀使用這個(gè) Canvas 來繪制。
// 在離屏 canvas 上繪制
var canvasOffscreen = document.createElement('canvas');
canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext('2d').drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
// 在繪制每一幀的時(shí)候彰檬,繪制這個(gè)圖形
context.drawImage(canvasOffscreen, x, y);