尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用 CKEditor 5 打造类 Word 的文档编辑器:基于 DecoupledEditor 的纸感 UI 实战指南

用 CKEditor 5 打造类 Word 的文档编辑器:基于 DecoupledEditor 的纸感 UI 实战指南 用 CKEditor 5 打造类 Word 的文档编辑器基于 DecoupledEditor 的纸感 UI 实战指南【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5本指南以 CKEditor 5 的DecoupledEditor为核心完整演示如何从零构建一个带纸张感界面的文档编辑器Document EditorUI 组件自由布局、工具栏与可编辑区分离、内容呈现在仿 A4 的页面上编辑体验接近原生文字处理软件。读完本文你将掌握DecoupledEditor的多种初始化方式、工具栏手动挂载机制以及一套可直接复用的 HTML/CSS 排版方案并理解其底层 UI 视图的组成与运行原理。一、理解文档编辑器与 DecoupledEditor 架构文档编辑器Document Editor是 CKEditor 5 中一类面向文档型内容的编辑形态它提供一个类似 Google Docs / 原生文字处理软件的界面编辑区域呈现出可滚动页面的效果非常适合用来撰写最终会打印或导出为 PDF 的文档。这种形态并非一个新的编辑器预置包而是构建在DecoupledEditor解耦式编辑器类之上的自定义 UI。DecoupledEditor的核心理念与经典Classic、行内Inline、气泡Balloon编辑器不同它只提供编辑能力不负责把 UI 组件渲染到页面上——工具栏、菜单栏、可编辑区全部由你决定放在哪里。从源码看这种解耦体现在三处packages/ckeditor5-editor-decoupled/src/decouplededitor.tsDecoupledEditor继承自ElementApiMixin( Editor )是标准编辑器基座其 UI 由DecoupledEditorUIDecoupledEditorUIView构成在 decouplededitoruiview.ts 中UI 视图只组合了三个组件——toolbar工具栏、menuBarView菜单栏、editable可编辑区并明确不提供任何组件在 DOM 中的具体排布而是通过extendTemplate为工具栏/菜单栏加上ck-reset_all、ck-rounded-corners类与正确的dir属性保证它们被放到页面任意位置后样式依旧干净、可正确呈现圆角与文本方向。类文件中的注释如此定位它dedicated to integrations which require a customized UI with an open structure, allowing developers to specify the exact location of the interface专为需要自定义 UI、开放结构、可指定界面精确位置的集成而设计。因此实现文档编辑器只需要四步初始化编辑器 → 准备承载 UI 的 HTML 结构 → 注入工具栏 → 用 CSS 把可编辑区打扮成纸。二、初始化编辑器三种创建方式与数据读取DecoupledEditor.create()是唯一的初始化入口不要直接调用构造函数。它返回一个 Promiseresolve 后即为可用的编辑器实例。根据你的页面情况有三种等价但场景不同的写法源码文档与 decouplededitor.ts 中均有完整示例方式一使用现有 DOM 元素并从中加载数据import { DecoupledEditor } from ckeditor5; DecoupledEditor .create( { root: { element: document.querySelector( .document-editor__editable ) } } ) .then( editor { console.log( Editor was initialized, editor ); // 手动把工具栏挂到页面上任意位置。 document.body.appendChild( editor.ui.view.toolbar.element ); } ) .catch( err { console.error( err.stack ); } );此时该元素的已有内容会被作为编辑器初始数据元素本身成为可编辑区editable。方式二传入原始数据字符串创建游离编辑器DecoupledEditor .create( { root: { initialData: pHello world!/p } } ) .then( editor { // 工具栏和可编辑区都需要手动追加到 DOM。 document.body.appendChild( editor.ui.view.toolbar.element ); document.body.appendChild( editor.ui.getEditableElement() ); } ) .catch( err { console.error( err.stack ); } );这种游离detached方式适合页面内容由客户端动态生成、初始化时 DOM 结构尚未就绪的场景。方式三既有元素 通过配置提供数据DecoupledEditor .create( { root: { element: document.querySelector( #editor ), initialData: h2Initial data/h2pFoo bar./p } } ) .then( editor { document.body.appendChild( editor.ui.view.toolbar.element ); } ) .catch( err { console.error( err.stack ); } );适合源元素内容难以预先写入但 DOM 节点已存在的集成场景。注意若同时以第一参数和root.initialData传入数据会抛出错误。读取与销毁输出数据无论哪种方式创建获取最终内容统一使用editor.getData()继承自Editor基类。销毁时的行为值得留意默认情况下销毁不会把内容写回源元素除非配置了updateSourceElementOnDestroy: trueDecoupledEditor销毁时不会自动移除 DOM 中的工具栏和可编辑区需要你在销毁链中手动清理见 decouplededitor.tseditor.destroy() .then( () { // 从 DOM 中移除工具栏。 editor.ui.view.toolbar.element.remove(); // 从 DOM 中移除可编辑区。 editor.ui.view.editable.element.remove(); console.log( Editor was destroyed ); } );关键细节等EditorUI#ready后再注入 UI文档编辑器与经典编辑器最核心的差异在于工具栏不会自动出现。你需要确保编辑器 UI 已就绪再把工具栏元素插入应用。官方推荐的做法是在create().then()回调中通过editor.ui.view.toolbar.element取到工具栏 DOM 节点并追加到目标容器DecoupledEditor.create( { root: { element: document.querySelector( .document-editor__editable ) }, cloudServices: { // CKEditor Cloud Services 配置。 // ... } } ) .then( editor { const toolbarContainer document.querySelector( .document-editor__toolbar ); toolbarContainer.appendChild( editor.ui.view.toolbar.element ); window.editor editor; } ) .catch( err { console.error( err ); } );从源码实现看这一手动挂载模式是刻意设计的DecoupledEditorUIView不把子视图渲染进任何固定父容器而DecoupledEditorUI在init()中通过_initToolbar()调用toolbar.fillFromConfig()按配置填充按钮、并调用addToolbar()注册进全局焦点追踪器实现 AltF10 等键盘导航随后触发ready事件见 decouplededitorui.ts。三、搭建 UI 结构承载工具栏与可编辑区的 HTML编辑器本身能跑但界面还需要宿主结构。官方示例使用如下 HTML对应配置中的.document-editor__editablediv classdocument-editor div classdocument-editor__toolbar/div div classdocument-editor__editable-container div classdocument-editor__editable pThe initial editor data./p /div /div /div.document-editor是最外层容器虽然并非强制但官方建议用它把整套 UI圈在一起便于整体排版与样式控制编辑器启动后会把工具栏注入.document-editor__toolbar把可编辑内容渲染进.document-editor__editable。完整可运行示例可见仓库中的 docs/_snippets/examples/document-editor.html 与配套的 docs/_snippets/examples/document-editor.js。重要警告创建编辑器时这段 HTML 必须已经存在于 DOM 中。要么把引导代码放在 HTML 之后要么使用DOMContentLoaded事件延迟 JavaScript 执行直到 DOM 就绪。四、样式化把界面打扮成纸上文档样式是文档编辑器物质化的关键。官方文档将 CSS 分为四层下面完整还原并逐段注释其意图可对照官方演示 docs/_snippets/examples/document-editor.html 中的style区块两者基本一致。4.1 主容器边框、圆角与纵向布局.document-editor { border: 1px solid var(--ck-color-base-border); border-radius: var(--ck-border-radius); /* 为文档编辑器设定纵向边界。 */ max-height: 700px; /* 使用 flex 容器便于纵向排列工具栏在上、页面区域在下。 */ display: flex; flex-flow: column nowrap; }这里大量使用 CKEditor 主题变量--ck-color-base-border、--ck-border-radius等保证与编辑器自身 UI 风格一致换肤时可自动跟随。4.2 工具栏制造悬浮在页面上方的错觉.document-editor__toolbar { /* 确保工具栏容器始终位于可编辑区之上。 */ z-index: 1; /* 用投影制造工具栏悬浮于页面之上的视觉。 */ box-shadow: 0 0 5px hsla( 0,0%,0%,.2 ); /* 复用 CKEditor 的 CSS 变量保持 UI 一致。 */ border-bottom: 1px solid var(--ck-color-toolbar-border); } /* 微调容器内的工具栏外观。 */ .document-editor__toolbar .ck-toolbar { border: 0; border-radius: 0; }4.3 可编辑区像一张纸一样居中于可滚动容器/* 让可编辑容器看起来像原生文字处理软件的工作区。 */ .document-editor__editable-container { padding: calc( 2 * var(--ck-spacing-large) ); background: var(--ck-color-base-foreground); /* 允许页面内容纵向滚动。 */ overflow-y: scroll; } .document-editor__editable-container .ck-editor__editable { /* 设定页面尺寸接近 A4 比例。 */ width: 15.8cm; min-height: 21cm; /* 让页面与容器边界保持距离。 */ padding: 1cm 2cm 2cm; border: 1px hsl( 0,0%,82.7% ) solid; border-radius: var(--ck-border-radius); background: white; /* 轻微的投影制造 3D 立体感。 */ box-shadow: 0 0 5px hsla( 0,0%,0%,.1 ); /* 水平居中页面。 */ margin: 0 auto; }4.4 内容排版字体、标题、段落与引用先设定页面内容的默认字体同时让标题下拉列表Headings Dropdown里的预览与实际内容风格一致/* 为页面内容设定默认字体。 */ .document-editor .ck-content, .document-editor .ck-heading-dropdown .ck-list .ck-button__label { font: 16px/1.6 Helvetica Neue, Helvetica, Arial, sans-serif; }官方提示对编辑内容标题、段落、列表等进行视觉样式化时建议使用.ck-content类作为选择器前缀。接下来是标题与段落的完整样式。注意.ck-heading-dropdown中的样式是为了让下拉预览与正文保持一致的观感体验/* 让标题下拉能容纳更大的标题样式。 */ .document-editor .ck-heading-dropdown .ck-list .ck-button__label { line-height: calc( 1.7 * var(--ck-line-height-base) * var(--ck-font-size-base) ); min-width: 6em; } /* 缩小下拉中标题预览的尺寸原尺寸过大无法在 UI 中完整呈现但保持相对比例。 */ .document-editor .ck-heading-dropdown .ck-list .ck-button:not(.ck-heading_paragraph) .ck-button__label { transform: scale(0.8); transform-origin: left; } /* 标题 1样式。 */ .document-editor .ck-content h2, .document-editor .ck-heading-dropdown .ck-heading_heading1 .ck-button__label { font-size: 2.18em; font-weight: normal; } .document-editor .ck-content h2 { line-height: 1.37em; padding-top: .342em; margin-bottom: .142em; } /* 标题 2样式。 */ .document-editor .ck-content h3, .document-editor .ck-heading-dropdown .ck-heading_heading2 .ck-button__label { font-size: 1.75em; font-weight: normal; color: hsl( 203, 100%, 50% ); } .document-editor .ck-heading-dropdown .ck-heading_heading2.ck-on .ck-button__label { color: var(--ck-color-list-button-on-text); } /* 标题 2正文样式。 */ .document-editor .ck-content h3 { line-height: 1.86em; padding-top: .171em; margin-bottom: .357em; } /* 标题 3样式。 */ .document-editor .ck-content h4, .document-editor .ck-heading-dropdown .ck-heading_heading3 .ck-button__label { font-size: 1.31em; font-weight: bold; } .document-editor .ck-content h4 { line-height: 1.24em; padding-top: .286em; margin-bottom: .952em; } /* 段落样式。 */ .document-editor .ck-content p { font-size: 1em; line-height: 1.63em; padding-top: .5em; margin-bottom: 1.13em; }最后用衬线字体与额外边距收尾让引用块更精致/* 让引用文字使用衬线字体并增加间距。 */ .document-editor .ck-content blockquote { font-family: Georgia, serif; margin-left: calc( 2 * var(--ck-spacing-large) ); margin-right: calc( 2 * var(--ck-spacing-large) ); }官方的 document-editor.html 还额外包含响应式细节例如在max-width: 960px时把页边距从2cm缩小为1.5em在更宽屏时调整页面宽度为60%以保持纸张观感——这些都可以按你的布局需求取舍。五、源码级解析文档编辑器 UI 是如何拼装起来的理解了用法后我们看底层是如何实现的这能帮助你更好地自定义扩展。5.1 UI 视图的三大组件decouplededitoruiview.ts 中的DecoupledEditorUIView组合了三个子视图组件类型说明toolbarToolbarView主工具栏通过shouldGroupWhenFull控制空间不足时是否自动分组收起按钮menuBarViewMenuBarView菜单栏视图editableInlineEditableUIView行内可编辑区可传入已有的editableElement复用 DOM 节点构造时还接受三个选项editableElement不传则由引擎自动创建、shouldToolbarGroupWhenFull对应工具栏的自动分组行为、label作为可编辑区的无障碍aria-label。渲染时通过registerChild( [ menuBarView, toolbar, editable ] )注册三个子组件。5.2 UI 初始化流程decouplededitorui.ts 中的init()按序完成为可编辑区命名与编辑根同名供 ARIA 识别渲染 UI 视图view.render()把可编辑元素注册进编辑器setEditableElement()将可编辑区的焦点状态绑定到全局焦点追踪器保证焦点在工具栏/下拉等区域时可编辑区仍保持聚焦样式通过editingView.attachDomRoot()把 DOM 可编辑元素与编辑引擎绑定——这是引擎与 UI 相遇的地方初始化占位符读取roots.main.placeholder配置并启用占位符视图初始化工具栏fillFromConfig()按toolbar配置填充按钮组件再注册进焦点系统触发ready事件。5.3 工具栏自动分组与解耦样式细节工具栏被放到页面任意位置后可能继承宿主页面的字体等样式因此视图层特意为工具栏与菜单栏追加了ck-reset_all重置继承样式与ck-rounded-corners圆角类并设置dir为 UI 语言方向见 decouplededitoruiview.ts。工具栏的行为可通过配置控制默认在空间不足时会动态分组把溢出的按钮收进更多里若想禁用自动分组可在配置中设置toolbar.shouldNotGroupWhenFull: true对应实现位于 packages/ckeditor5-ui/src/toolbar/toolbarview.ts其中DynamicGrouping与StaticLayout两种行为类按此选项二选一。六、进阶配置让文档编辑器更完整6.1 官方演示的完整配置仓库中实际运行文档编辑器演示的配置见 docs/_snippets/examples/document-editor.js可以作为一个很好的起点DecoupledEditor .create( { root: { element: document.querySelector( .document-editor__editable ) }, extraPlugins: [ TableColumnResize ], cloudServices: CS_CONFIG, ckbox: { tokenUrl: TOKEN_URL, forceDemoLabel: true, allowExternalImagesEditing: [ /^data:/, origin, /ckbox/ ] }, toolbar: { items: [ undo, redo, |, heading, |, bold, italic, |, link, insertImage, insertTable, mediaEmbed, |, bulletedList, numberedList, outdent, indent ] }, ui: { viewportOffset: { top: getViewportTopOffsetConfig() } } } ) .then( editor { document .querySelector( .document-editor__toolbar ) ?.appendChild( editor.ui.view.toolbar.element ); window.editor editor; setViewportTopOffsetDynamically( editor ); } ) .catch( err { console.error( err ); } );要点解读extraPlugins: [ TableColumnResize ]为表格启用列宽拖拽调整ckbox配置对接 CKBox 云媒体库tokenUrl为授权令牌地址forceDemoLabel用于演示环境打标allowExternalImagesEditing允许编辑外部来源图片toolbar.items使用|分隔按钮组ui.viewportOffset.top配合getViewportTopOffsetConfig()处理吸顶工具栏等场景下的悬浮层如上下文菜单、下拉定位偏移。6.2 按需启用更多内容功能官方建议为了获得最好的文档编辑体验还可以进一步配置以下功能高亮Highlight通过HighlightConfig配置荧光笔/马克笔颜色适合文档批注场景字号Font Size通过FontSizeConfig支持预设字号选项或自定义数值让页面排版更接近专业文字处理器字体系列Font Family通过FontFamilyConfig提供衬线/无衬线等多组字体。示例中的默认工具栏已经包含撤销/重做、标题、加粗/斜体、链接、插图、插表、媒体嵌入、项目符号/编号列表、缩进等能力加入上述字体与高亮插件后请记得把对应按钮如fontSize、fontFamily、highlight追加进toolbar.items。七、总结文档编辑器本身并不是一个新编辑器而是建立在DecoupledEditor之上的自定义 UI实践其成功的关键在于初始化DecoupledEditor.create()支持现有元素、纯数据字符串、元素数据三种方式统一用getData()取数挂载工具栏不会自动渲染必须在ready之后手动把editor.ui.view.toolbar.element注入页面销毁时也要手动移除工具栏与可编辑区结构用.document-editor→__toolbar__editable-container→__editable的语义化 HTML 承载 UI样式利用 CKEditor 主题变量与分层 CSS容器 → 工具栏 → 页面 → 内容排版实现 A4纸张视觉扩展基于源码中的 UI 视图组合工具栏/菜单栏/可编辑区与配置系统可继续叠加高亮、字号、字体系列等功能同时保留无障碍键盘导航如工具栏的 AltF10 快捷键等既有能力。正是得益于DecoupledEditor的只提供组件、不决定布局的开放架构你可以快速实验并创造出任意形态的自定义编辑器界面——文档编辑器只是其中最典型的一个范例。更多背景可参阅仓库中的 docs/examples/builds/document-editor.md其演示预设即本教程所述方案的直接产物。【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表