使用 CKEditor 配置评论区富文本编辑器
在 Web 应用的评论区中集成富文本编辑器可以极大地提升用户体验。CKEditor 是一款成熟且可扩展的所见即所得编辑器,以下将以 id_content
为目标元素,介绍如何一步步集成并增强 CKEditor 功能。
🧩 基本集成
首先确保页面已正确引入 CKEditor 核心脚本,并准备好一个 <textarea id="id_content">
作为绑定目标。
<script src="/static/ckeditor/ckeditor/ckeditor.js"></script>
然后用如下代码初始化编辑器:
if (document.getElementById('id_content')) {
CKEDITOR.replace('id_content', {
width: 'auto',
height: '250px',
skin: 'moono',
resize_enabled: false,
tabSpaces: 4,
removePlugins: 'elementspath',
// 工具栏配置
toolbar: 'Custom',
toolbar_Custom: [
{ name: 'document', items: ['Source', '-', 'Preview', '-', 'Templates'] },
{ name: 'clipboard', items: ['Undo', 'Redo'] },
{ name: 'editing', items: ['Find', 'Replace', '-', 'SelectAll'] },
{ name: 'forms', items: ['Form', 'TextField', 'Textarea', 'Select', 'Button'] },
'/',
{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline', 'RemoveFormat'] },
{ name: 'paragraph', items: ['NumberedList', 'BulletedList', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight'] },
{ name: 'links', items: ['Link', 'Unlink'] },
{ name: 'insert', items: ['Image', 'Table', 'CodeSnippet', 'SpecialChar', 'Iframe'] },
'/',
{ name: 'styles', items: ['Font', 'FontSize'] },
{ name: 'colors', items: ['TextColor', 'BGColor'] },
{ name: 'tools', items: ['Maximize'] },
{ name: 'customplugins', items: ['dotblock', 'echartsblock', 'selectblock'] }
],
extraPlugins: 'codesnippet,prism,widget,lineutils,clipboard,dialog,mathjax,markdown,dotblock,echartsblock,uploadimage,selectblock',
uploadUrl: '/admin/upload/ckeditor-image',
filebrowserImageUploadUrl: '/admin/upload/ckeditor-image',
extraAllowedContent: 'div.echarts-placeholder[*]{*}(*)',
allowedContent: true,
mathJaxLib: '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
});
} else {
console.error('CKEditor 4: Element with ID "id_content" not found.');
}
📊 自定义图表插件支持(ECharts)
集成自定义插件如 echartsblock
:
CKEDITOR.plugins.addExternal('echartsblock', '/static/ckeditor/plugins/echartsblock/', 'plugin.js');
在插件中定义图表 JSON 配置面板,并在插入占位符时使用 data-option
存储配置。
例如默认图表配置可写成:
{
"title": { "text": "2024 年各月网站访问量统计" },
"xAxis": { "type": "category", "data": ["1月", ..., "12月"] },
"yAxis": { "type": "value" },
"series": [{ "type": "line", "data": [820, ..., 1750], "smooth": true }]
}
🧠 添加快捷键支持
通过 keystrokes
可以添加键盘快捷方式,增强效率:
CKEDITOR.config.keystrokes = [
[CKEDITOR.ALT + 121, 'toolbarFocus'],
[CKEDITOR.ALT + 67, 'codeSnippet'],
[CKEDITOR.ALT + 77, 'mathjax'],
[CKEDITOR.ALT + 122, 'maximize'],
[CKEDITOR.CTRL + CKEDITOR.SHIFT + 121, 'contextMenu']
];
🪄 工具栏自动隐藏与焦点交互
为了在评论区节省空间,可以设置工具栏在焦点时显示、失焦后隐藏:
document.addEventListener('DOMContentLoaded', function () {
CKEDITOR.on('instanceReady', function (ev) {
var editor = ev.editor;
editor.ui.space('top').setStyle('display', 'none');
editor.ui.space('contents').setStyle('height', '243px');
editor.on('focus', function () {
editor.ui.space('top').setStyle('display', '');
editor.ui.space('contents').setStyle('height', '200px');
});
editor.on('blur', function () {
editor.ui.space('top').setStyle('display', 'none');
editor.ui.space('contents').setStyle('height', '243px');
});
});
});
🎨 提示
- 如需支持自动高度,可考虑监听
change
或input
事件,在 iframe 或editor.document.$.body.scrollHeight
基础上调整编辑区域高度。 - 插件如
echartsblock
、dotblock
必须在/static/ckeditor/plugins/
目录下,并含plugin.js
文件才可被识别。 - 对上传图片,需确保
uploadUrl
对应后端上传处理接口已启用。
在 CKEditor 中集成 ECharts 插件教程
—— 实现图表插入与预览功能
1. 项目结构和文件介绍
假设你的项目目录结构大致如下:
D:\Nextcloud\go\blog\
├─ static\
│ ├─ ckeditor\
│ │ ├─ ckeditor\plugins\echartsblock\plugin.js
│ │ ├─ ckeditor\plugins\echartsblock\dialogs\echartsblock.js
│ │ └─ ckeditor\plugins\echartsblock\icons\echartsblock.png
├─ templates\
│ └─ write_blog.html
└─ adminpanel\
└─ templates\write_blog.html
plugin.js
:插件入口,负责注册插件和UI按钮。dialogs/echartsblock.js
:弹窗对话框定义,编辑图表配置 JSON。write_blog.html
:包含 CKEditor 初始化与插件调用的页面模板。- 另外,页面还包含
renderAllCharts
函数,实现 ECharts 渲染预览。
2. 插件核心代码解析
2.1 插件注册 (plugin.js)
CKEDITOR.plugins.add('echartsblock', {
requires: 'dialog',
icons: 'echartsblock',
init: function(editor) {
var pluginName = 'echartsblock';
CKEDITOR.dialog.add(pluginName, this.path + 'dialogs/echartsblock.js');
editor.addCommand(pluginName, new CKEDITOR.dialogCommand(pluginName));
editor.ui.addButton('echartsblock', {
label: '插入 ECharts 图表',
command: pluginName,
toolbar: 'insert',
icon: this.path + 'icons/echartsblock.png'
});
}
});
- 注册插件名称为
echartsblock
。 - 依赖对话框组件
dialog
。 - 在工具栏添加按钮,点击时弹出配置对话框。
2.2 插件弹窗定义 (dialogs/echartsblock.js)
CKEDITOR.dialog.add('echartsblock', function(editor) {
// 简单柱状图默认配置示例,格式是 JSON 字符串
var defaultExample = JSON.stringify({
title: {
text: '示例柱状图'
},
tooltip: {},
xAxis: {
data: ['苹果', '香蕉', '橘子', '梨子', '葡萄']
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10]
}]
}, null, 2); // pretty print 2 空格缩进
return {
title: '插入/编辑 ECharts 图表',
minWidth: 400,
minHeight: 200,
contents: [{
id: 'tab-basic',
label: '基本',
elements: [{
type: 'textarea',
id: 'option',
label: 'ECharts 配置 (JSON 格式)',
validate: CKEDITOR.dialog.validate.notEmpty("请填写配置"),
setup: function(element) {
this.setValue(element.getAttribute('data-option') || defaultExample);
},
commit: function(element) {
element.setAttribute('data-option', this.getValue());
}
}]
}],
onShow: function() {
var optionTextarea = this.getContentElement('tab-basic', 'option');
optionTextarea.getInputElement().setStyle('height', '500px');
var selection = editor.getSelection();
var element = selection.getSelectedElement();
if (element && element.hasClass('echarts-placeholder')) {
this.setupContent(element);
this._.selectedElement = element;
} else {
// 默认填充示例 JSON
this.getContentElement('tab-basic', 'option').setValue(defaultExample);
delete this._.selectedElement;
}
},
onOk: function() {
var dialog = this;
var json = dialog.getValueOf('tab-basic', 'option');
var encodedJson = CKEDITOR.tools.htmlEncode(json);
var elementToModify = this._.selectedElement;
if (elementToModify && elementToModify.hasClass('echarts-placeholder')) {
this.commitContent(elementToModify);
elementToModify.setText('ECharts 图表占位符 (点击编辑或预览): ' + encodedJson.substring(0, 100) + '...');
} else {
var html = '<div class="echarts-placeholder" contenteditable="false" ' +
'data-option=\'' + encodedJson + '\' ' +
'data-width="600px" data-height="400px" ' +
'style="width:600px;height:400px;' +
'border:1px dashed #999;' +
'background:#f9f9f9;' +
'display:flex; align-items:center; justify-content:center; text-align:center; font-size:14px; color:#888; cursor:pointer;">' +
'ECharts 图表占位符 (点击编辑或预览)<br>' +
encodedJson.substring(0, 100) + '...' +
'</div>';
editor.insertHtml(html);
}
if (typeof renderAllCharts === 'function') {
setTimeout(function() {
renderAllCharts(editor);
}, 50);
}
}
};
});
- 弹窗内包含一个
textarea
用于输入 ECharts JSON 配置。 - 支持编辑已插入图表占位符,或新插入一个带
data-option
属性的占位符div
。 - 插入后调用
renderAllCharts
函数渲染图表预览。
2.3 ckeditor插件配置(id_content)
<script>
if (document.getElementById('id_content')) {
CKEDITOR.replace('id_content', {
toolbar_Custom: [
{ name: 'customplugins', items: [ 'echartsblock'] }
],
// 额外插件
extraPlugins: 'echartsblock',
});
} else {
}
</script>
3. 编辑器中图表的插入与编辑流程
- 点击工具栏的“插入 ECharts 图表”按钮。
- 弹出 JSON 配置编辑框,输入符合 ECharts 配置规范的 JSON 字符串。
- 确认后,编辑器插入一个不可编辑的
div.echarts-placeholder
,携带配置数据。 - 触发
renderAllCharts(editor)
,将配置渲染为静态图片形式插入,防止编辑时占用大量资源。 - 点击占位符可再次编辑配置。
注意事项:ECharts 图表编辑json技巧
-
当光标位于 ECharts 占位符的下方(紧贴图表占位符)时,按删除键可以逐步清空该位置的文本。
-
如果删除到光标“消失”或文档尾部无内容时,请不要继续按删除键。
-
继续删除会导致图表占位符被误删,从而使后续点击
echartsblock
插件按钮时,编辑器在错误位置插入重复或异常的图表代码。 -
建议保持编辑器中至少存在一个空段落,以避免插入位置异常和图表丢失。
ckeditor 中渲染,可用再次编辑
<script>
function renderAllCharts(editor) {
// 确保编辑器处于可视化模式且已就绪
if (!editor || !editor.editable || editor.mode !== 'wysiwyg') {
console.log('ECharts 渲染跳过:编辑器未就绪或不在可视化模式。');
return;
}
const editable = editor.editable();
// 遍历所有 ECharts 占位符
editable.find('.echarts-placeholder').toArray().forEach(function (widget) {
const el = widget.$; // 获取原始的 .echarts-placeholder div 的 DOM 元素
// 避免重复渲染:如果这个占位符已经包含了一个 ECharts 预览图片,就跳过
if (el.querySelector('img[alt^="ECharts 图表预览"]')) {
// console.log('ECharts 渲染跳过:已存在预览图片。');
return;
}
// 清除占位符内的任何旧内容(如文本提示或旧图片),准备渲染新内容
el.innerHTML = '';
// 获取 ECharts 配置 JSON
const rawOption = el.getAttribute('data-option');
let option;
try {
option = JSON.parse(rawOption);
} catch (e) {
console.warn('ECharts 渲染失败:无效的 ECharts 配置 JSON。', e, '原始配置:', rawOption);
el.innerHTML = '<div style="width:100%;height:100%; border:1px dashed #f00; background:#fff0f0; display:flex; align-items:center; justify-content:center; font-size:14px; color:#f00;">ECharts 配置错误!</div>';
return;
}
try {
// 创建一个临时 div 用于离屏渲染 ECharts
const tempDiv = document.createElement('div');
// 确保 tempDiv 继承原占位符的尺寸,如果 style 属性未设置,则使用默认值
tempDiv.style.width = el.style.width || el.getAttribute('data-width') || '600px';
tempDiv.style.height = el.style.height || el.getAttribute('data-height') || '400px';
// 将 tempDiv 放置到屏幕外,不影响页面布局
tempDiv.style.position = 'absolute';
tempDiv.style.left = '-9999px';
tempDiv.style.top = '-9999px';
document.body.appendChild(tempDiv); // 将临时 div 添加到 body
const chart = echarts.init(tempDiv); // 在临时 div 上初始化 ECharts 实例
// 监听 'finished' 事件,该事件在图表渲染完成后触发,是捕获图片的最佳时机
chart.on('finished', function() {
const canvas = tempDiv.querySelector('canvas');
if (canvas) {
const img = document.createElement('img');
img.src = canvas.toDataURL('image/png'); // 将 canvas 内容转换为图片 URL
img.style.maxWidth = '100%'; // 确保图片不会超出容器
img.style.height = 'auto'; // 保持图片宽高比
img.style.pointerEvents = 'none'; // 防止图片干扰编辑器交互
img.alt = 'ECharts 图表预览';
// 将生成的图片插入到原始的 .echarts-placeholder div 中
el.innerHTML = ''; // 再次清空,确保只显示图片
el.appendChild(img);
}
chart.dispose(); // 销毁 ECharts 实例,释放资源
document.body.removeChild(tempDiv); // 移除临时 div
// console.log('ECharts 渲染完成并已转换为图片。');
});
// 设置 ECharts 配置并强制重绘
chart.setOption(option, true); // true 表示不合并配置,确保完全重新渲染
chart.resize(); // 强制 ECharts 重新计算和绘制尺寸
// 添加一个回退计时器,以防 'finished' 事件因某种原因未触发(例如,数据为空的图表)
setTimeout(() => {
if (!chart.isDisposed()) { // 检查 ECharts 实例是否已经被处理过
console.warn("ECharts 'finished' 事件未触发,执行超时回退捕获。");
const canvas = tempDiv.querySelector('canvas');
if (canvas) {
const img = document.createElement('img');
img.src = canvas.toDataURL('image/png');
img.style.maxWidth = '100%';
img.style.height = 'auto';
img.style.pointerEvents = 'none';
img.alt = 'ECharts 图表预览 (回退)';
el.innerHTML = '';
el.appendChild(img);
}
chart.dispose();
document.body.removeChild(tempDiv);
}
}, 2000); // 延长回退超时时间
} catch (e) {
console.error('ECharts 渲染过程中发生错误:', e);
el.innerHTML = '<div style="width:100%;height:100%; border:1px dashed #f00; background:#fff0f0; display:flex; align-items:center; justify-content:center; font-size:14px; color:#f00;">ECharts 渲染过程中发生错误!<br>详情请查看控制台。</div>';
}
});
}
</script>
4. ECharts 图表渲染机制说明
4.1 renderAllCharts
函数
- 遍历所有
.echarts-placeholder
元素。 - 读取
data-option
JSON,初始化离屏临时div
。 - 使用
echarts.init
创建图表实例,监听finished
事件后将 canvas 转为图片。 - 替换原占位符内容为生成的图片,减少编辑器渲染压力。
- 设置回退定时器防止事件未触发导致无法显示。
4.2 页面首次加载时自动渲染(detail.html)
<script>
document.addEventListener('DOMContentLoaded', function() {
// 查找所有带有 data-option 属性的 div,无论是 placeholder 还是已渲染的 div
document
.querySelectorAll('div[data-option]') // <-- 修改此处选择器
.forEach(function(el) {
try {
// 避免重复渲染已经激活的 ECharts 实例
if (el._echarts_instance_) {
return;
}
var option = JSON.parse(el.getAttribute('data-option'));
var chartDom = document.createElement('div');
// 从 data-属性获取尺寸
chartDom.style.width = el.getAttribute('data-width') || '600px';
chartDom.style.height = el.getAttribute('data-height') || '400px';
// 复制 data-* 属性到新的容器 (可选,但有助于调试和一致性)
chartDom.setAttribute('data-option', el.getAttribute('data-option'));
if (el.hasAttribute('data-width')) chartDom.setAttribute('data-width', el.getAttribute('data-width'));
if (el.hasAttribute('data-height')) chartDom.setAttribute('data-height', el.getAttribute('data-height'));
// 替换占位节点或已渲染但未激活的节点
el.parentNode.replaceChild(chartDom, el);
echarts.init(chartDom).setOption(option);
} catch (e) {
console.error('ECharts 渲染失败:', e);
}
});
});
</script>
- 页面中所有带
data-option
属性的元素将直接被渲染成 ECharts 图表。
5. 使用示例与效果展示
5.1 插入示例
编辑器中点击按钮,输入如下 JSON 配置:
{
"title": { "text": "示例折线图" },
"tooltip": {},
"xAxis": {
"data": ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
},
"yAxis": {},
"series": [{
"name": "销量",
"type": "line",
"data": [5, 20, 36, 10, 10, 20]
}]
}
确认后会插入带有图表预览的占位符,显示图表快照。
5.2 编辑示例
点击图表占位符,会弹出对话框,显示 JSON 配置,允许修改。
5.3 效果截图
插入时显示占位符,渲染完成后显示图表图片,编辑时弹出 JSON 配置框。
6. 结语与扩展建议
- 本方案将 ECharts 配置以 JSON 字符串形式存储于元素属性,简洁直观。
- 通过将图表渲染为图片预览,提升编辑器性能和稳定性。
- 可扩展支持图表交互和动态更新。
- 推荐对输入 JSON 进行格式校验和美化,提升用户体验。
- 适合用于技术内容编辑、后台数据展示、报告生成等多种应用场景。
本文作者: 永生
本文链接: https://www.yys.zone/detail/?id=149
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
评论列表 (0 条评论)