Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 欧美11一12周岁a在线观看,一二三四社区在线高清观看在线,国产国语高清在线视频二区

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          小程序和H5中canvas卡頓的性能優(yōu)化方向和實(shí)踐

          么是CANVAS? 首先介紹下canvas, 前端的同學(xué)可能很熟悉,舉個(gè)很簡單的例子,
          平常用的網(wǎng)頁截圖、H5游戲、前端動(dòng)效、可視化圖表…,都有canvas 的應(yīng)用場景, 官方的定義:
          canvas是HTML5提供的一種新標(biāo)簽,
          ie9才開始支持的,canvas是一個(gè)矩形區(qū)域的畫布,可以用JS控制每一個(gè)像素在上面繪畫。canvas 標(biāo)簽使用 JavaScript
          在網(wǎng)頁上繪制圖像,本身不具備繪圖功能。canvas 擁有多種繪制路徑、矩形、圓形、字符以及添加圖像的方法。
          看著很簡單,其實(shí)canvas這個(gè)標(biāo)簽的加入,賦予了我們更多創(chuàng)建驚艷的前端效果的能力。但是你知道他也有性能問題??本篇文章就簡單談一談canvas的性能優(yōu)化。

          哪些因素會(huì)影響canvas的性能

          canvas優(yōu)化的幾種方式

          我們都知道瀏覽器上渲染動(dòng)畫 每一秒高達(dá)60幀,也就是1秒鐘內(nèi)我們完成60次圖像繪制, 也就是每一幀圖像的繪制時(shí)間其實(shí)就是(1000/ 60)。 如果在每一幀動(dòng)畫的時(shí)間小于 16.7 ms 辣么就會(huì)出現(xiàn)卡頓、丟幀。而canvas 其實(shí)是一個(gè)指令式繪圖系統(tǒng), 他通過繪圖指令來完成繪圖操作。
          影響canvas兩個(gè)很關(guān)鍵的因素:
          第一個(gè)渲染的圖形數(shù)量多,就是調(diào)用繪圖指令的次數(shù)比較多,
          第二個(gè)渲染的圖形大,就是一次繪圖渲染的時(shí)間比較長

          優(yōu)化canvas

          1. 減少繪圖指令的調(diào)用

          這句話怎么理解呢 , 假設(shè)你要在場景中畫正n變形,這是一個(gè) 很常見的需求可能你稍不注意寫下了下面這幾行代碼:

          function drawAnyShape(points) {
                for(let i=0; i<points.length; i++) {
                      const p1 = points[i]
                      const p2 =  i=== points.length - 1 ? points[0] : points[i+1]
                      ctx.fillStyle = 'black'
                      ctx.beginPath();
                      ctx.moveTo(...p1)
                      ctx.lineTo(...p2)
                      ctx.closePath();
                      ctx.stroke()
                }
             }
          


          points 對(duì)應(yīng)的生成多邊形的點(diǎn),代碼如下:

           function  generatePolygon(x,y,r, edges = 3) {
                const points = []
                const detla = 2* Math.PI / edges;
                for(let i= 0;i<edges;i++) {
                    const theta = i * detla;
                    points.push([x+ r * Math.sin(theta), y + r * Math.cos(theta)])
                }
                return points 
            }
          

          ?
          一看這fps低成這個(gè)樣子,很多人這時(shí)候說,你畫的圖形多,那我只要悄悄的改下代碼,就能讓fps 回歸正常

          重寫了正多邊形的方法:

          function drawAnyShape2(points) {
                ctx.beginPath();
                ctx.moveTo(...points[0]);
                ctx.fillStyle = 'black'
                for(let i=1; i<points.length; i++) {
                      ctx.lineTo(...points[i])
                }
                ctx.closePath();
                ctx.stroke()
            }
          

          看了下fps 已經(jīng)成功升到了30fps, 這是為什么呢, 第一段我們在循環(huán)中去做繪圖操作, 循環(huán)一次, stoke() 一次,這顯然是不合理的,第二個(gè)直接把stoke() ,放到循環(huán)外,其實(shí)就調(diào)用了一次,所以我們可以得出減少繪圖指令是可以提高canvas的性能的

          2.分層渲染

          為什么需要分層渲染, 在游戲中,假設(shè)人物的不停地在移動(dòng),但是呢背景可能加了很多花里呼哨的元素,但是我在每一次更新的時(shí)候,場景本身是不變的,變的只有人物不停的移動(dòng),如果每一幀再去重繪不就造成了性能浪費(fèi), 這時(shí)候分層canvas就出現(xiàn)了 我們先看下一張圖你可能就明白了。

          我通過3個(gè)canvas疊在一起,通過設(shè)置每個(gè)canvas的 z-index 達(dá)到了3個(gè)畫布還是在同一層的錯(cuò)覺,這樣我在requestAnimation中,只需要對(duì) 動(dòng)的圖形去做重新繪制就好了,其余的依舊是保持不動(dòng) 。

          偽代碼

           
          <canvas id="backgroundCanvas" />
          <canvas id="peopleActionCanvas" />
          const peopleActionCanvas = document.getElementById('peopleActionCanvas');
          const backgroundCanvas = document.getElementById('backgroundCanvas');
          ?
          function draw(){
            drawPeopleAction(peopleActionCanvas);
            if (needDrawBackground) {
              drawBackground(backgroundCanvas);
            }
            requestAnimationFrame(draw);
          }
          

          一個(gè)背景層一個(gè)運(yùn)動(dòng)層, 在抽象一點(diǎn),我們什么時(shí)候應(yīng)該去做分層 ,如果畫布純是靜態(tài)的就沒有必要去做分層了, 如果當(dāng)前有靜態(tài)有東動(dòng)態(tài)的,你可以邏輯層放在最上面,然后展示層 放在最底下就可以實(shí)現(xiàn)所謂的 分層渲染了,但是最好保持在3-5個(gè)。

          3. 局部渲染

          局部渲染的話其實(shí)就是調(diào)用canvas 的 clip方法。官方文檔MDN 對(duì)這個(gè)方法的使用

          CanvasRenderingContext2D.clip() 是 Canvas 2D API 將當(dāng)前創(chuàng)建的路徑設(shè)置為當(dāng)前剪切路徑的方法

          如何用canvas 畫一個(gè)1/4圓。

          const canvas  = document.getElementById('canvas');
          const ctx  = canvas.getContext('2d');
          ctx.fillStyle = 'red'
          ctx.arc(100, 100, 75, 0, Math.PI*2, false);
          //ctx.clip();
          ctx.fillRect(0, 0, 100,100);
          

          這里填充的時(shí)候 沒有用clip 畫面上應(yīng)該是一個(gè)矩形。

          這時(shí)候我把clip注釋解開來, 矩形變成了一個(gè)半圓。 所以clip 這個(gè) api 結(jié)合 fillRect 填充 就是實(shí)現(xiàn)填充任意圖形路徑。

          canvas 中畫了1000 個(gè)圓形, 如果你只改一個(gè)顏色,那其他999都是不變的 這種浪費(fèi)是肯定存在性能問題, 如果在做動(dòng)畫效果可想而知,丟幀非常厲害。 這里就可以使用我們上面的api

          正確的做法其實(shí)就是我們要做局部刷新:

          確定改變的元素的包圍盒(是否存在相交)
          畫出路徑 然后 clip
          最后重新繪制繪制改變的圖形
          clip() 確定繪制的的裁剪區(qū)域,區(qū)域之外的圖形不能繪制,詳情查看 CanvasRenderingContext2D.clip() clearRect(x, y, width, height) 擦除指定矩形內(nèi)的顏色,查看 CanvasRenderingContext2D.clearRect()

          包圍盒
          用一個(gè)框去把圖形包圍住, 其實(shí)在幾何中我們叫包圍盒 或者是boundingBox。 可以用來快速檢測兩個(gè)圖形是否相交, 但是還是不夠準(zhǔn)確。最好還是用圖形算法去解決。 或者游戲中的碰撞檢測,都有這個(gè)概念。這里討論的是2d的boudingbox, 還是比較簡單的。

          虛線框其實(shí)就是boundingBox, 其實(shí)就是根據(jù)圖形的大小,算出一個(gè)矩形邊框。理論我們知道了,映射到代碼層次, 我們怎么去表達(dá)呢? 這里帶大家原生實(shí)現(xiàn)一下bound2d 類, 其實(shí)每個(gè)2d圖形,都可以去實(shí)現(xiàn)。 因?yàn)?d圖形都是由點(diǎn)組成的,所以只要獲得每一個(gè)圖形的離散點(diǎn)集合, 然后對(duì)這些點(diǎn),去獲得一個(gè)2d空間的boundBox。

          4.離屏CANVAS 和WEBWORKER

          我們先說下 什么是離屏canvas???

          OffscreenCanvas提供了一個(gè)可以脫離屏幕渲染的canvas對(duì)象。它在窗口環(huán)境和web worker環(huán)境均有效。

          脫離屏幕渲染的canvas對(duì)象,這對(duì)我們實(shí)際寫動(dòng)畫的時(shí)候真的有用嗎???

          想象以下這個(gè)場景:如果發(fā)現(xiàn)自己在每個(gè)動(dòng)畫幀上重復(fù)了一些相同的繪制操作,請考慮將其分流到屏幕外的畫布上。 然后,您可以根據(jù)需要頻繁地將屏幕外圖像渲染到主畫布上,而不必首先重復(fù)生成該圖像的步驟。由于瀏覽器是單線程,canvas的計(jì)算和渲染其實(shí)是在同一個(gè)線程的。這就會(huì)導(dǎo)致在動(dòng)畫中(有時(shí)候很耗時(shí))的計(jì)算操作將會(huì)導(dǎo)致App卡頓,降低用戶體驗(yàn)。

          幸運(yùn)的是, OffscreenCanvas 離屏Canvas可以非常棒的解決這個(gè)麻煩!

          到目前為止,canvas的繪制功能都與標(biāo)簽綁定在一起,這意味著canvas API和DOM是耦合的。而OffscreenCanvas,正如它的名字一樣,通過將Canvas移出屏幕來解耦了DOM和canvas API。

          由于這種解耦,OffscreenCanvas的渲染與DOM完全分離了開來,并且比普通canvas速度提升了一些,而這只是因?yàn)閮烧撸–anvas和DOM)之間沒有同步。但更重要的是,將兩者分離后,canvas將可以在Web Worker中使用,即使在Web Worker中沒有DOM。這給canvas提供了更多的可能性。

          這就離屏canvas 為啥和webworker 這么配的緣故了。

          如何創(chuàng)建離屏CANVAS?
          創(chuàng)建離屏canvas有兩種方式:

          一種是通過OffscreenCanvas的構(gòu)造函數(shù)直接創(chuàng)建。比如下面的示例代碼:

           // 離屏canvas 
           const offscreen = new OffscreenCanvas(200, 200);
          第二種是使用canvas的transferControlToOffscreen函數(shù)獲取一個(gè)OffscreenCanvas對(duì)象,繪制該OffscreenCanvas對(duì)象,同時(shí)會(huì)繪制canvas對(duì)象。比如如下代碼:
           
          const canvas  = document.getElementById('canvas');
          const offscreen = canvas.transferControlToOffscreen();
          我寫了下面這個(gè)小demo 驗(yàn)證下到底是不是可靠的
           
            const canvas  = document.getElementById('canvas');
            // 離屏canvas 
            const offscreen1 = new OffscreenCanvas(200, 200);
            const offscreen2 = canvas.transferControlToOffscreen();
            console.error(offscreen1,offscreen2, '222')    
          

          離屏canvas怎么與主線程的canvas通信呢?

          這時(shí)候引用另外一個(gè)api transferToImageBitmap

          通過transferToImageBitmap函數(shù)可以從OffscreenCanvas對(duì)象的繪制內(nèi)容創(chuàng)建一個(gè)ImageBitmap對(duì)象。該對(duì)象可以用于到其他canvas的繪制。

          比如一個(gè)常見的使用是,把一個(gè)比較耗費(fèi)時(shí)間的繪制放到web worker下的OffscreenCanvas對(duì)象上進(jìn)行,繪制完成后,創(chuàng)建一個(gè)ImageBitmap對(duì)象,并把該對(duì)象傳遞給頁面端,在頁面端繪制ImageBitmap對(duì)象。

          寫個(gè)小demo測試下:

          優(yōu)化前
          我們畫 10000 * 10000 個(gè)矩形看看頁面的響應(yīng)和時(shí)間,代碼如下:

            const canvas  = document.getElementById('canvas');
            const ctx  =  canvas.getContext('2d');
           
            function draw() {
                for(let i = 0;i < 10000;i ++){
                  for(let j = 0;j < 1000;j ++){
                    ctx.fillRect(i*3,j*3,2,2);
                  }
                }
            }
            draw()
            ctx.arc(100,75,50,0,2*Math.PI);
            ctx.stroke()
           
          

          可以很明顯的感受到,在渲染出圖形前,瀏覽器是失去響應(yīng)的,我們無法做認(rèn)可操作。這樣的用戶體驗(yàn)肯定是非常差的。

          優(yōu)化后
          我們使用離屏canvas + webworker 進(jìn)行優(yōu)化,代碼如下:

          我們先看下worker 的代碼:

          let offscreen,ctx;
          // 監(jiān)聽主線程發(fā)的信息
          onmessage = function (e) {
            if(e.data.msg == 'init'){
              init();
              draw();
            }
          }
           
          function init() {
            offscreen = new OffscreenCanvas(512, 512);
            ctx = offscreen.getContext("2d");
          }
          // 繪制圖形
          function draw() {
             ctx.clearRect(0,0,offscreen.width,offscreen.height);
             for(var i = 0;i < 10000;i ++){
              for(var j = 0;j < 1000;j ++){
                ctx.fillRect(i*3,j*3,2,2);
              }
            }
            const imageBitmap = offscreen.transferToImageBitmap();  
            // 傳送給主線程
            postMessage({imageBitmap:imageBitmap},[imageBitmap]);
          }
          

          看下主線程的代碼:

          const worker = new Worker('./worker.js')
          worker.postMessage({msg:'init'});
          worker.onmessage = function (e) {
            // 這里就接受到work 傳來的離屏canvas位圖
            ctx.drawImage(e.data.imageBitmap,0,0);
          }
           ctx.arc(100,75,50,0,2*Math.PI);
           ctx.stroke()
          

          對(duì)比兩個(gè)很明顯的變化, 畫多個(gè)矩形是個(gè)非常耗時(shí)的操作會(huì)影響其他圖形渲染,可以采用離屏canvas + webworker 來解決這種失去響應(yīng)。

          5.禁用頁面和canvas的滾動(dòng)事件

          touchmove事件和滾動(dòng)事件有時(shí)候是有沖突的,這樣在我們移動(dòng)手指時(shí)回導(dǎo)致繪畫效果的卡頓,或者事件點(diǎn)位跳躍的情況發(fā)生,這時(shí)候我們只需要把滾動(dòng)事件禁用既可以了
          禁用方式是在標(biāo)簽上加上或者微信小程序頁面加上"disableScroll": true,如果是uniapp在pages.json加上
          “disableScroll”: true

          <canvas  :id="cid" 		 disable-scroll="true" type="2d" ></canvas>
          

          總結(jié)

          1. 繪制的圖形的數(shù)量和大小會(huì)影響canvas的性能,減少繪圖次數(shù),減少canvas接口調(diào)用次數(shù)
          2. 圖形數(shù)量過多,但是只刷新部分 可以使用局部渲染
          3. 邏輯層和背景圖層分離 可以使用分層渲染
          4. 某些長時(shí)間的邏輯影響主線程的, 可以使用離屏渲染 和webworker 來解決問題
          5. 禁用頁面和容器的滾動(dòng)

          清除繪圖區(qū)內(nèi)的圖形,可以用clearRect()方法

          clearRect(x, y, width, height)

          例說說用html5的Canvas技術(shù)實(shí)現(xiàn)簡易繪畫板

          以往也用canvas劃過繪畫布局,今次在原來的基礎(chǔ)上加上一些能選擇的功能,比如筆頭的大小、繪畫的形狀、繪畫的線條、清除按鈕等!

          功能還不是很完善, 如有不恰當(dāng)或有問題之處,歡迎給予斧正,感謝支持!

          下面看看效果圖:

          實(shí)現(xiàn)代碼

          html結(jié)構(gòu):

          css樣式:

          javascript代碼:


          主站蜘蛛池模板: 国产亚洲欧洲Aⅴ综合一区| 午夜一区二区在线观看| 精品一区二区三区3d动漫| 中文字幕精品亚洲无线码一区应用 | 精品在线视频一区| 无码国产精品一区二区免费vr| 日韩一区二区三区在线| 亚洲国产精品第一区二区| 亚洲片一区二区三区| 无码国产精品一区二区免费I6| 夜夜添无码试看一区二区三区| 日韩伦理一区二区| 日韩一区二区在线视频| 国偷自产av一区二区三区| 日本韩国黄色一区二区三区| 亚洲乱码av中文一区二区 | 麻豆一区二区免费播放网站| 黑人大战亚洲人精品一区| 精品理论片一区二区三区| 日本无码一区二区三区白峰美| 性色AV一区二区三区| 中文字幕无码一区二区三区本日| 精品3d动漫视频一区在线观看| 蜜桃传媒一区二区亚洲AV| 无码人妻精品一区二区三区蜜桃| 日本免费一区二区久久人人澡| 国产午夜精品一区二区三区小说 | 亚洲性色精品一区二区在线| 久久久久无码国产精品一区| 国产午夜三级一区二区三| 久久精品无码一区二区app| 一区二区三区免费在线视频 | 无码人妻精品一区二区三区蜜桃| 消息称老熟妇乱视频一区二区| 精品一区二区三区在线视频| 亚洲一区二区女搞男| 日韩免费观看一区| 无码精品黑人一区二区三区| 精品日韩亚洲AV无码一区二区三区| 久久一区二区三区免费| 国产日韩精品一区二区三区在线|