太原北京网站建设公司哪家好,wordpress 修改id,网络营销策略是什么,wordpress 的主题修改前言 对于word的协同编辑#xff0c;已经构思很久了#xff0c;但是没有找到合适的插件。今天推荐基于canvas/svg 的富文本编辑器 canvas-editor#xff0c;能实现类似word的基础功能#xff0c;如果后续有更好的#xff0c;也会及时更新。
Canvas-Editor
效果图 官方文…前言 对于word的协同编辑已经构思很久了但是没有找到合适的插件。今天推荐基于canvas/svg 的富文本编辑器 canvas-editor能实现类似word的基础功能如果后续有更好的也会及时更新。
Canvas-Editor
效果图 官方文档
canvas-editor | rich text editor by canvas/svgrich text editor by canvas/svghttps://hufe.club/canvas-editor-docs/ 官方DEMO
canvas-editorhttps://hufe.club/canvas-editor/
Gitee
canvas-editor: 同步自https://github.com/Hufe921/canvas-editorhttps://gitee.com/mr-jinhui/canvas-editor 前置条件与实现思路 虽然canvas-editor做的还不错API都比较完善但是对协同部分还是空缺因此我们此次的重点是实现协同部分的代码难免会修改源码部分。因此我们需要阅读源码实现 ts 代码的编写修改其源码实现协同。
下载源码并运行 大家可以直接从 github下载 也可以从刚才给的 gitee 下。 npm i // 下载相关依赖 npm run dev // 启动服务 npm run build // 打包项目 启动后能出来与demo一致的页面即完成了这一步。
实现用户选区 用户闪烁的光标目前还没有思路实现后面会攻克技术难点但是用户选取可以通过API实现 但是这个API会导致我的选取也会发生改变因此不能直接使用需要添加新的API 简单解释一下文件command文件向外暴露了API command 指向 commandAdapt 文件Adapt 文件中有需要的全部对象包括 画布、选取对象等可以直接进行底层绘制。 public setUserRange(startIndex: number, endIndex: number, payload?: string) {if (startIndex 0 || endIndex 0 || endIndex startIndex) returnconst isReadonly this.draw.isReadonly()if (isReadonly) return// 根据 index 获取 domList 设置颜色const elementList this.draw.getElementList()for (let i startIndex; i endIndex; i) {elementList[i].highlight payload||#F5EEA0}this.draw.render({isSetCursor: false,isCompute: false})} 这样用户选取才不会影响我的选取而取消选取就是设置透明色即可。 // 用户取消选取public setUserUnRange(startIndex: number, endIndex: number) {if (startIndex 0 || endIndex 0 || endIndex startIndex) returnconst isReadonly this.draw.isReadonly()if (isReadonly) return// 根据 index 获取 domList 设置颜色const elementList this.draw.getElementList()for (let i startIndex; i endIndex; i) {elementList[i].highlight transparent}this.draw.render({isSetCursor: false,isCompute: false})} 用户的光标是无状态的因此需要记录光标信息不然我重新设置了选取上次的选取是需要取消哦这个后面再说。
搭建CRDT 协同的核心就是数据一致性因此我们需要根据现有的数据结构实现CRDT。
新建yjs文件
// editor/core/websocket
import * as Y from yjs
import { WebsocketProvider } from y-websocket
import { IWebsocketProviderStatus } from ../../interface/Websocketexport class Ydoc {private ydoc: Y.Docprivate ymap: Y.Mapunknownprivate ytext: Y.Textprivate provider: any | undefinedprivate connect: boolean | undefinedprivate url: stringprivate roomname: stringconstructor(url: string, roomname: string) {console.log(new Ydoc)this.url urlthis.roomname roomnamethis.connect false// 创建 YDoc 文档this.ydoc new Y.Doc()this.ymap this.ydoc.getMap(map)this.ytext this.ydoc.getText(text)this.ymap.observe(() {})this.ytext.observe(() {})// 【方案二】 websocket 方式实现协同已自己搭建 websocket 服务this.provider new WebsocketProvider(this.url, this.roomname, this.ydoc)// 监听链接状态F·this.provider.on(status, (event: IWebsocketProviderStatus) {let { status } eventif (status connected) this.connect trueelse this.connect false})}public disConnection() {if (!this.connect) returnthis.provider.disconnect()}
}初始化 yjs 入口文件 index.ts 实现创建并传参 // 创建 websocketif (ydocInfo) {let { url, roomname, userid, username, color } ydocInfoif (!url || !roomname || !userid || !username)throw Error(参数错误url、roomname、userid、username必传)// 1. 如果存在则创建协同ydoc new Ydoc(url, roomname, userid, this.command, color)Reflect.set(window, ydoc, ydoc)console.log(用户${username}初始化)ydoc.userInitEditor(用户${username})} 这样整个编辑器需要实现协同的地方都能调用 ydoc 实现。
实现用户登录 Yjs 的基本使用中通过Map设置数据observe观察器实现数据获取协同部分不懂得可以看上一篇文章
深度解析 Yjs 协同编辑原理【看这篇就够了】_深度 解析yjs原理-CSDN博客文章浏览阅读1k次点赞21次收藏16次。本文带大家分析了Yjs的API、y-websocket 的实现原理、Yjs的应用及底层协同模型并使用Logic Flow 简单实现了其协同。大致的协同实现都有类似的思想大家以后需要协同的场景希望也能自行开发。_深度 解析yjs原理https://blog.csdn.net/weixin_47746452/article/details/135079472?spm1001.2014.3001.5501 这样用户每次初始化 Editor的时候都会广播其他用户
实现用户选区 用户每次操作鼠标抬起都会触发setRangeStyle事件 因此在这个事件中捕获用户的选区操作; yjs中则是正常转发然后调用上面实现的选区API public userRange({ data }: IYMapObserve) {let { startIndex, endIndex, userid, color } datathis.command.setUserRange(startIndex, endIndex, userid, color)} 效果如下 实现用户取消选区 现在的选区还是有bug的用户退出后无法识别还有就是单击时无法优化选区。 如上图我点击时理论上只占用一个格子不应该有选区【用户光标目前还没能实现】 if (startIndex endIndex) return 如果点击的开始与结束相同则不进行渲染。还有用户退出时清空用户选区 实现删除历史选区并删除lastRange 记录即可。 实现文本输入与删除 CanvasEvent监听了input 事件实现监听用户的输入修改参数实现在draw 中获取用户数据文档变化时会调用 draw 中的方法 因此在这里通过yjs广播事件修改参数后就能拿到用户新增的数据了 // 内容区变化public contentChangeHandle(payload: IEditorData) {/*** 因此在这里需要重新解析用户的选区设置不然会导致选区异常 BUG*/// 这里要解析 userRangelet { header, footer, main } payloadmain.forEach(item {if (item.userRange) {delete item.highlightdelete item.userRange}})this.setValue({ header, footer, main })} 实现效果 删除实现 keydown.ts 中对每个事件做了监听在该文件实现广播还是拿到本地的数据进行数据解析重新渲染。 效果如下 实现样式协同 样式的协同就是基于API实现的因为在main.ts中所有的菜单栏操作都是基于API实现因此我们需要在API调用处进行统一处理即可 // 选区样式改变public rangeStyleChange(payload: IRangeStyle) {// 样式只能针对 用户的当前选区// 直接使用 element 的事件机制let { startIndex 0, endIndex 0, attr, value } payloadconst isReadonly this.draw.isReadonly()if (isReadonly) returnif (startIndex endIndex) return// 根据 index 获取 domList 设置颜色const elementList this.draw.getElementList()for (let i startIndex; i endIndex; i) {let el elementList[i]if (el) {switch (attr) {case color:value ? (el.color string | undefinedvalue) : delete el.colorbreakcase bold:value ? (el.bold true) : delete el.boldbreakcase italic:value ? (el.italic true) : delete el.italicbreakcase fontSize:breakcase underline:value ? (el.underline true) : delete el.underlinebreakcase highlight:// 这里还有BUG因为用户选区结束又被设置透明value? (el.highlight string | undefinedvalue): delete el.highlightbreakdefault:break}}}this.draw.render({isSetCursor: false,isCompute: false})} 效果如下 用户协同选区与高亮冲突了这个还得在想办法处理。
打包在项目中使用 想要打包需要注释 main.ts 中的window.onload 事件将Editor 暴露到window身上 打包后将dist 放置到项目 public/libs.canvas-editor下【如果你打包报错基本上是TS语法检查的问题 let const 引入没用的模块等】 这样已经实现了基本的协同编辑了至于说 菜单栏、目录其实也是它自己加上的然后调用API实现 剩下的就是自行实现菜单栏调用API即可。 总结 对这个文章简单说一下
这个版本的代码肯定是粗糙的哈大家稍微谅解一下自己的TS还有点差功能实现上还有些缺陷有些功能底层限制了修改起来难度非常大比如协同选区问题后续会再优化协同的底层一定是数据一致性、广播监听、调用相应API实现相同功能后续可能会完善这部分代码争取能实现基本的、稳定的协同环境包括也会更新在 mpoe 项目中有一个稳定版本支撑协同编辑文章在书写过程中会发现BUG然后调整代码可能会出现页面与实际代码不匹配大家以实际代码为主哈也会持续关注大家的问题与需求大家可以提一些好的建议。