如下內容轉載自totoro的文章《小程序Canvas性能優化實戰!》做者:totorohtml
連接:https://blog.totoroxiao.com/c...ios
來源:https://blog.totoroxiao.com/web
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。canvas
騰訊位置服務基於微信提供的小程序插件能力,專一於(圍繞)地圖功能,打造一系列小程序插件,能夠幫助開發者簡單、快速的構建小程序,是您實現地圖功能的最佳夥伴。目前微信小程序插件提供路線規劃、地鐵圖、地圖選點等服務,歡迎你們體驗!
咱們將陸續推出更多功能的插件,敬請期待!小程序
在小程序中使用canvas組件繪製地鐵圖,地鐵圖包括地鐵線路、站點圖標、線及站點名稱文字,繪製元素爲線、圓、圖片、文字。
支持拖動平移和雙指縮放。微信小程序
小程序中的canvas性能有限,特別在交互的過程當中不斷觸發重繪會引起嚴重卡頓。api
在不考慮優化的狀況下,先說說如何實現繪製和交互。數組
首先看看數據,服務返回的數據中每一個元素都是獨立的,包括該元素的樣式及座標性能優化
// 線路數據 lineData = { path: [x0, y0, x1, y1, ...], strokeColor, strokeWidth } // 站點數據:分爲普通站點和換乘站點 // 普通站點繪製簡單圓形 stationData = { x, y, r, fillColor, strokeColor, strokeWidth } // 換乘站點繪製換乘圖標(png圖片) stationData_transfer = { x, y, width, height } // 線路名稱 lineNameData = { text, x, y, fillColor } // 站點名稱 stationNameData = { text, x, y }
繪製的時候遍歷繪製元素數組,根據元素類型設置上下文樣式,繪製及填充。接口參考:https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.html。微信
• 設置樣式:setStrokeStyle, setFillStyle, setLineWidth, setFontSize
• 繪製路線:moveTo, lineTo, stroke
• 繪製站點:moveTo, arc, stroke, fill
• 繪製圖片:drawImage
• 繪製文字:fillText
實現交互主要步驟以下:
• 經過bindtouchstart、bindtouchmove、bindtouchend實現對用戶拖動和雙指縮放的監聽,獲得拖動位移向量、縮放比例,觸發重繪。
• 繪製時經過scale和translate在不用對數據座標進行處理的狀況下實現縮放和平移
最終獲得的結果以下,平均渲染時長爲42.82ms,真機(ios)驗證:龜速移動,畫面延遲很是大。
徹底不瞭解canvas優化方案的同窗能夠先看看: canvas的優化。
避免沒必要要的畫布狀態改變
參考Canvas 最佳實踐(性能篇) ,繪圖上下文是一個狀態機,狀態的改變是有必定開銷的。畫布狀態改變這裏主要指strokeStyle、fillStyle等樣式的改變。
如何減小這部分的開銷呢?咱們能夠儘可能讓樣式相同的元素放在一塊兒進行一次性的繪製。觀察一下數據能夠發現,不少站點元素樣式都是相同的,那麼在繪製以前能夠先作一次數據的聚合,將樣式相同的數據組合成一條數據:
function mergeStationData(mapStation) { let mergedData = {} mapStation.forEach(station => { let coord = `${station.x},${station.y},${station.r}` let stationStyle = `${station.fillColor}|${station.strokeColor}|${station.strokeWidth}` if (mergedData[stationStyle]) { mergedData[stationStyle].push(coord) } else { mergedData[stationStyle] = [coord] } }) return mergedData }
聚合後,329條站點數據合併爲24條,有效的減小了90%的冗餘狀態改變開銷。修改以後測試一下,平均渲染時長降到了20.48ms,真機驗證:移動稍快了一些,但畫面仍有較高延遲。
合併數據的時候須要注意,此應用場景下各站點是沒有互相壓蓋的,而若是有壓蓋順序的話,在合併時只能合併相鄰且樣式相同的數據。
• 篩除視野外的繪製物: 當用戶在放大圖像時,其實大部分繪製物都消失在了視野範圍以外,避免繪製視野外的元素能夠節省沒必要要的開銷。點元素是比較容易判斷是否在視野範圍以外的,而站點、站點名、線路名均可以做爲點元素處理;線路也能夠計算出在視野範圍內的部分線段,較爲複雜,這裏先不作處理。篩除掉視野外的繪製物以後測試一下,平均渲染時長17.02ms,真機驗證:同上,沒有太多變化。
• 篩除太小的繪製物: 當用戶在縮小圖像時,文字和站點會因爲尺寸過小而看不大清,在不影響用戶體驗的前提下能夠考慮直接去掉。根據測試,最終決定在顯示比例小於30%時去除文字和站點,這個級別下的渲染時長從22.12ms,減小到了9.68ms。
雖然平均渲染時長已經低了不少,可是在交互時卻仍有較高的延遲,這是由於每次ontouchmove都會將渲染任務加入到異步隊列中,事件觸發頻率遠高於每秒可以執行的渲染次數,致使渲染任務嚴重積壓,不斷滯後。在PC端通常使用requestAnimationFrame解決這個問題,小程序裏沒有,可是能夠本身實現,參考微信小程序中使用requestAnimationFrame:
const requestAnimationFrame = function (callback, lastTime) { var lastTime; if (typeof lastTime === 'undefined') { lastTime = 0 } var currTime = new Date().getTime(); var timeToCall = Math.max(0, 30 - (currTime - lastTime)); lastTime = currTime + timeToCall; var id = setTimeout(function () { callback(lastTime); }, timeToCall); return id; }; const cancelAnimationFrame = function (id) { clearTimeout(id); };
PC端咱們通常將渲染間隔控制在16ms左右,可是在小程序中考慮到性能限制,且移動端各機型性能不一,因此這裏留了一些空間,控制在30ms,對應到30FPS左右。
但若是一直循環調用也會形成靜止狀態下沒必要要的開銷,因此能夠在交互開始ontouchstart和結束ontouchend時分別開啓、中止動畫:
animate(lastTime) { this.animateId = requestAnimationFrame((t) => { this.render() this.animate(t) }, lastTime) }, stop() { cancelAnimationFrame(this.animateId) }
修改以後真機驗證一下:畫面比較流程,有輕微卡頓,但不會延遲。
因爲本例中縮放和平移狀態是以絕對狀態保存的,因此scale和translate要搭配save和restore一塊兒使用;但也可使用setTransform直接重置矩陣。從理論上看這樣應該能節省開銷,但實際測試並沒什麼效果,平均渲染時長在18.12ms。這個問題有待研究。
小程序中避免使用setData保存與界面渲染無關的數據,以免引發頁面重繪。
通過以上優化,渲染時長從42降到了17ms左右,真機驗證下安卓機型廣泛很是流暢,體驗很好;ios機型有輕微卡頓,且隨着使用時長卡頓逐漸明顯,後期能夠深刻研究下是否有內存管理的問題。