初探CKEditor5

beat365网址大全 📅 2025-11-04 19:23:21 👤 admin 👁️ 7569 ❤️ 555
初探CKEditor5

选择的原因这里将wangEditor5与CkEditor5作为比较

CkEditor优势

文档详细

用户量大

自定义插件扩展非常容易

CkEditor劣势

纯英文文档,无翻译

未解决的_issues_尚且较多

npm月下载量比较CKEditor月下载量如下图所示:

wangEditor月下载量如下图所示:

结论:CKEditor的月下载量吊打了wangEditor,并且最新的wangEditor5月下载量不过1400,个人比较倾向于使用月下载量10000+的

github关键数据比较:CKEditor的GitHub星星数与issues数量如下图所示:

wangEditor的GitHub星星数与issues数量如下图所示:

结论:wangEditor在star上和处理问题的及时程度秒杀了CKEditor, 这一定程度可能也是CKEditor的star如此少的原因,但介于wangEditor是由国人开发,国人开发的特点是star量比较虚,实际使用量和其star量不成正比,参考字节跳动web infra团队开源的Modern.js

打包体积比较相同功能的情况下CKEditor体积

wangEditor体积

结论:

同样的功能CKEditor的打包体积更小,B端要求不高,C端原则基本遵循满足需求的情况下,能使用小的就使用小的

CKEditor功能栏采取按需加载的方式,即我们如果不需要某些功能,那些功能文件就不会被打包,测试了一下如果只留一个Bold插件的情况下可以减少_42.2kb_的js体积

功能比较主要功能大同小异,性能上的表现也几乎一致,CKEditor的亮点在于其插件化的扩展更加友好, 除了自己编写插件,还可以继承已有的插件,重写对应的插件方法,如果需要继承,官网的插件都是提供对应的方法的,重写方法就好。并且插件的编写方式类似于编写webpack的插件,提供了一系列的钩子在特定的条件下执行,后面会详细介绍。

CkEditor基本环境搭建根据业务需要, 使用create-react-app搭建项目, 使用react-app-rewired启动项目, 使用customize-cra修改webpack配置

安装1234567yarn add --dev \ css-loader@5 \ postcss-loader@4 \ raw-loader@4 \ style-loader@2 \ webpack@5 \ webpack-cli@4

webpack配置CKEditor集成到项目中需要重新定义某些文件的解析规则否则会报Error: Cannot read property 'getAttribute' of null (ckeditor)https://stackoverflow.com/questions/66416928/error-cannot-read-property-getattribute-of-null-ckeditor

需要做如下解析:

内置的svg文件需要使用raw-loader

如果需要做视觉集成(主题), 内置的css文件优先使用postcss-loader,并且引入@ckeditor/ckeditor5-theme-lark包, 主题将增加约_30kb_的打包体积, 开发者可以酌情考虑。链接如下:https://ckeditor.com/docs/ckeditor5/latest/examples/framework/theme-customization.html

对于特定的CKEditor中的css样式,可以用如下的正则表达式判断

123456789101112131415161718const CKERegex = { svg: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/, css: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,};{ loader: 'postcss-loader', options: { postcssOptions: styles.getPostCssConfig({ themeImporter: { themePath: require.resolve('@ckeditor/ckeditor5-theme-lark') }, minify: true }) }}

富文本的模式

经典模式如下

npm install --save @ckeditor/ckeditor5-build-classic

内联模式

npm install --save @ckeditor/ckeditor5-build-inline

气泡模式

npm install --save @ckeditor/ckeditor5-build-balloon

气泡块模式npm install --save @ckeditor/ckeditor5-build-balloon-block

文档模式npm install --save @ckeditor/ckeditor5-build-decoupled-document

文档链接如下:https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/alternative-setups/predefined-builds.html

插件添加插件添加逻辑必须遵循官方文档个人比较推荐从以下的网站进去,定义自己的模板https://ckeditor.com/ckeditor-5/online-builder/

按照指示下载完成后会有以下的文件,还有demo,如下图

也可以根据已有的插件集,像上图那样的自己去引用官方已有的插件, 对应的所有插件如下:https://ckeditor.com/docs/ckeditor5/latest/features/index.html

编写CkEditor组件当前组件被antd的Form包裹,作为表单组件

