写点什么

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

  • 2020 年 2 月 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 年 2 月 15 日 17:28941

评论

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

架构师训练营 - 大作业(二)

树森

Spring Boot返回Json数据及数据封装

武哥聊编程

Java springboot SpringBoot 2 28天写作

现代浏览器原理

roadup

前端 浏览器 基础知识

谬误词典:因果谬误

lidaobing

28天写作 谬误词典

读《专访朱啸虎》,我学到了什么?

李忠良

学习 写作 投资 创业者 读后感

架构二期-第十二周作业(1)

浮生一梦

第十二周 2组 架构师训练营第2期

【架构师训练营 1 期】大作业一

诺乐

第 12 周 系统架构总结

心在那片海

算法:罗马数字转换为整数,RxSwift的好处,git pull问题解决error: cannot lock ref,产品经理新人如何落地 John 易筋 ARTS 打卡 Week 34

John(易筋)

ARTS 打卡计划 算法:罗马数字转换为整数 RxSwift的好处 git pull cannot lock ref 产品经理新人如何落地

架构师训练营第 12 周:数据应用(一)

xiaomao

架构师训练营第十二周总结

xiaomao

架构师训练营-大作业二

石子头

架构师训练营大作业(一)

Bear

架构师训练营第 1 期

架构师训练营第十二周作业1

韩儿

第 12 周 系统架构作业

心在那片海

架构师训练营大作业(二)

Bear

架构师训练营第 1 期

架构师训练营第 2 期 第12周总结

月下独酌

架构师训练营第2期

架构师训练营 week12 学习笔记

花果山

架构师训练营 week12 课后作业

花果山

架构师训练营第十二周作业2

韩儿

架构师训练营 - 大作业(一)

树森

生产环境全链路压测建设历程 26:FAQ之 压测结束阶段的相关问题

数列科技杨德华

28天写作

一个技术主管的年终管理心得

陆陆通通

程序员 技术管理 28天写作

精选算法面试-栈

李孟

算法 堆栈 28天写作

大作业一

golangboy

架构师训练营第 1 期

python 变量

赵开忠

Python 28天写作

【架构师训练营 1 期】大作业二

诺乐

漫谈中台系列:《1小时带你深入理解中台》学习整理

程序员架构进阶

架构 中台 技术 探索与实践 28天写作

Week 12 大数据应用

evildracula

学习 架构

大作业

ABS

架构师训练营 - 大作业一

石子头

数据cool谈(第2期)寻找下一代企业级数据库

数据cool谈(第2期)寻找下一代企业级数据库

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