11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

跨平台 Canvas 绘图引擎背后的黑科技

  • 2020-02-15
  • 本文字数:3201 字

    阅读完需:约 11 分钟

跨平台 Canvas 绘图引擎背后的黑科技

在 2018 年初的时候,因为要组建可视化团队,接手公司内部的一些可视化项目,做了一些技术调研。


之前的一些可视化项目或者一些内部系统中的可视化功能,奇舞团主要是使用 d3.js 或 echarts 实现的。d3.js 由于使用上比较灵活,因此也应用的比 echarts 更广。


但是 d3 有一个缺点,就是虽然它主要的功能是处理基于数据的文档,其实对如何具体展示并没有特别限定,但是它的官方例子多半是使用 SVG 和 DOM 实现的,而考虑性能和跨平台性,我们的项目使用 Canvas 渲染要优于使用 SVG 和 DOM。


但是因为 Canvas 的 API 和 DOM/SVG 差别较大,因此要把例子移植为 Canvas 渲染,改动比较大,这样不利于开发人员快速学习和使用 D3 完成项目。


因此最初我们只是想实现一个很简单的库,封装 Canvas,让它对外暴露和 DOM/SVG 较一直的 API,这也就是实现 SpriteJS 这个库的初衷。



SpriteJS+d3 实现的地图效果及代码


在后来,随着库的设计不断深入和我们对可视化探索的不断深入,SpriteJS 逐渐完善成为一个独立的用于可视化底层的渲染库。与其他同类库相比,SpriteJS 主要有以下几个优势:


  • 与 DOM 高度一致的盒模型以及 API,使得它与 d3.js 和其他适合操作 DOM 的库非常友好

  • 支持属性继承,font、lineHeight、color 等许多属性为可继承属性,适合实现可视化的 UI 组件化

  • 支持 CSS,可无缝对接文档中的样式,使用样式来控制 SpriteJS 的节点元素

  • 支持标准 Flex 布局,也支持扩展其他类型的布局

  • 支持 Web Animation API,也支持 CSS3 Animation 和 Transition

  • 支持文字的排版,支持 line-break、word-break 等相关属性

  • 支持外部时钟,可以很好地和其他 Canvas 库无缝集成

  • 支持 React、PReact、Vue 等现代前端框架

  • 跨平台,支持 Node.js 服务端渲染、支持微信小程序


SpriteJS 有与 DOM 高度一致的模型,它的对象以树状结构组织:



SpriteJS 的默认元素类型有 6 种,分别是 Scene、Layer、Group、Sprite、Label 和 Path。


其中 Sprite、Label 和 Path 分别是可带图片纹理的元素、可带文字的元素和可带 SVG Path 的矢量元素,Group 是容器,Layer 可以分层渲染,Scene 是根元素。


我们也可以使用 React 框架:


jsximport React from 'react';import ReactDOM from 'react-dom';import {Scene} from 'sprite-react';
class Block extends React.Component { constructor(props) { super(props); this.state = {color: 'green'}; }
handleClick() { this.setState({ color: 'blue', }); }
render() { return ( <sprite pos={[100, 100]} bgcolor={this.state.color} size={[50, 50]} onClick={this.handleClick.bind(this)}></sprite> ); }}
ReactDOM.render( <Scene> <layer id="fglayer" handleEvent={true}> <group> <sprite pos={[200, 100]} size={[50, 50]} bgcolor="red" onClick={function () { this.attr('bgcolor', 'blue') } }></sprite> <Block/> </group> </layer> </Scene>, document.getElementById('app'));
复制代码


或者使用 Vue:


html<template>  <scene id="container" :viewport="[300, 300]">    <layer>      <sprite        :textures="imgUrl"        bgcolor="#fff"        :pos="[0,0]"        :size="[400, 400]"        borderRadius="200"      />    </layer>  </scene></template>
<script>export default { data() { return { imgUrl: 'https://s5.ssl.qhres.com/static/ec9f373a383d7664.svg' } }}</script>
复制代码


我们还可以使用文档中的 CSS 来控制 SpriteJS 元素的样式:


html<script src="https://unpkg.com/spritejs/dist/spritejs.min.js"></script><div id="container"></div><style>  sprite.myclass {    background-color: red;    --sprite-x: 0;    --sprite-y: 0;    width: 400px;    height: 400px;    border-radius: 200px;  }</style><script>    const imgUrl = 'https://s5.ssl.qhres.com/static/ec9f373a383d7664.svg'    const {Scene, Sprite} = spritejs;    const paper = new Scene('#container', {      viewport: [400, 400],      useDocumentCSS: true,    })
const sprite = new Sprite(imgUrl) sprite.className = 'myclass'; paper.layer().appendChild(sprite)</script>
复制代码


这些特性使得我们能够使用 DOM 的方式来构建我们的图表和 UI 组件库,实际上我们也正是使用 SpriteJS 的这一系列特性来实现类似于 element-ui 这样的 UI 设计系统的。



基于 SpriteJS 的图表库


那么如何实现上面这些特性,尤其是高性能地实现这些特性呢?


SpriteJS 对上述特性的具体实现比较复杂,有兴趣的同学可以自己去浏览 GitHub 仓库的源代码,在这里将简化的流程试解释一二。


一、渲染时机

