摘要:寫在前面微信小程序出來已經有一段時間了,上也有很多人開源了很多項目。但是由于微信平臺的限制底層能力調用為一系列封裝,圖表的制作一直是個比較頭疼的問題。這個圖形庫已經完成基本的圖形建設,已經支持瀏覽器,,以及微信小程序等環境。
寫在前面
微信小程序出來已經有一段時間了,github上也有很多人開源了很多項目。但是由于微信平臺的限制(底層Canvas能力調用為一系列JSBridge封裝),圖表的制作一直是個比較頭疼的問題。當前的成熟圖表庫無法正常運行,即使經過改造,性能也有很大問題。
當前已經有做的比較好的圖表庫,如xiaolin3303的 wx-charts,但是只是為小程序量身定做的,在普通的H5頁面就無法執行。因此,在開發中萌生了寫一個多環境下兼容的圖形庫的念頭。
這個圖形庫已經完成基本的圖形建設,已經支持PC瀏覽器,WAP,React以及微信小程序等環境。并上傳至github,大家可以自由使用:wx-chart,并歡迎在Issues中提出BUG以及修改建議。
效果圖無圖無真相,首先上個效果圖吧
工程結構wx-chart 圖形庫參考了眾多圖形庫的構建,分為三層,如下圖所示:
最下方一層為兼容層,其中最主要的是 WxCanvas 和 WxCanvasRenderingContext2D
用于兼容瀏覽器以及微信小程序的環境。在本章節中,一會兒會展開講解實現原理。
中間層為基礎組件以及動畫庫,最上層為實現的各種圖形。這兩部分會在后續的章節中再另行闡述。
首先,我們實現這個中間層的目的很明確,就是完成一套與W3C Canvas一致的接口層,忽略小程序的底層接口,并使得上層調用時與普通Canvas使用方式一致。
小程序的架構是類似于React Native的JSBridge混合開發模式,因此其Canvas繪圖實現也很有局限性,與普通的瀏覽器對比,主要有四個不同點:
節點和Canvas對象創建:小程序的Canvas節點(嚴格意義上小程序沒有節點的概念)id的標識屬性為canvas-id,其創建JS對象使用wx.createCanvasContext方法
屬性賦值不同:在W3C的規范中,繪制的屬性都是賦值形式的,例如:
ctx.fillStyle = "#ffffff"; ctx.font = "40px Arial";
而小程序全部是調用形式的:
ctx.setFillStyle("#ffffff"); ctx.setFontSize(20);
每次繪圖后需要調用 draw 方法完成渲染
有些Canvas的能力與原生的參數有區別,如shadowBlur,shadowOffsetX均要調用wx.setShadow;有些能力并沒有實現,例如lineDash屬性,clip剪切能力等。
其中,以上這些基本也都是由于其JSBridge的實現機理導致的,有興趣的同學可以Google一下具體了解。
那么,問題來了,我們的中間層能夠解決上面那些問題?實現思路是什么?
第一個問題很簡單,我們聲明節點同時書寫id與canvas-id,而其他三個問題我的解決思路是:
能夠檢測小程序環境
與W3C標準一樣,實現Canvas & CanvasRenderingContext2D 兩個類:我們稱其為WxCanvas & WxCanvasRenderingContext2D
使用Object.defineProperty將調用轉換為賦值
當調用有區別和沒有實現的函數時,自動處理。
檢測小程序環境export function checkWX() { return __GLOBAL__DEBUG__WX__ === true || (typeof wx != "undefined" && typeof wx === "object"); }
忽略__GLOBAL__DEBUG__WX__,此變量用于調試。我們簡單檢測 wx 全局是否存在。代碼
實現兼容類WxCanvas主要邏輯如下:
class WxCanvas { constructor(id, config, contextOptions) { let me = this; // 檢查小程序環境 me.isWeiXinAPP = checkWX(); me._config = extend({}, WX_CANVAS_DEFAULT_PROPERTY, me.initConfig(config)); // 提前獲取Canvas與context(這里忽略了webgl的情況) let {canvas, context} = this.acquireContext(id, config); me._canvas = canvas; me._ctx = context; // 實例化WxCanvasRenderingContext2D me.wxCanvasRenderingContext2D = new WxCanvasRenderingContext2D(canvas, context, contextOptions); return me; } /** * Create Canvas context * @param {String} id * @param {Object} config * @returns {*} */ acquireContext(id, config) { let me = this, canvas, context; // Outer canvas config let handlerCanvas = config.canvas; if (me.isWeiXinAPP) { if (is.String(id)) { canvas = context = wx.createCanvasContext(id); } else { //... } } else { if (handlerCanvas) canvas = handlerCanvas; else canvas = is.String(id) ? document.getElementById(id) : (typeof HTMLCanvasElement != "undefined" && id instanceof HTMLCanvasElement) ? id: null; if (typeof canvas != "undefined") { context = canvas.getContext && canvas.getContext("2d"); } } // ... return {canvas, context}; } getContext(str){ } set height() {...} get height() {...} set width() {...} get width() {...} }
WxCanvas的實現還是比較簡單的,主要注意的就是acquireContext方法,在這里我們判斷了小程序與非小程序環境,并獲得Canvas上下文。
下面我們看下WxCanvasRenderingContext2D的實現。
class WxCanvasRenderingContext2D { constructor(canvas, context, options) { let me = this; me.canvas = canvas; me._ctx = context; me.isWeiXinAPP = checkWX(); // 聲明canvas屬性緩存棧,用于優化性能 me._ctxOptions = options; me._propertyCache = [extend({}, WX_CANVAS_CTX_DEFAULT_PROPERTY, options)]; me.cp = me._propertyCache[0]; // 屬性賦值轉化 me.createStyleProperty(); me.createShadowsProperty(); // 后續省略,我們就拿前兩個舉例足以... return me; } createStyleProperty() { let me = this; let styleProperty = ["fillStyle", "strokeStyle"]; styleProperty.forEach(p => { Object.defineProperty(me, p, { get: () => { return me.cp[p]; }, set: (value) => { if (value) { return me._wxSetPropertyCallable(value.toLowerCase(), p) } } }) }); } _wxSetPropertyCallable(value, propertyName, wxSetName = "set" + propertyName.replace(/(w)/, v => v.toUpperCase())) { let me = this; if (is.Null(value) || is.Undefined(value)) { return value; } // performance if (me.cp[propertyName] === value) { return value; } if (me.isWeiXinAPP) { me._ctx[wxSetName](value); me.cp[propertyName] = value; } else { me._ctx[propertyName] = value; me.cp[propertyName] = me._ctx[propertyName]; } return value; } ... draw(ctu = true) { if (this.isWeiXinAPP) { this._ctx.draw(ctu); } } }
上面的例子,我們只舉例 fillStyle, strokeStyle 兩個賦值屬性的兼容轉換??梢钥吹剑覀兌x了通用方法_wxSetPropertyCallable,在小程序環境下,它會調用set[wxSetName](也就是setFillStyle和setStrokeStyle),而在普通環境下會正常的屬性賦值。
再來看我們的draw方法,同樣只在微信小程序中有效,但是遺憾的是每次繪制以后還是要調用draw方法一次,這是唯一的與W3C方法有區別的事情。不過,這主要是為了性能考慮,因為JSBridge每一次的調用都是有成本的,如果每次設置我們都自動的調用draw會大幅增加這個成本。
以上完成以后,我們就可以做到類似于W3C標準,在小程序中繪圖了:
import WxCanvas, { WxCanvasRenderingContext2D } from "wx-chart/util/wxCanvas" let wxCanvas = new WxCanvas("myCanvas", {width: "800px", height: "500px"}); let wxCanvasContext = wxCanvas.getContext("2d"); wxCanvasContext.fillStyle="#FF0000"; wxCanvasContext.fillRect(0,0,80,100); wxCanvasContext.draw();一些遇到的問題
在實現兼容庫時,有幾個比較棘手的問題。
首先是小程序并沒有實現 measureText 方法,因此我們需要模擬一個:
measureText(text, fontSize = this.fontSize) { let me = this; if (me.isWeiXinAPP) { if (!text) { return 0; } let textLen = text.length; let hanzi = text.match(new RegExp(REG_HANZI, "g")); let hanziNum = !!hanzi ? hanzi.length: 0; let otherNum = textLen - hanziNum; return {"width": fontSize * (otherNum + hanziNum * 2) / 2 + fontSize / 4}; } else {//...} }
我們對漢字和非漢字進行了處理。不過,需要承認的是,即便是這樣,也與實際有一些不準。
另外,textBaseline,textAlign 也需要特殊處理(當前1.1版本已經支持)
結束由于時間和和篇幅有限,第一段文章就先寫到這里。有錯誤或者不完善的情況請多多諒解和支持。也換用大家使用wx-chart 圖形庫并提出意見和建議。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88916.html
摘要:前端日報精選第期寫給前端應屆生的職業規劃建議應用編譯優化之路進階篇命名空間模式解析源碼解析之任務管理入門教程快速上手聊聊改變歷史中文正式發布,帶來種新的圖表類型關系圖解好好寫代碼吧使用手冊掘金發布在即將全面支持掘金仿懂球帝 2017-10-10 前端日報 精選 【第1074期】寫給前端應屆生的職業規劃建議webpack 應用編譯優化之路JS進階篇--命名空間模式解析gulp源碼解析之任...
摘要:微信小程序中提供了的支持,利用自行繪制圖表。關注我的項目查看完整代碼。 微信小程序中圖表現狀 由于微信小程序本身框架的限制,很難集成目前已有的圖表工具,顯示圖表目前有兩種方案:1、服務器端渲染圖表,輸出圖片,微信小程序中直接顯示渲染好的圖片,比如highcharts提供了服務端渲染的能力hightcharts server render,這種方式需要后臺有一套渲染服務,并且有一定的網絡...
摘要:本期大綱確定縱坐標的范圍并繪制根據真實數據繪制折線相關閱讀在微信小程序中繪制圖表在微信小程序中繪制圖表關注我的項目查看完整代碼。相關閱讀在微信小程序中繪制圖表在微信小程序中繪制圖表 本期大綱 1、確定縱坐標的范圍并繪制 2、根據真實數據繪制折線 相關閱讀:在微信小程序中繪制圖表(part1)在微信小程序中繪制圖表(part3) 關注我的 github 項目 查看完整代碼。 確定縱坐...
閱讀 1355·2019-08-30 15:44
閱讀 2099·2019-08-30 11:04
閱讀 518·2019-08-29 15:17
閱讀 2540·2019-08-26 12:12
閱讀 3133·2019-08-23 18:09
閱讀 922·2019-08-23 15:37
閱讀 1522·2019-08-23 14:43
閱讀 2920·2019-08-23 13:13