插件 2.0
接入指南
在云信官网上下载最新版本的 WEB 端 SDK 压缩包,解压后,会得到这样一个目录结构
├── js
│ ├── NIM_Web_Chatroom_v7.1.0.js
│ ├── NIM_Web_NIM_v7.1.0.js
│ ├── NIM_Web_NRTC_v7.1.0.js
│ ├── NIM_Web_Netcall_v7.1.0.js
│ ├── NIM_Web_SDK_v7.1.0.js
│ ├── NIM_Web_WebRTC_v7.1.0.js
│ └── NIM_Web_WhiteBoard_v7.1.0.js
├── 插件
│ ├── DrawPlugin.2.0.0.js // 新版本
│ ├── DrawPlugin.js // 老版本
│ ├── DrawPluginUI.2.0.0.js
│ ├── RecordPlayer.2.0.0.js
│ ├── RecordPlayerUI.2.0.0.js
│ ├── replayTest.html // 示例
│ ├── toolbarTest.html // 示例
│ ├── webview.2.0.0.js // webview
│ └── webview.html // webview页面示例
在使用新白板插件之前,需要先拿到 IM 和 WhiteBoard 的实例对象,IM 的实例对象负责 IM 的消息发送,WhiteBoard 的实例对象是负责传输白板插件绘制的数据内容。 请参考互动白板-初始化 章节里的内容,和[互动白板-双人通话][互动白板-多人通话], 创建 im
实例和 whiteboard
实例,通过whiteboard.createChannel
或者 whiteboard.joinChannel
创建并加入房间后,就可以初始化 DrawPlugin 实例了。
工具栏的示例代码可以看github 上的仓库
引入插件 2.0.js
在项目里可以使用 <script src="/path/to/lib/DrawPlugin.2.0.0.js"></script>
或者 import * as DrawPlugin from 'path/to/lib/DrawPlugin.2.0.0.js'
, 引入对象DrawPlugin
。
获得 DrawPlugin 实例
成功加入白板房间后,可以使用以下代码获得 DrawPlugin 实例,容器会被画布填充 Canvas 元素,此时用户还无法与画布交互。执行完下面代码后,鼠标在画布上是激光笔工具,可以看到红色激光笔随着鼠标移动。
let handler = DrawPlugin.getInstance({
account: "user1", // 当前使用白板用户的account,
ownerAccount: "user1000", // 白板房间的创建者的account
identity: "owner", // 可以传入字符串 'normal' 或者 'owner','owner' 代表房主,'normal' 代表普通学生
appKey: "1ee5a51b7d008254cd73b1d4369a9494", // 当前im SDK使用的appkey
nim: nim,
whiteboard: whiteboard
whiteboardContainer: document.getElementById("wb"), // 这里传入白板插件画图容器
debug: true, // 是否开启drawPlugin的debug,
});
调用 getInstance 可以传入的具体参数如下:
属性 | 类型 | 说明 |
---|---|---|
debug | bool | 可选,是否开启调试, 默认 false;除此以外,需要在浏览器的 console 里设置 localStorage.debug = true,将会输出更多调试内容。 |
account | string | 必选, 当前使用白板用户的 account |
ownerAccount | string | 必选, 白板房间的创建者的 account |
whiteboardContainer | HTMLElement | 必选, 白板容器的 DOM 节点 |
identity | 'owner' / 'normal' | 必选, 当前参与白板画板的身份,'owner' 代表房主,'normal' 代表普通学生,一个白板 channel 里只能有一个'owner' 。owner 身份会尽量使用容器大小作为自己的画布大小,normal 身份会使用老师传过来的数据作为画布大小。 |
appKey | string | 必选,当前 im sdk 使用 appkey |
width | number | 可选,白板画布的宽度,白板插件会根据宽高比在各端保持一致的画布比例,保证绘画内容不歪曲。配置宽度同时必需配置高度,若不配置宽高,则房主的宽高将会默认撑满容器 |
height | number | 可选,白板画布的高度,白板插件会根据宽高比在各端保持一致的画布比例,保证绘画内容不歪曲 |
backgroundUrl | string | 可选,图片 url 地址或者 base64 编码的图片,白板画布在加载 PPT 文档时,画布中展示的图片 |
clearTransparentPix | boolean | 可选,是否清除一些透明像素 |
maxUserHistory | number | 可选,默认为 10,用户可以撤销自己的历史记录的长度,可以设置为Number.POSITIVE_INFINITY |
inited 事件
当我们调用DrawPlugin.getInstance
方法后,白板插件会进入页面初始化流程,会初始化当前画布的比例。
只有抛出inited
事件后,白板才算初始化完毕,才能正确响应 handler 的方法。建议用户在inited
事件后,才设置画布的颜色,工具等设置,建议开发者在此处调用 handler 的方法来设置默认工具等初始化状态。如果是普通用户端,需要在 enableDraw 以后设置画布工具的状态。
handler.on("inited", () => {
// 在初始化完成后,执行下面的方法,
handler.readyToSyncData(); // 建议在inited方法后调用同步操作
handler.setEnable(true); // 调用handler上的方法,让画布可以交互。必须要enableDraw以后,才能设置工具,大小和颜色等等
handler.useTool("free"); // 使用自由画笔工具,
});
当画布同步完成以后,画布就可以使用了,各端也都会展示相同的画笔内容。
同步流程
当我们调用handler.readyToSyncData
方法后,白板插件会进入如下的初始化流程:
如果当前用户是房主,会将自己的画布容器比例通知其他成员,然后马上抛出
afterSync
事件。其他成员收到消息后,会清空画布内容,并设置自己的画布比例和房主的画布比例一致,然后将自己的插件状态同步给房主,避免普通用户的画布状态突然恢复为默认状态。如果当前用户是普通成员,会向房主请求同步数据,此时插件进入等待状态,直到房主返回数据,才会继续初始化。房主收到要求同步数据的消息后,会取得将当前画布状态的快照,然后经过 gzip 压缩后返回给当前的普通用户。普通用户收到画布快照后,会还原画布内容,然后还原自己保存在房主那里的状态(如果快照里没有,将使用默认状态),然后抛出
afterSync
事件。
画布比例同步
当owner
初始化时,会使用自己容器的宽高作为画布的宽高,如果设置了width
或者height
,将会以设置的值作为画布的宽高,然后将这个宽高发送给其他用户。其他用户收到这个宽高以后,也会设置画布为这个宽高。画布始终会尽量充满容器,所以在normal
端的画布会等比例缩放。owner
端改变窗口大小,其他端的画布也会跟着改变。建议owner
端对容器宽高比改变特别大的时候,清空画布内容重新书写。
handler 上的实例方法
方法 | 参数类型 | 说明 |
---|---|---|
setFileObj | object | 传入 IM 转码完成后返回的对象,参考文档转码这一章节里的 getFileList 返回的数据,handler 自动会转换为插件所需的格式 ,然后设置为画布的背景;画布背景不可以填充,擦除 |
setFileObjRaw | object | 手动传入插件所需的格式 |
clearImage | void | 清除图片格式的 PPT 背景 |
setTool | string | 设置当前画布使用的工具,工具具体参数,请见下方的工具图表 |
setFontSize | number | 设置文字工具的字体大小 |
setContainer | HTMLElement | 设置画布容器,只有在调用 DrawPlugin.getInstance 没有传入 container 时,才需要手动调用这个方法 |
setColor | string | 设置画笔颜色,需要为"#xxxxxx"格式的颜色 |
setSize | number | 设置画笔大小, |
enableDraw | boolean | 传入 true 时,表示允许使用画布;传入 false 时,将不允许使用画布 。注意,在不允许交互的情况下,DrawPlugin 实例对设置颜色,设置工具等等都是不会生效的。 |
changeRoleToPlayer | void | 等同于 enableDraw(true) |
changeRoleToAudience | void | 等同于 enableDraw(false) |
undo | void | 撤销画笔操作,只能撤销自己的,撤销的数量受 maxUserHistory 控制 |
redo | void | 重做画笔操作,只能重做自己画的内容 |
clear | void | 清空当前画布所有笔画数据,不包括 PPT |
resetState | void | 重置画布状态,建议在断线重连后,还原默认画布状态调用 |
page | string | 参数可以为,'leftEnd' / 'left' / 'up' / 'down' / 'right' / 'rightEnd' 翻页操作 |
getImageBlob | (callback,mimeType?,quality?) | Canvas.prototype.toBlob 所需的参数 |
getImageURL | (mimeType?, quality?) | 返回 base64 编码的图片数据 ,Canvas.prototype.toDataURL 所需的参数 |
destroy | void | 销毁实例 |
以下是 setTool 可以传入的工具类型字符串。
工具类型 | 说明 |
---|---|
flag | 红色激光笔工具 |
free | 自由画笔工具 |
line | 直线工具 |
rect | 直角空心矩形工具 |
solidRect | 直角实心矩形工具 |
roundRect | 圆角空心矩形工具 |
solidRoundRect | 圆角实心矩形工具 |
circle | 空心圆形工具 |
solidCircle | 实心圆形工具 |
ellipse | 空心椭圆工具 |
solidEllipse | 实心椭圆工具 |
fill | 油漆桶填充工具 |
text | 文字工具 |
erase | 像素橡皮擦 |
handler 上的事件
事件名称 | 事件参数类型 | 说明 |
---|---|---|
inited | void | 初始化画布完成后会调用,画布会计算容器大小 |
afterSync | void 或 同步数据快照对象 | 普通用户同步完成后,会返回快照数据,房主不会返回快照 |
userUpdate | (account:string, updated:object) | 第一个参数是用户状态更新的 account, udpated 是用户更新了哪些状态的 diff 对象 |
userShapes | (account:string,visible:number,recycle:number) | 第一个参数是用户状态更新的 account ,visible 是用户的展示中的图层数, recycle 是用户可以重做的次数 |
globalShapes | (count:number) | 全局的图层总数 |
globalFile | (file:object) | 全局的画布里使用的 PPT 文档 |
canDraw | boolean | 画布允许绘制或者不允许绘制时,会抛出事件 |
handler 上的属性
userMap: { [uid: string]: any }; // shadow copy,映射当前的worker里的用户状态
file?: IFileActionOption // 当前使用的ppt数据
totalShapes: number; // 所有图层的总数
isEnable: boolean; // 是否允许绘制,当前画布是否允许绘制
DrawPluginUI 实例
为了方便用户使用图形 UI 对画布进行操作,建议在界面上展示图形界面封装对 DrawPlugin 实例方法的调用。开发者可以引入 DrawPluginUI 来实例化一个工具栏,方便用户进行操作。目前的 DrawPluginUI 使用的是preact
框架开发的,只是一个 SampleCode,具体代码放在 github 上,开发者可以参考里面的逻辑,自行实现其他框架的工具栏以及其他定制需求。开发者也可以提 PR 到 github 仓库里,增强 DrawPluginUI 的功能。
在项目里可以使用 <script src="/path/to/lib/DrawPluginUI.2.0.0.js"></script>
或者 import * as DrawPluginUI from 'path/to/lib/DrawPluginUI.2.0.0.js'
, 引入对象DrawPluginUI
。
DrawPluginUI
上只有mount
和unmount
两个方法,如下所示:
mount = (
root: HTMLElement, // mount方法的第一个参数是,工具栏挂载的容器DOM
user: { account: UID_TYPE, identity: 'owner' | 'normal' }, // 当前工具栏使用的account和identity
handler: WBHook, // DrawPlugin实例
options: UIControlDefaultOptions, // 工具栏支持的自定义配置项
compare?: Element // 可选参数,preact.render方法里的compare参数
) => [PreactElement, app] ; // 将会返回PreactElement对象和它挂载的DOM节点,在调用unmout的时候,需要将这个DOM节点,作为第二个参数传入unmount方法
unmount = (
root: HTMLElement, //工具栏挂载的容器DOM
app: Element // 工具栏挂载的DOM节点,用于替换
) => void;
)
使用的示例代码如下:
handler.on("inited", function() {
handler.enableDraw(true);
handler.setSize(6);
handler.setTool("free");
// 挂载工具栏
DrawPluginUI.mount(
document.getElementById("tb"),
{ account: "cs1", identity: "owner" }, // 注意identity参数
handler, // 传入DrawPlugin实例
{
customClear: false, // 是否使用自定义事件通知
toolbar: [
"flag",
"free",
{ type: "text" }, // 等同于 "text"
"fill",
{ type: "shapes", items: ["line", "rect", "circle"] },
{ type: "formats", fontsize: [23, 30, 44] },
"erase",
"undo",
"redo",
"clear",
{ type: "customTXT", label: "文档库", eventName: "selectDoc" } // 文档库建议普通用户不要设置
]
}
);
});
mount 方法传入的工具栏配置项的结构如下:
interface UIControlDefaultOptions {
customClear?: boolean; // 是否使用自定义清除,当用户点击clear按钮时,将不会清除画布,而是以customEvent事件通知,可以做二次弹窗确认
toolbar: ToolbarArray; // 这是一个数组,可以传入 上面 setTool可以传入的字符串工具类型
}
以下参数都是合法的工具栏配置参数,toolbar 数组里的顺序可以随意调换
{
toolbar:[
'free' // 这个是字符串类型的工具名称,和传入 {type:'flag'} 对象 是等价的
{type:'free'}, // 这两个是等价的,
// setTool可以传入的参数都可以这么设置
]
}
// 以下是特殊的类型
{
toolbar:[
{
type: "shapes", // shapes类型可以配置items数组,建议传入绘画图形的集合
items: [
"line", "rect", "circle"
]
},
{
type: "formats", // formats类型可以配置fontsize,size,colors 数组,若没有设置,将会使用默认值
fontsize: [23, 30, 44], // 设置工具的文本的大小,建议不要太多
// size:[4,6,12], // 也可以不传入 ,设置工具的大小,建议不要太多
colors: ['#334455','#ff0000'] ,// 建议不要太多
},
{
type: "customTXT", // 自定义文字,可以配置label,和eventName,
label: "文档库",
eventName: "selectDoc" ,// 这个eventName将会在 handler上的'custom'
},
{
type: "customSVG",
label: "<svg ></svg>",// 这里是svg文本,注意做好XSS防范
eventName: 'evt1'
},
{
type: "customIMG",
label: "https://163.com/logo.jpg", // 可以是图片的url,也可以是base64格式的
eventName: 'evt1'
},
]
}
当使用 DrawPluginUI 后,会在 hanlder 上抛出 customEvent
事件,示例如下:
handler.on('customEvent', function (event) { //这里的event是在
if (event === 'selectDoc') { //
// 建议开发者这里弹出选择文档的组件让用户选择,然后再调用下面的方法。传入的参数是格式如下
handler.setFileObj({
currentPage: 1,
docId: "c83e6ff4-297e-4fc3-b0c0-a0294776cb12",
type: "img",
pageCount: "13",
url: "http://nim.nosdn.127.net/60a9143c-146d-42f0-b55e-d8009dadc9e4_2_1.jpg",
urlStr: "http://nim.nosdn.127.net/60a9143c-146d-42f0-b55e-d8009dadc9e4_2_{index}.jpg"
type: "h5",
pageCount: "22",
})
} else if (event === 'closeDoc') { // 这个是在toolbar内部的自定义事件
handler.clearFile()
}else if(event === 'evt1'){
// 如果是用户点击了自定义设置的工具栏
}
}
断线重连
var isReconnecting;
whiteboard.on("connected", () => {
// 如果断线重连之后,重新连上,
if (isReconnecting) {
isReconnecting = false;
if (handler) {
// 连接上以后,同步数据
handler.readyToSyncData();
}
}
});
whiteboard.on("willReconnect", () => {
// 标记重连
isReconnecting = true;
});