Q Code

遇事不决 可问春风 春风不语 遵循自心

小程序中canvas和svg的踩坑记录

小程序中canvas和svg的踩坑记录

自己总结的一些踩坑记录~

前言

我之前负责的是一款百度旗下财经类股票自选股的智能小程序。业务中经常会有图表类的需求。比如 k线图,分时图,各种财务报表,数据分析图等等..

股票k线图

图1

股票k线图

图2

canvas与svg

在这些绘制图表类的需求中,一般会在canvas与svg二者中选择技术实现方案(如图1为canvas, 图2为svg),且往往选择canvas居多。
小程序中,canvas和svg各有所长,亦各有所短,我们只有在掌握其各自的利弊,才能在项目中应用自如,少入坑。

canvas

小程序中使用canvas来实现绘制各种图表,还是比较常见了,也有一些优点。

优点:

  1. 支持动画
  2. 支持复杂的交互
  3. 可操作图片,保存图片等..

缺点:

1.层级问题:
组件分为原生组件和小程序组件,若没有解决同层渲染问题,就有出现层级的问题,比如, swiper组件(滑块组件)不能与canvas 一起使用。

这里对于常用的小程序: 微信小程序,百度智能小程序,支持宝小程序,三家对于canvas组件分析一下。

微信小程序:在2.9.0版本后支持了同层渲染(当前最新版本v2.14.3 (2021-01-05))

images.png

百度小程序:目前还没有支持同层渲染(所以图2中使用了svg方案)

支付宝小程序:在2.6.2基础库之前为非原生组件,后续版本会切换为原生组件(秀~~),但是切换为原生组件是否支持同层渲染呢?个人认为应该是不支持的,我没调研… (写到这里有点困了….)

images.png

2.复杂的交互容易造成卡顿,影响体验,需要降频处理

3.在高DPR(设备像素比)的情况保持细腻的画质,需要配套处理

1
2
3
4
// 先将 canvas 用属性设置放大,用样式缩小
<!-- getSystemInfoSync().pixelRatio === 2 -->

<canvas width="200" height="200" style="width:100px;height:100px;"/>

svg

以微信,百度,支付宝三家小程序为例,小程序中使用svg的方式分为3种;

image 组件

image组件 支持svg格式图片

svg标签

微信小程序支持使用 Cax 引擎高性能渲染 SVG。
本人没有亲测调研(太困啦….)
但是看到了这句总结:

使用小程序内置的 Canvas 渲染器, 在 Cax 中实现 SVG 标准的子集,使用 JSX 或者 HTM 描述 SVG 结构行为表现

所以最后渲染出得还是canvas, 不过微信已经支持canvas(2d)同层渲染了,所以还是很哇塞的.

文档链接: https://developers.weixin.qq.com/community/develop/article/doc/000ca493bc09c0d03a8827b9b5b013

background-url

最后就是使用背景图片的方式,也是我在小程序中常用的方式.
这里先看一个例子

模版中使用元素的background-url属性动态绑定svg图片

1
2
// .wxml | .swan | .axml
<view style="width:148px;height:148px;background:url('{{ svgTpl }}') no-repeat center"></view>

js文件中赋值svgTpl变量

这里注意一下:常用的svg图片格式分为两种

  1. data:image/svg+xml;base64,….
  2. data:image/svg+xml;utf8,<svg…

至于为什么这里用base64编码的呢,因为要敲黑板了, 重点了! 稍后说

1
2
3
4
5
6
// .js
Page({
data: {
svgTpl:'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjE0OCIgaGVpZ2h0PSIxNDgiIHZpZXdCb3g9IjAgMCAxNDggMTQ4Ij48Y2lyY2xlIGN4PSI3NCIgY3k9Ijc0IiByPSI3MCIgc3Ryb2tlLXdpZHRoPSI4IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI0Y3NTM0RiIgc3Ryb2tlLWRhc2hhcnJheT0iMTE2LjExMzI2NDQ3NjY3ODc1IDQzOS44MjI5NzE1MDI1NzEiIHRyYW5zZm9ybT0icm90YXRlKDAsIDc0LCA3NCkiPjwvY2lyY2xlPjxjaXJjbGUgY3g9Ijc0IiBjeT0iNzQiIHI9IjcwIiBzdHJva2Utd2lkdGg9IjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkE5NzAwIiBzdHJva2UtZGFzaGFycmF5PSIxNTQuODE3Njg1OTY4OTA1IDQzOS44MjI5NzE1MDI1NzEiIHRyYW5zZm9ybT0icm90YXRlKDEwNS44NCwgNzQsIDc0KSI+PC9jaXJjbGU+PGNpcmNsZSBjeD0iNzQiIGN5PSI3NCIgcj0iNzAiIHN0cm9rZS13aWR0aD0iOCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIiBzdHJva2U9IiM0QUM4ODEiIHN0cm9rZS1kYXNoYXJyYXk9Ijc3LjQwODg0Mjk4NDQ1MjUgNDM5LjgyMjk3MTUwMjU3MSIgdHJhbnNmb3JtPSJyb3RhdGUoMjQzLjM2LCA3NCwgNzQpIj48L2NpcmNsZT48Y2lyY2xlIGN4PSI3NCIgY3k9Ijc0IiByPSI3MCIgc3Ryb2tlLXdpZHRoPSI4IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzMzODhGRiIgc3Ryb2tlLWRhc2hhcnJheT0iMzguNzA0NDIxNDkyMjI2MjUgNDM5LjgyMjk3MTUwMjU3MSIgdHJhbnNmb3JtPSJyb3RhdGUoMzE3LjUyMDAwMDAwMDAwMDA0LCA3NCwgNzQpIj48L2NpcmNsZT48L3N2Zz4='
}
})

