如何在同一网页加载多个CKEditor工具栏?Knockout JS场景疑难
解决CKEditor + Knockout JS多div编辑器工具栏不显示问题
嘿,我刚好碰到过类似的CKEditor + Knockout的坑,来给你捋捋怎么解决!你的问题核心是两个点:CKEditor对多实例的标识逻辑,以及Knockout自定义绑定和CKEditor实例的生命周期同步,咱们一步步来拆:
问题根源拆解
- 用
id="ckToolbar"时只有第一个div显示工具栏:因为CKEditor的replace方法会绑定到第一个匹配该id的元素,id是页面唯一的,所以第二个div根本没被实例化。 - 改用类选择器后工具栏全没了:CKEditor默认对textarea的支持更完善,直接用div的话需要额外配置,再加上Knockout自定义绑定没处理好实例化逻辑,导致编辑器没正确初始化。
解决方案步骤
1. 先修正CKEditor的Config.js配置
确保你的Config.js里开启了div元素的编辑支持,不然CKEditor会把div当成普通内容过滤掉:
CKEDITOR.editorConfig = function( config ) { // 允许编辑带任意类的div元素 config.extraAllowedContent = 'div(*)'; // 启用可视化编辑模式(如果需要) config.startupMode = 'wysiwyg'; // 自定义工具栏(根据你的需求调整) config.toolbar = [ { name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline' ] }, { name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent' ] }, { name: 'links', items: [ 'Link', 'Unlink' ] }, { name: 'tools', items: [ 'Source' ] } ]; // 其他你的原有配置... };
2. 编写Knockout自定义绑定
这一步是关键,要让每个div都能正确实例化CKEditor,同时实现数据双向绑定,还要处理实例销毁避免内存泄漏:
ko.bindingHandlers.ckeditor = { init: function(element, valueAccessor, allBindingsAccessor) { // 获取绑定的内容值和CKEditor配置选项 var contentValue = valueAccessor(); var editorOptions = allBindingsAccessor().ckeditorOptions || {}; // 给元素补个唯一id(如果页面里没设置的话) if (!element.id) { element.id = 'ckeditor-' + Math.random().toString(36).substring(2, 10); } // 实例化CKEditor到当前div var editor = CKEDITOR.replace(element, editorOptions); // 编辑器内容变化时,同步更新Knockout的observable editor.on('change', function() { var newContent = editor.getData(); if (ko.isObservable(contentValue)) { contentValue(newContent); } }); // 当Knockout销毁元素时,同步销毁CKEditor实例 ko.utils.domNodeDisposal.addDisposeCallback(element, function() { if (editor) { editor.destroy(true); // true表示清理DOM元素 } }); }, update: function(element, valueAccessor) { // Knockout数据变化时,同步更新编辑器内容 var currentContent = ko.unwrap(valueAccessor()); var editor = CKEDITOR.instances[element.id]; // 避免重复设置内容(防止无限循环) if (editor && editor.getData() !== currentContent) { editor.setData(currentContent); } } };
3. HTML页面的正确写法
每个div需要有唯一标识(可以手动设id,也可以让自定义绑定自动生成),然后应用自定义绑定:
<!-- 第一个编辑器 --> <div class="ckeditor-target" data-bind="ckeditor: editorContent1, ckeditorOptions: { customConfig: '/path/to/your/Config.js' }"></div> <!-- 第二个编辑器 --> <div class="ckeditor-target" data-bind="ckeditor: editorContent2, ckeditorOptions: { customConfig: '/path/to/your/Config.js' }"></div>
4. Knockout ViewModel配置
定义对应的observable来绑定编辑器内容:
function EditorViewModel() { var self = this; // 初始化编辑器内容 self.editorContent1 = ko.observable('<p>第一个编辑器的初始内容</p>'); self.editorContent2 = ko.observable('<p>第二个编辑器的初始内容</p>'); } // 应用绑定 ko.applyBindings(new EditorViewModel());
关键注意点
- 每个编辑器元素必须有唯一id:CKEditor内部用id来管理实例,即使你用类选择器,也得保证每个div的id唯一(自定义绑定里已经加了自动生成逻辑,也可以手动设置更易读的id)。
- 不要直接用CKEDITOR.replace遍历类元素:因为Knockout的绑定是异步的,手动遍历会和Knockout的DOM操作冲突,必须通过自定义绑定来处理。
- 销毁编辑器实例:单页应用场景下一定要在dispose回调里销毁实例,不然会有内存泄漏问题。
这样配置后,两个div应该都能正常显示工具栏,并且和Knockout的observable实现双向数据绑定啦!
内容的提问来源于stack exchange,提问作者user1647160
相关产品推荐
相关产品推荐

