LeaferJS 插件开发教程
前言
Leafer 是新开源的一个 2D 图形绘制库,目前已经有了 1.1k 的 Star 数,成长趋势非常不错,本篇不对 Leafer 过多介绍,不了解的同学可以直接阅读官网介绍,或者阅读我在之前写的 LeaferJS 尝鲜 及 应用数学 文章中也做了简单的介绍
由于目前 Leafer 尚在成长期,现在仍然还只是一个单纯的 图引擎,许多功能可能还需要社区来支持,前段时间写了一个 Leafer 插件,整理了一下 Leafer 插件的开发方法,本篇主要是教大家如何为 Leafer 实现一个简单的 Tooltip
插件,以供社区开发者参考。
本来本篇文章应当更早一些就发出来了,但是为了保证严谨,一边阅读 Leafer 插件的源码,一遍撰写,在撰写过程中发现了一个问题,排查了很久才确定,提交 issue 后才继续撰写本文,这也就导致了延期发布。
插件系统介绍
插件系统是现在开源库很多都会实现的一种能力,为了方便社区提供更灵活的功能,插件系统必不可少。
在 Leafer 发布 v1.0.0-beta.8 发布之前,Leafer 插件具有很大的限制,一个实例只能使用一个插件,但在 beta.8 中,加入了全局实例的生命周期钩子,不必单独在实例中指定,如此灵活性得到了大幅提升。
创建插件
如果要将插件发布 npm 包,则约定命名方式为 leafer-xxx-plugin
, 这样方便后续其他开发者进行搜索。
插件属性
属性 | 说明 | 类型 | 默认值 |
---|---|---|---|
name | 可选参数,插件的 npm 包名 | string | - |
import | 指定需要从 leafer-ui 中导入的方法 |
string[] | [] |
importVersion | 当前使用的 leafer-ui 版本号,用于检查用户 leafer-ui 版本是否兼容 |
string | - |
插件方法
方法 | 说明 | 回调参数 |
---|---|---|
run | 插件的主程序,自动运行, 支持传入配置对象 config 。 |
LeaferUI: IObject , config?: IObject |
onLeaefer | leafer 实例创建钩子,当 leafer 实例创建完成后,会调用 | leafer: ILeafer |
创建示例
创建插件
@leafer-ui/interface
包含了所有类的接口,如 ILeafer
对应 Leafer
,需要单独安装。
run
函数是插件的主函数- 第一个参数
LeaferUI
: 是import
属性中声明导入的方法和类 - 第二个参数
config
: 是用户使用插件时传入的参数,插件可以指定需要哪些参数。
- 第一个参数
onLeafer
函数是Leafer
创建时的生命周期钩子- 该函数是为了解决插件无法获取所有
Leafer
实例的问题 - 参数
leafer
传入的是当前创建的Leafer
实例
- 该函数是为了解决插件无法获取所有
1 |
|
使用插件
- 使用插件时只需要拿到 plugin 对象,并通过
usePlugin()
传入插件对象和自定义参数即可。
1 |
|
usePlugin 源码
以下是 usePlugin
中的实现
PluginManager
PluginManager
是维护了全局的插件
power
: Leafer 中所有的方法list
: 当前已注册的插件onLeafer
: leafer create 钩子
这里就不贴注入 PluginManager.power
与调用 PluginManager.onLeafer
的方法了,有兴趣的可以在 leafer-ui
仓库中自行查阅。
usePlugin
在 usePlugin
中,首先将 plugin
注册到 PluginManager.list
中,要注意到这时 PluginManager
其实就是一个简单的发布-订阅模式,由 usePlugin
进行订阅,在 leafer 创建时执行 PluginManager.onLeafer(leafer)
进行通知发布。
PluginManager.power
在程序运行一开始就被注入了平台相关的所有能力,如 web 端注入了 @leafer-ui/web
, node 端注入了 /packages/platform/node
相关能力。
通过 plugin.import
获取插件开发者所需依赖,将 Leafer 方法按需将依赖注入到 realParams
中转换为真实参数,再调用 plugin.run
传入转换后的真实参数和用户配置。
实现一个提示框插件
接下来,将会讲解如何实现一个提示框插件。
在正式开始前,我们先介绍一个方法:LeaferTypeCreator
LeaferTypeCreator
LeaferTypeCreator
是用于提供 Leafer 实例额外的能力,其实类似与插件。
创建一个 LeaferType
下面注册了一个名为 background
的 LeaferType, 当有 Leafer 类型指定为 background
时,会调用 backgroundType
这个方法。
1 |
|
使用 LeaferType
在实例化 Leafer 时,指定 type 为 background
, 此时 Leafer 背景色会变为 红色。
1 |
|
可以看到,我们通过 LeaferTypeCreator 也可以实现类似于插件的能力,但缺点是每个 Leafer 只能指定一个类型,不能同时作用于多个 LeaferType。
实现提示框
我将整个实现过程分为三步进行讲解,让大家更容易理解这个插件具体要做的事情。
1. 需求分析
一个很常见的场景,一个页面中有很多元素,要展示各种各样的信息,为了避免画布上文字过多,且不易于直观阅读,我们需要一个小工具,使我们能在数据悬浮时展示相应的信息。
如下图是一个 Echarts 示例,鼠标放到数据点上,展示当前点位更详细的信息。
2. 实现思路
提示框可以完全使用 Leafer 实现,但使用 HTML 实现更加通用,且更容易控制样式。
我们可以监听用户鼠标在 Leafer 实例当前的位置,同时监听 Leafer.view 鼠标移动事件,这样,当我们确定要触发事件时,只需要使用 dom 位置即可,不需要再进行位置换算。
如下图,我们只需要拿到 pageX 与 pageY , 就能确定当前鼠标的位置,如果只依赖 Leafer 坐标则还需要进行大量的判断如:画布平移、缩放等等。
3. 具体实现
我们实例只实现关键部分,不对扩展功能进行具体实现。
创建 Plugin 对象
在上面我们讲了,onLeafer
会在所有的 Leafer 实例创建时调用,而 LeaferTypeCreator
可以指定 Leafer 使用时才生效,这样,我们就可以根据用户参数,判断这个插件是需要全局生效还是指定的 Leafer 实例生效。
这样做的好处是,插件的作用域是可控的,不会因此导致所有的 Leafer 都必须使用插件。
由于我们只有在 run
函数中才能接收到插件调用者传入的 config
, 所以当我们想在 onLeafer
中使用 用户传入的 config
与 LeaferUI
时,必须将 LeaferUI
与 config
挂载到当前对象上。
1 |
|
tooltipPluginType
通过 PointerEvent.MOVE
可以监听 leafer 中的鼠标移动事件,我们可以根据这个事件捕获画布上的元素。
通过 LeaferEvent.VIEW_READY
可以监听 Leafer 实例化时的事件,这时可以通过 event.view
拿到画布的DOM 元素,我们可以直接监听 DOM 的鼠标移动事件,当 PointerEvent.MOVE
捕获到画布元素时进行展示 Tooltip。
1 |
|
createTooltip
创建弹框的部分就比较简单了,这里就不展开讲了,代码中关键步骤都加入了注释。
1 |
|
至此,一个简单的 Tooltip 弹框插件就实现好了,不过这个示例缩减了部分功能和逻辑判断,更完整的实现在文末给出。
一个重复注册插件引发血案
这是在撰写本文时发现的一个问题,同一个插件 重复注册 会引起一个 bug。
在上文中我们已经知道了,我们只有在 run
函数中才能拿到用户传入的 config
, 如果我们在 onLeafer
中想要使用,只能通过挂载到当前插件对象上实现。
示例代码
1 |
|
如果这个插件只会使用一次没有问题,如果当注册多次时会覆盖之前的插件参数
异常代码
下面提供了一个示例代码, 插件接收一个 myType
参数, 当连续注册多个相同插件时,最后一次注册的插件参数会覆盖前面的参数。
1 |
|
注册第一个插件
PluginManager.list
仅有一条,同时 config
为插件注册的 {myType:'plugin-1'}
注册第二个插件
PluginManager.list
有两条数据,两个插件的 config
都是第二个插件注册的 {myType:'plugin-2'}
原因及解决方法
其实产生这个问题的原因很简单,就是由于在 plugin
实现中,直接使用了用户传入的插件对象,在 run
函数执行时所有相同插件对象的 this
指向的都是同一个,这就造成了即便 config
不同,也只有最后配置一个生效的问题。
如我们需要解决这个问题,也很简单,当注册多个相同的插件时,每次注册都对当前的对象进行浅拷贝即可
1 |
|
对于该问题,已向 leafer 提交了 pr ,在注册插件时,使其内部浅拷贝解决,但根据 leafer 在 issue 的回复,这块的逻辑可能会进一步修改。
- 在线可复现地址:https://codesandbox.io/p/sandbox/leafer-plugin-use-bug-jft8t6?file=%2Fsrc%2FApp.vue%3A22%2C34
- issue 地址:https://github.com/leaferjs/ui/issues/42
- pr 地址:https://github.com/leaferjs/leafer/pull/3
结语
这篇文章的主要目的是为弥补官网插件示例过于简单的问题,同时将我在开发插件时的一些思考整理成文章以供社区开发者参阅。
- 完整插件代码:https://github.com/Alessandro-Pang/leafer-tooltip-plugin
- 在线演示地址:https://alexpang.cn/leafer-tooltip-plugin/
- 基于 Leafer 社区实现的折线图 + Tooltip 实际使用案例:https://codesandbox.io/p/sandbox/great-frog-w7mkz8