【ZRender 渲染引擎 - 壹】 | 基础图形元素绘制

2,993 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 7 天,点击查看活动详情


开篇前言

在掘金认识我的都知道,我主要是研究 Flutter 的。其实我一直希望开发出一套好用的 Flutter 的图表库,但是期间遇到了一些瓶颈。当我偶然知道 echarts 底层是由 ZRender 引擎渲染时,内心是非常激动的。无论是简单的统计图,还是复杂的雷达图、地图、关系图,本质上都是通过 ZRender 引擎渲染绘制的。

image.png

ZRender 封装了前端 canvas 的绘制逻辑,通过上层的接口去操作底层的绘制功能。从而屏蔽不同环境的差异性,提供统一的访问方式,并提供更高级的图形元素的绘制功能,方便使用者的调度,这都是封装的特点。

image.png

所以我悟了,相比于 图表库 这种复杂上层建筑,在起步阶段时,一个好的引擎作为底层基础是必不可少的。想打造一个像 echarts 这样几乎完美的图表库,在短时间内是不可能凭空实现的。所以准备研究一下 ZRender ,体会一下其中的设计思想、结构思路和逻辑实现,先打造一个符合 Flutter 框架的二维绘图引擎库 render2d。这点对于绘制小能手的我感觉还是有些希望的。

所以本系列文章将作为对前端的 ZRender 引擎研究的探索记录。我对 web 前端的知识并不是非常精通,但对于前端的 Canvas 绘制还是略知一二的,借此可能还会巩固一些小前端的知识。


1. ZRender 和基础图形元素介绍

Flutter 中对于绘制封装了 CanvasPaintPathMatrix4 等类型,并且有自身的 Animation 动画机制。 相比而言,Html 的绘制显得更加原始一些,面向过程的味道更浓,这也是封装一个绘制引擎的必要性。 ZRender 的封装感觉要比 Flutter 绘制系统要高一层级,它封装了很多基础设施,让绘制对于使用者而言更加简易。所以有必要学习一下,它山之石可以攻玉。

ZRender 是一个开源项目,地址在 【ecomfe/zrender】,可以将其 clone 到本地,方便查看源码。

image.png


对于绘制的封装而言,基础图形元素是必不可少的,以后简称为 图元 。他们被定义在 graphic 文件夹中,其中 Displayable 是图元比较顶层的抽象。在 shape 文件夹中定义了一些基础图形,它们是 echarts 图表展示的基础。

image.png

所以在刚接触 ZRender 时,了解这些图元的使用是一个比较好的切入点。本文先从一些简单的图形元素绘制进行体验,了解 ZRender 的基本使用。

image.png


2. ZRender 的使用

作为一个 js 的库,引入的方式大同小异。这里先通过最原始的方式体验一下 zrender, 先不通过前端框架集成。在下载的源码后,在 dist 文件夹中可以得到库文件:

image.png

对于简单的集成体验,使用 script 标签引入库即可:

image.png


对于图元的绘制测试,将使用如下的展示方式:在 100*100的虚线框内部是绘制内容,框下是标题信息。所以先来准备一些结构和样式。如下所示,这些非常基础,就不介绍了:

image.png

---->[css 样式]----
<style>
    .wrapper {
        display: flex;
        flex-direction: column;
        align-items: center;
    }
    .box {
        width: 100px;
        height: 100px;
        border: 2px dashed;
    }
    .leabel {
        margin-top: 5px;
        font-size: 14px;
        font-weight: bold;
        color: gray;
    }
</style>

布局结构上是通过 flex 布局维护的上下结构:

---->[html 结构]----
<div class="wrapper">
    <div id="paper" class="box"></div>
    <span class="leabel">折线: Polyline</span>
</div>

script 标签下书写 js 脚本, 使用方式也比较简单。通过 zrender.init 来关联 dom 节点进行初始化,获取渲染对象。如何创建绘制对象,添加到渲染对象中即可。如下是折线 Polyline 的的绘制效果,可以看出 ZRender 默认的坐标系是以 dom 节点 左上角为原点,向左和下方为正方向的直角坐标系,这也是屏幕渲染中最常用的坐标系:

image.png

Polyline 通过 shape.points 属性提供点数组,将点依次连接进行显示:

---->[js 脚本]----
<script>
   const render = zrender.init(document.getElementById('paper'))
   const stokeStyle = {
     stroke: 'red',
     lineWidth: 1,
     fill: null,
   };
   
   const polyline = new zrender.Polyline({
        shape: {
            points: [
                [0, 50],
                [10, 60,],
                [20, 40,],
                [30, 80,],
                [40, 20,],
                [50, 50,],
                [60, 40,],
                [100, 40,],
            ]
        },
        style: stokeStyle
    })
    render.add(polyline)
