微信小程序的跨平台图表库开发
写在前面
微信小程序出来已经有一段时间了,github上也有很多人开源了很多项目。但是由于微信平台的限制(底层Canvas能力调用为一系列JSBridge封装),图表的制作一直是个比较头疼的问题。当前的成熟图表库无法正常运行,即使经过改造,性能也有很大问题。
当前已经有做的比较好的图表库,如xiaolin3303的 wx-charts,但是只是为小程序量身定做的,在普通的H5页面就无法执行。因此,在开发中萌生了写一个多环境下兼容的图形库的念头。
这个图形库已经完成基本的图形建设,已经支持PC浏览器,WAP,React以及微信小程序等环境。并上传至github,大家可以自由使用:wx-chart,并欢迎在Issues中提出BUG以及修改建议。
效果图
无图无真相,首先上个效果图吧
工程结构
wx-chart 图形库参考了众多图形库的构建,分为三层,如下图所示:
最下方一层为兼容层,其中最主要的是 WxCanvas 和 WxCanvasRenderingContext2D用于兼容浏览器以及微信小程序的环境。在本章节中,一会儿会展开讲解实现原理。中间层为基础组件以及动画库,最上层为实现的各种图形。这两部分会在后续的章节中再另行阐述。
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 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() {...}
}
上面的例子,我们只举例 fillStyle, strokeStyle两个赋值属性的兼容转换。可以看到,我们定义了通用方法_wxSetPropertyCallable,在小程序环境下,它会调用set[wxSetName](也就是setFillStyle和setStrokeStyle),而在普通环境下会正常的属性赋值。
再来看我们的draw方法,同样只在微信小程序中有效,但是遗憾的是每次绘制以后还是要调用draw
方法一次,这是唯一的与W3C方法有区别的事情。不过,这主要是为了性能考虑,因为JSBridge每一次的调用都是有成本的,如果每次设置我们都自动的调用draw会大幅增加这个成本。
以上完成以后,我们就可以做到类似于W3C标准,在小程序中绘图了:
mport 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 图形库并提出意见和建议。
原文作者:许晨晖
微信小程序的跨平台图表库开发的更多相关文章
- uni-app开发微信小程序引入UI组件库(Vant-weapp)步骤
uni-app开发微信小程序引入UI组件库(Vant-weapp)步骤 这里以vant-weapp为例 uni-app官方文档介绍引入组件的方法 1. 新建相关目录 根目录下创建 wxcomponen ...
- 微信小程序仿朋友圈功能开发(发布、点赞、评论等功能)
微信小程序仿朋友圈功能开发(发布.点赞.评论等功能) 1.项目分析 项目整体分为三个部分 发布 展示 详情页 graph LR 朋友圈发布 --内容发布--> 内容展示 内容展示 --点击展示卡 ...
- 在微信小程序中绘制图表(part2)
本期大纲 1.确定纵坐标的范围并绘制 2.根据真实数据绘制折线 相关阅读:在微信小程序中绘制图表(part1)在微信小程序中绘制图表(part3) 关注我的 github 项目 查看完整代码. 确定纵 ...
- [小程序开发] 微信小程序内嵌网页web-view开发教程
为了便于开发者灵活配置小程序,微信小程序开放了内嵌网页能力.这意味着小程序的内容不再局限于pages和large,我们可以借助内嵌网页丰富小程序的内容.下面附上详细的开发教程(含视频操作以及注意事项) ...
- 微信小程序(应用号)开发体验
昨天微信小程序(应用号)内测的消息把整个技术社区炸开了锅, 我也忍不住跟了几波,可惜没有内测资格,听闻破解版出来了, 今天早上就着原来的项目资源试开发了一下,总结一下体验. 总体体验 开发效率高,6: ...
- 微信小程序(应用号)开发资源汇总整理 - 一直更新中
开源项目 wechat-weapp-gank - 微信小程序版Gank客户端 wechat-dribbble - 微信小程序-Dribbble wechatApp-demo - 微信小程序 DEMO ...
- 转载:第六弹!全球首个微信小程序(应用号)开发教程!通宵吐血赶稿!
大家好!博卡君原计划是能在国庆假期前把小程序的开发教程做完,给大家一套完整.系统的东西,不过由于最近小程序开发工具的拍照组件尚未完善,很多功能还不能顺利实现.我考虑了一下,觉得不如把拍照部分的一些代码 ...
- 转载:第五弹!全球首个微信小程序(应用号)开发教程!通宵吐血赶稿,每日更新!
博卡君今天继续更新,忙了一天,终于有时间开工写教程.不罗嗦了,今天我们来看看如何实现一些前端的功能和效果. 第八章:微信小程序分组开发与左滑功能实现 先来看看今天的整体思路: 进入分组管理页面--&g ...
- 转载:第四弹!全球首个微信小程序(应用号)开发教程!通宵吐血赶稿,每日更新!
感谢大家支持!博卡君周末休息了两天,今天又回到战斗状态了.上周五晚上微信放出官方工具和教程了,推荐程序猿小伙伴们都去试一试,结合教程和代码,写写自己的 demo 也不错. 闲话不多说,开始更新! 第七 ...
随机推荐
- Android事件传递机制详解及最新源码分析——Activity篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...
- [js高手之路]javascript腾讯面试题学习封装一个简易的异步队列
这道js的面试题,是这样的,页面上有一个按钮,一个ul,点击按钮的时候,每隔1秒钟向ul的后面追加一个li, 一共追加10个,li的内容从0开始技术( 0, 1, 2, ....9 ),首先我们用闭包 ...
- h5的video标签
在video标签中,我们可以使用属性:videoWidth & videoHeight,它获取的是video的宽度和高度(媒体本身). 虽然不能直接使用,但是可以通过计算宽高比得到 video ...
- Java Runtime 详解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt219 那就首先说点Runtime类吧,他是一个与JVM运行时环境有关的类,这 ...
- 父子一对多iframe,子iframe改子iframe元素
$("iframe", parent.document).contents().find("#ProductNameIn").val(66666666); 1. ...
- Nginx做文件下载服务器
这是最简单的一种办法,贴完代码就能用 server { listen 80; charset utf-8; server_name localhost; root /data/file/; autoi ...
- 团队作业9——展示博客(Beta版本)
展示博客 1.团队成员的简介和个人博客地址,团队的源码仓库地址. 何琴琴(http://www.cnblogs.com/vviane/): 领导项目进行,协调各队员之间的矛盾合作,负责测试与需求分析. ...
- C语言中变量的作用域和生命周期
变量的类型: 1. 局部变量和全局变量 局部变量也称为内部变量. 局部变量是在函数内作定义说明的.其作用域仅限于函数内, 离开该函数后再 使用这种变量是非法的. 全局变量也称为外部变量,它是在函数外部 ...
- 201521123088 《Java程序设计》第1周学习总结
第1周学习总结 1.本周学习总结本周我们正式开始了对一门新的编程语言java的学习.Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此J ...
- 201521123071 《JAVA程序设计》第九周学习总结
第九周-异常 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 此处仅部分知识点归纳 2. 书面作业 1. 常用异常,题目5-1 1.1 截图你的提交结果(出现学号) ...