123456789101112131415161718192021222324252627282930313233343536373839function CKEditor(props) { const { onChange, readOnly, value, onError } = props; const handleChange = useCallback((event, editor) => { const data = editor.getData(); onChange(data) }, [onChange]) const handleBlur = useCallback((event, editor) => { const data = editor.getData(); onChange(data) }, [onChange]) return ( '} onReady={editor => { // You can store the "editor" and use when it is needed. console.log('Editor1 is ready to use!', editor); }} // 相当于 editor.model.document.on("change:data", handleChange) onChange={handleChange} onBlur={handleBlur} onError={onError || console.error} /> );}export default CKEditor;

关键属性如下:

editor:使用的富文本编辑器模板

config:富文本编辑器的配置项,默认会用Editor.defaultConfig配置好的

disabled: 富文本是否可编辑

data: 富文本的html字符串

onReady: 编辑器刚构建好时会调用,官网推荐在这里将富文本做存储,避免重复构建

onChange: 改变时调用

onBlur: 焦点变化时调用

onError: 出现错误时调用常见问题

编辑器被重复引用:

产生原因:

config属性中有plugins属性,官网将对应的插件直接在组件引用

1234567891011121314import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';const editorConfiguration = { plugins: [ Essentials, Bold, Italic, Paragraph ], toolbar: [ 'bold', 'italic' ]};

问题链接:https://github.com/ckeditor/ckeditor5/issues/5776

解决方案:编写前文图例的ckeditor.js,然后将CkEditor组件需要的配置注入,实际编写的组件中引入对应js的文件,不需要的删除,目前没看到什么场景必须使用config.plugins

打包内存溢出(我没遇到过)解决方案官网已经给出,链接如下:https://ckeditor.com/docs/ckeditor5/latest/installation/getting-started/frameworks/react.html#integrating-a-build-from-the-online-builder

中文包全局引入

1import "@ckeditor/ckeditor5-build-classic/build/translations/zh-cn"

1Editor.defaultConfig.language = 'zh-cn'

自定义插件npm包的形式个人推荐使用ckeditor5-package-generator

CkEditor5要求所有的插件包都必须以ckeditor5-开头,中间字符为0-9、a-z、. - _,创建和打包的时候都会正则校验

上述插件会提供一个打包模板,我们可以在这个模板上扩展,会有一个已经编写好的插件

详细的编写方式可见参考链接

简述如下:

一个插件就是一个继承于Plugin的class

editor.ui.componentFactory负责添加一个工具,可以使用模型层的监听器为用户对工具栏上自定义ui定义其操作

注意点:如果多个插件打包,并非工具生成的目录, 目录如下可以直接打包,也可以使用dll模式,这里推荐直接打包,webpack5自带缓存,dll不会对打包速度有明显的效果

123456789101112131415161718192021222324...|- src |- ckeditor5-plugin1 |- lang |- translations |- zh-cn.po |- contexts.json |- src |- index.js |- ckeditor5-plugin2 |- lang |- translations |- zh-cn.po |- contexts.json |- src |- index.js |- ckeditor5-plugin3 |- lang |- translations |- zh-cn.po |- contexts.json |- src |- index.js...

1234567891011121314151617181920212223242526272829303132import Plugin from '@ckeditor/ckeditor5-core/src/plugin';class InsertImage extends Plugin { init() { const editor = this.editor; // 为工具栏添加一个 insertImage,使用 imageIcon editor.ui.componentFactory.add( 'insertImage', locale => { const view = new ButtonView( locale ); view.set( { label: 'Insert image', icon: imageIcon, tooltip: true } ); // 监听执行这个Button,会弹出输入url,监听模型层,根据输入的url展示对应的图片,并插入到模型层对应的鼠标 selection 节点上 view.on( 'execute', () => { const imageUrl = prompt( 'Image URL' ); editor.model.change( writer => { const imageElement = writer.createElement( 'imageBlock', { src: imageUrl } ); editor.model.insertContent( imageElement, editor.model.document.selection ); } ); } ); return view; } ); }}

自定义扩展完整文档可见CKEditor提供了自定义扩展,根据先有的adapter做扩展,以下都将用Image Uploader做举例

例如: Upload adapter作用是在文件编辑器和文件上传服务器之间构建一个桥梁,我们可以自定义扩展用户上传的行为以及上传到服务器的接口等,这个adapter是基于File repository plugin创建的,像image upload plugin也是基于这个创建的,``File repository plugin`是整个上传的核心插件。

区别:自定义插件是完全从ui到功能的自定义实现自定义扩展是基于已有的富文本编辑器功能,做一些替换或者功能的提升

工作流程

首先,图像(或图像)需要进入富文本编辑器内容。有很多方法可以做到这一点,例如:

从剪贴板粘贴图像

从文件系统中拖动文件

通过文件系统对话框选择图像,即选择上传

这些行为都将被image uploader plugin插件捕获

对于每个上传的图像都会被image upload plugin创建出file loader的实例,通过upload adapter将其上传到服务器,并根据url正确的展示在编辑器内

在上传图片时,image upload plugin会做以下事情

创建图像的占位符

插入到编辑器

展示每一个图像的进度条

上传完成前如果做了删除图片的操作,终止上传

图片上传完成,upload adapter通知editor(调用Promise),image upload创建的标签中的src和srcset将被替换

扩展方式

image upload必须在编辑器中启用。它在所有官方版本中默认启用,如果正在自定义CKEditor 5编辑器,那么需要自己写这个插件

需要自定义upload adapter,我们可以根据使用已有的upload adapter,也可以自定义(建议自定义,将上传和回显操作控制在自己手中)

编写UploadAdapter

自定义UploadAdapter,主要是自定义上传的服务器路径以及自定义回显方式

根据UploadAdapter参考链接`可以找到对应的方法

upload()返回一个Promise

abort()上传中止所做的操作

通常我们使用XMLHttpRequest在这个UploadAdapter中,详细请见创建一个简易的UploadAdapter,完整代码如下

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899class MyUploadAdapter { constructor(loader) { // The file loader instance to use during the upload. this.loader = loader; } // Starts the upload process. upload() { return this.loader.file.then( (file) => new Promise((resolve, reject) => { this.initRequest(); this.initListeners(resolve, reject, file); this.sendRequest(file); }), ); } // Aborts the upload process. abort() { if (this.xhr) { this.xhr.abort(); } } // Initializes the XMLHttpRequest object using the URL passed to the constructor. initRequest() { this.xhr = new XMLHttpRequest(); // Note that your request may look different. It is up to you and your editor // integration to choose the right communication channel. This example uses // a POST request with JSON as a data structure but your configuration // could be different. this.xhr.open('POST', '/api/img/upload', true); this.xhr.responseType = 'json'; // 设置请求头(权限控制) this.xhr.setRequestHeader('AUTHENTICATION', '111'); } // Initializes XMLHttpRequest listeners. initListeners(resolve, reject, file) { const { xhr, loader } = this; const genericErrorText = `Couldn't upload file: ${file.name}.`; xhr.addEventListener('error', () => reject(genericErrorText)); xhr.addEventListener('abort', () => reject()); xhr.addEventListener('load', () => { const { response } = xhr; // This example assumes the XHR server's "response" object will come with // an "error" which has its own "message" that can be passed to reject() // in the upload promise. // // Your integration may handle upload errors in a different way so make sure // it is done properly. The reject() function must be called when the upload fails. if (!response || response.error) { return reject( response && response.error ? response.error.message : genericErrorText, ); } // If the upload is successful, resolve the upload promise with an object containing // at least the "default" URL, pointing to the image on the server. // This URL will be used to display the image in the content. Learn more in the // UploadAdapter#upload documentation. return resolve({ default: response.data, }); }); // Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded // properties which are used e.g. to display the upload progress bar in the editor // user interface. if (xhr.upload) { xhr.upload.addEventListener('progress', (evt) => { if (evt.lengthComputable) { loader.uploadTotal = evt.total; loader.uploaded = evt.loaded; } }); } } // Prepares the data and sends the request. sendRequest(file) { // Prepare the form data. const data = new FormData(); data.append('attach', file); // Important note: This is the right place to implement security mechanisms // like authentication and CSRF protection. For instance, you can use // XMLHttpRequest.setRequestHeader() to set the request headers containing // the CSRF token generated earlier by your application. // Send the request. this.xhr.send(data); }}export default MyUploadAdapter;

相关推荐

🔥三国杀正版大揭秘!拒绝踩坑,玩就玩最正的!
365bet体育投注网站

🔥三国杀正版大揭秘!拒绝踩坑,玩就玩最正的!

📅 10-11 👁️ 7313
Android 陷入工廠模式:退出 Android 工廠模式
beat365网址大全

Android 陷入工廠模式:退出 Android 工廠模式

📅 09-18 👁️ 9411
如何注销碧蓝航线账号?详细教程分享
beat365网址大全

如何注销碧蓝航线账号?详细教程分享

📅 10-09 👁️ 4466