效果图


小程序中的utf8与base64

现在在这里说一下为什么上面的示例中,采用了base64编码的svg图片而没有用utf8编码的图片

大家可以先复制如下代码在72版本以上的chrome浏览器与其他浏览器(比如Safari)

1
2
3
4
5
6
7
8
9
10
11
data:image/svg+xml;utf8,<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="148" height="148"
viewBox="0 0 148 148">
<circle cx="74" cy="74" r="70" stroke-width="8" stroke-linecap="round" fill="none" stroke="#F7534F"
stroke-dasharray="116.11326447667875 439.822971502571" transform="rotate(0, 74, 74)"></circle>
<circle cx="74" cy="74" r="70" stroke-width="8" stroke-linecap="round" fill="none" stroke="#FA9700"
stroke-dasharray="154.817685968905 439.822971502571" transform="rotate(105.84, 74, 74)"></circle>
<circle cx="74" cy="74" r="70" stroke-width="8" stroke-linecap="round" fill="none" stroke="#4AC881"
stroke-dasharray="77.4088429844525 439.822971502571" transform="rotate(243.36, 74, 74)"></circle>
<circle cx="74" cy="74" r="70" stroke-width="8" stroke-linecap="round" fill="none" stroke="#3388FF"
stroke-dasharray="38.70442149222625 439.822971502571" transform="rotate(317.52000000000004, 74, 74)"></circle>
</svg>

原因:

Chrome 71和更早版本在URI中支持#,但在72之后不支持, 在上述代码中我们的颜色使用的是带有#号的16进制。

补充一下:
微信,百度,支付宝小程序中js引擎

Android: V8

Ios: jsCore

所以如果在安卓端使用svg背景图时要特别注意一下。

解决方案:

A: 使用utf8编码时,颜色特别处理

避开#危险符号,颜色使用rgb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const hexToRGB = (hex) => {
if (typeof hex !== 'string') {
return null;
}
// 过滤非法输入
if (!/^#[0-9ABCDEFabcdef]{1,6}$/.test(hex)) {
return null
}
let pureStr = hex.slice(1);
// 处理简写形式
if (pureStr.length === 3) {
// 7f7 => [7, f, 7] => [[7], [f], [7]] => [[77], [ff], [77]] => [77, ff, 77] => 77ff77
pureStr = [].concat(...[...pureStr].map(v => [v]).map(v => v.concat(v))).join('')
}
let result = [];
for (let i = 0; i < pureStr.length; i += 2) {
result.push(parseInt((pureStr[i] + pureStr[i + 1]), 16));
}
return `rgn(${result.join(', ')})`
};
hexToRGB('#F0F0F0') // => rgb(240, 240, 240)

B: 采用base64编码

将计算好的svg字符串再编码成base64编码,

这里是我所用到的方法, 大佬们可以简单参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 * svg base64编码图片
* @param svgText {string} 拼接的svg字符串
* @returns imgStr {string} base64转码后的图片
*/
function getBase64Url(svgText, w, h) {
if (!svgText) {
return '';
}
const agreement = 'data:image/svg+xml;base64,';
svgText = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`
+ svgText
+ '</svg>';
// 必须这样搭配,可以支持中文且可编码成base64, 别问为什么... 好困....
svgText = encodeURIComponent(svgText);
const base64Url = btoa(unescape(svgText));
return agreement + base64Url;
}

其他:

若svg中设计除颜色之外的地方用到#,或没办法替换#的情况,只能用B方案

最后

以上就是对小程序中使用canvas与svg的简单介绍了, 欢迎各位大佬赏阅。

最后再安利一波,上面引用的小程序案例是百度旗下财经类自选股小程序:百股经,欢迎各位大佬试用~