SpriteJS 的更新机制与传统的 Canvas 框架不同,不是固定时间刷新的,而是采用类似 DOM 的方式在属性更新的时候才刷新:



SpriteJS 有属性比较机制,当属性变化的时候,标记更新并通知 Layer,这时候 Layer 会启动一个 Promise 任务等待下一帧并更新画布。


如果涉及到 Label 或带有 Layout 的 Group,还有可能会触发 retypesetting 和 relayout 操作,如果使用文档中的 CSS,涉及到的属性恰好包含在 CSS 规则中,那么还可能会触发更复杂的 updateStyles 操作。


总之,SpriteJS 有一套判断机制,控制 Canvas 更新的时机,并确保 retypesetting、relayout、updateStyles 等耗性能的操作尽可能减少,以保证渲染性能



文字排版



Flex 布局


二、缓存和批次

为了提升性能,SpriteJS 支持自定义缓存策略和批次渲染。


如果渲染对象的形态可枚举,我们可以采用自定义的缓存策略,利用少量的缓存对象来大大提升性能:



缓存策略


或者通过批次渲染的方式,使用起来更加方便:



批次渲染


三、SVG 和过渡动画

SpriteJS 对 SVG-Path 的支持非常的好,不仅能支持 Path 的绘制,还能支持过渡动画:



SpriteJS 支持 Web Animation API,因此可以用标准的 Web Animation 动画,也可以用 CSS3 的 keyframe 动画:


html<style>@keyframes myfirst{  0%   {background: red;}  25%  {background: yellow;}  50%  {background: blue;}  100% {background: green;}}group.list > label:nth-child(2n+1) {  color: #f37;  --sprite-scale: 1.5,1.5;  animation: myfirst 2s;}</style>
复制代码


四、选择器和 CSS

在对 CSS 的支持方面,SpriteJS 支持几乎所有的 CSS3 选择器,包括元素选择器、类选择器、属性选择器和伪类选择器。而且在文档里可以将 DOM 和 SpriteJS 的选择器混合使用,就像是使用原生的 DOM 一样操作 SpriteJS 的元素



用 CSS 定义样式


SpriteJS 支持大部分 DOM 的 CSS 属性,对于一部分 SpriteJS 独有的属性,可以使用–sprite-属性名的方式设置。


五、外部时钟

SpriteJS 支持外部时钟,因此可以很好地和第三方库联合使用:



SpriteJS 与 CurveJS 一同使用



SpriteJS 与粒子系统


六、跨平台

SpriteJS 依赖于独立 Canvas 环境而不依赖于 DOM,因此它有很好的跨平台属性,可以在 Node.js 中通过 node-canvas 渲染使用,也可以支持微信小程序。



SpriteJS 与微信小程序


目前 SpriteJS 主要用于 360 可视化项目中,作为底层渲染库使用,在未来会进一步提升它的跨平台能力,以及渲染性能,还会集成 WebGL 增加 3D 渲染的能力。


除了上面介绍的这些之外,SpriteJS 还有许多有用的能力,比如屏幕适配、资源预加载、css 雪碧图、.9 背景图片等等,具体可以参考官方文档。如果有做可视化项目的小伙伴,可以试试这个库,相信你们会有惊喜的~



作者介绍


月影(十年踪迹),360 前端技术委员会委员。奇舞团技术总监,JavaScript 程序猿,目前重点关注前端框架和新技术应用。


本文转载自公众号携程技术(ID:ctriptech)。


原文链接


https://mp.weixin.qq.com/s/8J-uDw0qwDbj21UkQch2KA


2020-02-15 17:281405

评论

发布
暂无评论
发现更多内容

Week 07 总结

鱼_XueTr

性能测试与操作系统

史慧君

使用 Redis 有序集合实现 IP 归属地查询

AlwaysBeta

Python redis 缓存

架构师训练营第七章作业

吴吴

性能压测

走过路过飞过

Python Elasticsearch DSL 查询、过滤、聚合操作实例

AlwaysBeta

Python elasticsearch elastic

Week7-总结

龙7

web 性能压测工具

leis

LeetCode题解:1. 两数之和,JavaScript,HashMap单词遍历,详细注释

Lee Chen

大前端 LeetCode

架构师训练营 week7 - 学习总结

devfan

【架构师训练营】第七期笔记

云064

架构师训练营第 0 期第 7 周作业

无名氏

Vue 学习笔记-1

多选参数

vue.js Vue

w7-分布式系统中性能的影响因素

麻辣

学习总结 - 架构师训练营 - 第七周

走过路过飞过

第七周学习总结

潜默闻雨

第七期作业

GAC·DU

架构师培训 -07 总结 性能测试与性能优化

刘敏

架构师训练营 No.7 周作业

连增申

架构师训练营 W7 作业

telliex

【架构师训练营】第七期作业

云064

性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化?为什么?

Jam

架构师训练营 W7 心得

telliex

第七周总结

考尔菲德

Week07总结

leis

【架构师训练营 - week7 -1】总结

早睡早起

架构师训练营第七章总结

吴吴

并发量与系统响应时间和吞吐量的关系

wei

文件系统简述:从基础存储到大数据

破晓_dawn

极客时间

第七周总结

史慧君

Week7-作业

龙7

跨平台 Canvas 绘图引擎背后的黑科技_技术管理_月影(十年踪迹)_InfoQ精选文章