</script>

3.直线、矩形、圆形的绘制

这三个图形,是基础中的基础,在 ZRender 中分别通过 LineRectCircle 绘制。由于基本流程是相同的,下面的绘制体验中,只贴出核心的图元对象创建的代码:

image.png

直线通过 shape 属性的 x1y1x2y2 指定两个坐标,进行连线:

const line = new zrender.Line({
    shape: {
        x1: 0,
        y1: 0,
        x2: 100,
        y2: 100,
    },
    style: stokeStyle
})

矩形 Rect : 通过左上角坐标 (x,y) 和宽高 widthheight 确定形状,另外可以使用 shape.r 属性指定四周圆角:

image.png

const rect = new zrender.Rect({
    shape: {
        x: 10,
        y: 10,
        width: 80,
        height: 80,
    },
    style: stokeStyle
})

const rrect = new zrender.Rect({
    shape: {
        x: 25,
        y: 25,
        r: [5,10,5,10],
        width: 50,
        height: 50,
    },
    style: stokeStyle
})

圆形 Circle : 通过圆心坐标 (cx,cy) 和半径 rheight 确定形状:

image.png

const circle = new zrender.Circle({
    shape: {
        cx: 50,
        cy: 50,
        r: 50,
    },
    style: stokeStyle
})

4. 圆弧、椭圆、贝塞尔曲线

下面来看一组曲线:圆弧、椭圆、贝塞尔曲线分别由 ArcEllipseBezierCurve 绘制。圆弧就是圆上的一段弧线,通过指定 startAngleendAngle 截取:

image.png

const arc = new zrender.Arc({
    shape: {
        cx: 50,
        cy: 50,
        r: 40,
        startAngle: 0,
        endAngle: 135 * Math.PI / 180,
    },
    style: stokeStyle
})

椭圆 Ellipse 通过中心点 (cx,cy) 和横纵半轴长 rxry 确定:

image.png

const ellipse = new zrender.Ellipse({
    shape: {
        cx: 50,
        cy: 50,
        rx: 50,
        ry: 30,
    },
    style: stokeStyle
})

贝塞尔曲线 BezierCurve在绘制界可谓老生常谈的知识了。这里的 BezierCurve 可以绘制二次和三次贝塞尔曲线。如下是一段二次贝塞尔曲线:参数包括 起点 (x1,x2)控制点1 (cpx1,cpy1)终点 (x2,y2)

image.png

const bezierCurve2 = new zrender.BezierCurve({
    shape: {
        x1: 30,
        y1: 20,
        cpx1: 70,
        cpy1: 10,
        x2: 80,
        y2: 40,
    },
    style: stokeStyle
})

另外,如果指定 控制点2 (cpx2,cpy2) 就会绘制三次贝塞尔曲线:

image.png

const bezierCurve3 = new zrender.BezierCurve({
    shape: {
        x1: 30,
        y1: 20,
        cpx1: 50,
        cpy1: 10,
        cpx2: 80,
        cpy2: 20,
        x2: 80,
        y2: 40,
    },
    style: stokeStyle
})

关于贝塞尔曲线,在 《【Flutter 绘制番外】svg 终篇 - 路径指令》 一文中有所介绍,结合其中的图来看更好理解一些:

image.png


5. 文字、图片的简单绘制

另外,绘制过程中还有文字和图片这两个非常重要的部分,通过 TextImage 绘制。 文字的样式非常多,属性在 style 中配置,基本上和 css 的属性是类似的,这里先简单体验一下:

image.png

const text = new zrender.Text({
    style: {
        text : 'ZRender',
        fontSize: 20,
        x:10,
        y:40
    }
})

图片通过 Image 进行展示,属性也在 style 中配置:

image.png

const text = new zrender.Image({
    style: {
        image : '../assets/icon_head.webp',
        x:10,
        y:10,
        with:80,
        height:80,
    }
})

这上面的 9 种是最基础的图形元素,这里只是简单的绘制体验,在 zrender 官网文档 中有对各种图形的详细属性介绍,感兴趣的可以自己参阅。下一篇,将介绍一下其他的不太常用的图元,并基于 Vue 框架来整合这些绘制样例。那本文就到这里,谢谢观看 ~

  • @张风捷特烈 2022.10.21 未允禁转
  • 我的 公众号: 编程之王
  • 我的 github 主页 :  toly1994328