wangEditor Demo 目录实战指南:从 MENU_CONF 配置到在线示例部署

原创2026-09-20 11:12:231,417 阅读
文章标签:前端富文本UI组件

wangEditor Demo 目录实战指南:从 MENU_CONF 配置到在线示例部署

本篇技术指南围绕 wangEditor 开源仓库中 packages/editor/demo 目录展开,系统梳理该目录下十余个可直接运行的 HTML 示例如何演示编辑器的默认/简洁模式、HTML 读写、目录跳转、最大长度、多编辑器、代码高亮与四类扩展菜单,并讲解 demo 页中反复出现的 MENU_CONF 配置入口以及基于 GitHub Actions 的 demo 部署流水线。读完本文,你将掌握如何借助这些示例快速搭建、验证和定制 wangEditor 富文本编辑器,并能直接对照源码定位每一项能力背后的实现。

一、demo 目录定位与整体结构

packages/editor/demo 是 wangEditor 官方维护的一组纯前端 HTML 示例页,目的是让开发者无需搭建任何工程化环境,直接在浏览器中打开即可体验编辑器全部核心能力。它和同目录下的 packages/editor/examples(另一组更基础的示例)相互补充:demo 目录更侧重"场景化演示",例如仿腾讯文档布局、超大文档、标题目录等。

从目录结构看,demo 由以下几个部分构成:

  • 示例页面:index.html(默认模式)、simple-mode.html(简洁模式)、get-html.html、set-html.html、like-qq-doc.html(仿腾讯文档)、code-highlight.html、multi-editor.html(多编辑器)、catalog.html(标题目录)、max-length.html、huge-doc.html(10w 字大文档)以及四个扩展菜单示例 extend-menu.html / extend-menu-select.html / extend-menu-drop-panel.html / extend-menu-modal.html;
  • 公共资源:css/layout.css(左右分栏布局)、css/view.css(内容预览样式)、js/custom-elem.js(顶部导航与左侧菜单两个自定义元素)、js/huge-content.js(大文档测试数据生成);
  • 目录说明文档:README.md。

demo 的 README 只有三行,核心信息有两条:其一,修改左侧目录、在 demo 目录中搜索 MENU_CONF 即可快速定位各示例的配置片段;其二,demo 的部署参考 deploy-demos.yml 配置(实际文件位于仓库根目录的 .github/workflows/deploy-demos.yml)。这两条线索正好是本文两条主线:配置实战与部署流水线。

所有示例页面都遵循同一套骨架:通过 CDN 引入 @wangeditor/editor@latest 的 CSS 与 JS(https://unpkg.com/@wangeditor/editor@latest/dist/css/style.css 和 index.js,页面中也保留了 jsdelivr 的注释版本),然后使用全局对象 window.wangEditor(页面中简写为 E)调用 createEditor 与 createToolbar。

二、贯穿所有示例的核心配置:MENU_CONF

README 特别提示在 demo 目录中搜索 MENU_CONF,它几乎是每个示例页面的标配。以 index.html 为例:

<script>
  const E = window.wangEditor

  // 切换语言
  const LANG = location.href.indexOf('lang=en') > 0 ? 'en' : 'zh-CN'
  E.i18nChangeLanguage(LANG)

  window.editor = E.createEditor({
    selector: '#editor-text-area',
    html: '<p><br></p>',
    config: {
      placeholder: 'Type here...',
      MENU_CONF: {
        uploadImage: {
          fieldName: 'your-fileName',
          base64LimitSize: 10 * 1024 * 1024 // 10M 以下插入 base64
        }
      },
      onChange(editor) {
        console.log(editor.getHtml())

        // 选中文字
        const selectionText = editor.getSelectionText()
        document.getElementById('selected-length').innerHTML = selectionText.length
        // 全部文字
        const text = editor.getText().replace(/\n|\r/mg, '')
        document.getElementById('total-length').innerHTML = text.length
      }
    }
  })

  window.toolbar = E.createToolbar({
    editor,
    selector: '#editor-toolbar',
    config: {}
  })
</script>

从源码层面看,MENU_CONF 在 packages/core/src/config/interface.ts 中被定义为 IMenuConfig:一个以菜单 key 为键、以单个菜单配置对象(ISingleMenuConfig)为值的映射表,注释明确说明"各个 menu 的配置汇总,可以通过 key 获取单个 menu 的配置"。因此 MENU_CONF.uploadImage 就是上传图片菜单(uploadImage 对应的菜单 key)的专用配置:fieldName 指定上传文件表单字段名,base64LimitSize 指定小于该字节数(示例为 10MB)的图片直接以 base64 插入而不走服务器上传。示例页中几乎都保留了这一配置片段,方便对照测试上传行为。

页面中还展示了两个高频 API:editor.getHtml() 获取完整 HTML 内容,editor.getSelectionText() 获取当前选中文字,配合 editor.getText() 即可在 onChange 回调中实时统计"全部文字长度"与"选中文字长度",这是许多后台管理系统的常见字数统计需求。

三、默认模式与简洁模式(simple mode)

默认模式对应 index.html,是功能最完整的形态:完整工具栏 + 选中文字后的悬浮菜单(hoverbar)。简洁模式对应 simple-mode.html,其创建方式与默认模式几乎一致,区别仅在于多传了一个 mode 参数:

window.editor = E.createEditor({
  selector: '#editor-text-area',
  html,
  mode: 'simple', // 关键差异
  config: { ... }
})

window.toolbar = E.createToolbar({
  editor,
  mode: 'simple', // 工具栏同样要传 mode
  selector: '#editor-toolbar',
  config: {}
})

simple-mode.html 的页面文案直接说明了这一模式的两个效果:"简化工具栏菜单"与"取消选中文字的悬浮菜单"。从 packages/editor/src/create.ts 的实现可以看到,mode 的取值(default 或 simple)决定了加载哪一套全局配置:Boot.editorConfig 还是 Boot.simpleEditorConfig——这也是两套模式行为差异的底层来源。当需要"轻量编辑器"场景(如评论框、简短的备注输入)时,简洁模式是开箱即用的选择。

四、HTML 的读与写:getHtml 与 setHtml

get-html.html 与 set-html.html 一读一写,完整演示了编辑器和外部 HTML 之间的数据交换。

读取 HTML(get-html.html)在 onChange 中实时把 editor.getHtml() 的结果同时渲染到右侧的预览区域(editor-content-view)和只读 textarea,实现"编辑即预览":

onChange(editor) {
  const html = editor.getHtml()
  document.getElementById('editor-content-view').innerHTML = html
  document.getElementById('editor-content-textarea').value = html
}

写入 HTML(set-html.html)通过按钮触发 editor.setHtml(textarea.value) 把 textarea 中的 HTML 注入编辑器。该页面在 textarea 的初始化注释中明确提醒:wangEditor 只识别 editor.getHtml() 生成的 html 格式,不可以随意自定义 html 代码——因为 HTML 格式过于灵活,编辑器不会全部兼容。这是使用 setHtml 时最重要的边界认知:回显内容应当来自编辑器自身输出,或遵循相同的元素结构规范(对应各模块的 parse-elem-html 解析规则,如 packages/basic-modules/src/modules/header/parse-elem-html.ts 一类实现)。

这一对能力是"服务端保存 HTML、再次进入页面回显"的标准方案,也是大多数富文本业务的核心闭环。

五、仿腾讯文档:外层滚动与工具栏裁剪

like-qq-doc.html 演示的是文档型产品常见布局:固定顶部工具栏 + 居中的白纸卡片 + 页面级滚动。它的关键配置有两点:

const editorConfig = {
  placeholder: 'Type here...',
  scroll: false, // 禁止编辑器滚动,由外层页面统一滚动
  MENU_CONF: { uploadImage: { ... } },
  onChange(editor) { console.log(editor.getHtml()) }
}

const editor = E.createEditor({
  selector: '#editor-text-area',
  content: [],
  config: editorConfig
})

const toolbar = E.createToolbar({
  editor,
  selector: '#editor-toolbar',
  config: {
    excludeKeys: 'fullScreen', // 排除全屏按钮
  }
})

scroll: false 关闭编辑器内部滚动,让外层容器(#content 的 overflow-y: auto)接管滚动行为;excludeKeys: 'fullScreen' 从工具栏中剔除全屏菜单,避免与文档页布局冲突。页面还演示了"点击空白处聚焦编辑器"的交互:先 editor.blur() 再 editor.focus(true)(focus 到末尾)。scroll 与工具栏 excludeKeys 的接口定义可分别在 IEditorConfig 与 IToolbarConfig 中查到。

六、标题目录(catalog):getElemsByTypePrefix 与 scrollToElem

catalog.html 实现了一个类似在线文档的"左侧标题导航":编辑时实时提取所有标题元素生成目录,点击目录项滚动到对应标题。核心逻辑在 onChange 中:

onChange(editor) {
  const headers = editor.getElemsByTypePrefix('header')
  headerContainer.innerHTML = headers.map(header => {
    const text = E.SlateNode.string(header)
    const { id, type } = header
    return `<li id="${id}" type="${type}">${text}</li>`
  }).join('')
}

点击目录项时,通过 editor.scrollToElem(id) 滚动到目标标题(见页面 mousedown 事件处理)。这里有两个值得学习的点:一是 getElemsByTypePrefix('header') 可以按类型前缀一次性取回 header1 ~ header6 的所有标题节点,再通过 E.SlateNode.string(header) 提取纯文本(E.SlateNode 即 Slate 的 Node 工具暴露);二是每个块级元素都携带稳定的 id,这正是 scrollToElem 能够定位元素的依据。该示例为"自动生成文档大纲 / 锚点导航"类需求提供了直接可复用的模板。

七、最大长度限制与 10w 字大文档

max-length.html 演示字数上限控制:

const MAX_LENGTH = 30

window.editor = E.createEditor({
  selector: '#editor-text-area',
  html: `<p>MaxLength: ${MAX_LENGTH}</p><p><br></p>`,
  config: {
    placeholder: 'Type here...',
    maxLength: MAX_LENGTH,
    onMaxLength(editor) {
      alert('Trigger maxlength callback')
    },
    ...
  }
})

配置项 maxLength(数值)定义最大字符数,配合 onMaxLength 回调在超出限制时触发提示。maxLength 与 onMaxLength 均在 IEditorConfig 中声明,其拦截逻辑由 core 包的 with-max-length.ts 以 Slate 插件(withXxx)的形式在编辑器创建时注入,覆盖插入、删除等各类内容变更操作。

huge-doc.html 则用于验证大文档性能:它通过 js/huge-content.js 在运行时用 deepClone 循环 370 次拼接出包含标题与长段落的 window.HUGE_CONTENT,再以 content 对象数组(而非 html 字符串)传入 E.createEditor({ content: window.HUGE_CONTENT, ... })。同时页面在 onChange 中用 editor.getText() 实时统计去换行后的字符总数,方便直观感受大文档下的编辑与渲染流畅度。注意这里与 html 传参二选一:createEditor 支持 content(Slate 节点数组)或 html(HTML 字符串)两种初始化内容方式,接口定义见 packages/editor/src/create.ts。

八、代码高亮:编辑器输出 + Prism 渲染

code-highlight.html 演示代码块内容与 Prism 语法高亮的配合。页面通过 CDN 引入 prismjs 的主题 CSS 与 JS(prism.css、prism.js、prism-core.js、autoloader 插件),展示了两部分内容:

  • 静态示例:直接手写 pre > code.language-javascript / code.language-html 结构展示高亮效果;
  • 动态示例:编辑器输出的代码块 HTML(同样遵循 pre > code 结构,如 <pre><code class="language-javascript">function fn(a) {...}</code></pre>)先写入容器,再调用 Prism.highlightAll() 完成渲染:
document.getElementById('btn-render').addEventListener('click', () => {
  document.getElementById('content-container').innerHTML = html
  Prism.highlightAll()
})

仓库中的 packages/code-highlight 模块正是编辑器内代码块语法高亮的完整实现(包含 SelectLangMenu 语言选择菜单与 decorate 装饰逻辑),而本 demo 说明了编辑器之外的渲染侧如何配合:编辑器只管输出带 language-* class 的 pre > code,展示层交给 Prism 等第三方库负责着色。这是"编辑态高亮"与"展示态高亮"解耦的典型做法。

九、扩展菜单:button / select / dropPanel / modal 四类

demo 目录最富实战价值的一组页面是四个扩展菜单示例,它们演示了通过 E.Boot.registerMenu 注册自定义菜单的完整姿势,对应工具栏的四种菜单形态。

9.1 简单按钮菜单(extend-menu.html)

extend-menu.html 演示最基础的 button 型菜单:

class MyMenu {
  constructor() {
    this.title = 'My menu'
    // this.iconSvg = '<svg >...</svg>'
    this.tag = 'button'
  }
  getValue(editor) { return ' hello ' }
  isActive(editor) { return false } // or true
  isDisabled(editor) { return false } // or true
  exec(editor, value) {
    editor.insertText(value) // value 即 this.getValue(editor) 的返回值
  }
}
const myMenuConf = {
  key: 'myMenu',
  factory() { return new MyMenu() }
}
E.Boot.registerMenu(myMenuConf)

注册后通过工具栏配置把菜单插入到指定位置:

window.toolbar = E.createToolbar({
  editor,
  selector: '#editor-toolbar',
  config: {
    insertKeys: {
      index: 0,
      keys: ['myMenu'], // show menu in toolbar
    }
  }
})

菜单类需要实现的核心钩子一目了然:title(标题/提示)、tag(渲染为 button)、getValue(取值,会作为参数传给 exec)、isActive(激活态高亮)、isDisabled(禁用态)、exec(点击执行,入参即 getValue 返回值)。insertKeys 配置的接口定义见 IToolbarConfig。

9.2 select 下拉菜单(extend-menu-select.html)

extend-menu-select.html 将 tag 改为 'select' 并新增 getOptions 与 width:

class MyMenuClass {
  constructor() {
    this.title = 'My Select Menu'
    this.tag = 'select'
    this.width = 60
  }
  getOptions(editor) {
    return [
      { value: 'beijing', text: '北京', styleForRenderMenuList: { 'font-size': '32px', 'font-weight': 'bold' } },
      { value: 'shanghai', text: '上海', selected: true },
      { value: 'shenzhen', text: '深圳' }
    ]
  }
  getValue(editor) { return 'shanghai' } // 匹配 options 其中一个 value
  ...
  exec(editor, value) {
    editor.insertText(value)
    editor.insertText(' ')
  }
}

getOptions 返回选项数组,每项支持 value、text、selected(默认选中)以及 styleForRenderMenuList(控制下拉列表中的渲染样式,例如加大字号加粗);getValue 的返回值需要匹配其中一个 value;选中后 exec 收到的 value 即所选选项值。需要"字号、字体、行高"这类选择型菜单时可直接套用此模板(仓库内置的 FontSizeMenu 等即为此形态的官方实现)。

9.3 dropPanel 下拉面板(extend-menu-drop-panel.html)

extend-menu-drop-panel.html 通过 this.showDropPanel = true 开启下拉面板,核心方法是 getPanelContentElem(editor),返回一个 DOM 元素作为面板内容:

getPanelContentElem(editor) {
  const $list = $(`<ul class="w-e-panel-my-list">
      <li>北京</li><li>上海</li><li>深圳</li>
      <li>广州</li><li>天津</li><li>成都</li>
      <li>南京</li><li>郑州</li>
    </ul>`)

  $list.on('click', 'li', function () {
    editor.insertText(this.innerHTML)
    editor.insertText(' ')
  })

  return $list[0]
  // PS:也可以把 $list 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
}

面板内容可以是任意自定义 DOM,示例以 jQuery 构建城市列表,点击后向编辑器插入文字。源码注释还给出了性能优化提示:可以把 $list 缓存起来,避免每次打开面板重复创建与绑定事件。内置模块中"颜色选择"(ColorMenu)等菜单即采用这一形态。

9.4 modal 弹窗菜单(extend-menu-modal.html)

extend-menu-modal.html 通过 this.showModal = true 与 this.modalWidth = 300 开启弹窗,核心方法是 getModalPositionNode(editor)(返回 null 时 modal 依据选区定位)与 getModalContentElem(editor)(返回弹窗内容 DOM):

getModalContentElem(editor) {
  const $container = $('<div></div>')
  const inputId = `input-${Math.random().toString(16).slice(-8)}`
  const buttonId = `button-${Math.random().toString(16).slice(-8)}`
  const $inputContainer = $(`<label class="babel-container"><span>Text</span>
      <input type="text" id="${inputId}" value="hello world">
    </label>`)
  const $buttonContainer = $(`<div class="button-container">
      <button id="${buttonId}">insert text</button>
    </div>`)
  $container.append($inputContainer).append($buttonContainer)

  $container.on('click', `#${buttonId}`, e => {
    e.preventDefault()
    const text = $(`#${inputId}`).val()
    if (!text) return
    editor.restoreSelection() // 恢复选区
    editor.insertText(text)
    editor.insertText(' ')
  })

  setTimeout(() => { $(`#${inputId}`).focus() })

  return $container[0]
  // PS:也可以把 $container 缓存下来,这样不用每次重复创建、重复绑定事件,优化性能
}

弹窗内点击按钮时先 editor.restoreSelection() 恢复打开弹窗前的选区,再执行插入,这是 modal 型菜单的标准时序。内置的"插入链接/插入图片"等菜单(如 packages/basic-modules/src/modules/link/menu/InsertLinkMenu.ts)均基于此形态实现。

四个示例共同演示了 E.Boot.registerMenu 注册机制:给菜单配置一个唯一 key 与 factory() 工厂函数,注册后即可在 createToolbar 的 insertKeys 中按 key 引用。这套扩展能力使 wangEditor 可以针对业务场景自由增删工具栏菜单。

十、多编辑器实例与独立配置

multi-editor.html 演示同一页面创建两个独立编辑器:editor1 使用默认模式,editor2 使用 mode: 'simple' 简洁模式,两者共享同一套页面骨架但拥有各自独立的 selector、html、onChange 回调与上传配置(fieldName 分别命名为 your-file-name1 / your-file-name2):

const editor1 = E.createEditor({
  selector: '#editor-text-area-1',
  config: { placeholder: 'Type here...', autoFocus: false, MENU_CONF: {...}, onChange(editor) {...} },
  html: '<p>editor1</p><p><br></p>'
})
const toolbar1 = E.createToolbar({ editor: editor1, selector: '#editor-toolbar-1', config: {} })

const editor2 = E.createEditor({
  selector: '#editor-text-area-2',
  config: {...},
  html: '<p>editor2</p><p><br></p>',
  mode: 'simple'
})

两个值得注意的细节:一是 autoFocus: false 避免多个编辑器同时抢占焦点;二是每个编辑器的 onChange 独立驱动各自的预览区(content-view-1 / content-view-2)。页面布局上用 flex 双栏排列两个"工具栏 + 编辑区 + 预览区"组合,适合后台中"并排对比编辑"或"同一页面多个富文本输入"的场景。

十一、左侧导航与公共布局的实现

所有示例页面都引入了 js/custom-elem.js,它用原生 Web Components 定义了 demo-nav(顶部导航条)和 demo-menu(左侧菜单)两个自定义元素,页面里直接写 <demo-nav title="..."></demo-nav> 与 <demo-menu></demo-menu> 即可使用。其中 demo-menu 内部维护了一个 MENU_CONF 常量数组——注意这是导航菜单配置(每个条目含 zh-CN/en 双语文案与 link 链接),与编辑器配置中的 MENU_CONF 是两回事,README 所说的"修改左侧目录,在 demo 目录搜索 MENU_CONF"正是让你在这个数组里增删导航条目。

左侧菜单覆盖了本文介绍的全部示例,并额外收录了 Shadow DOM 示例(packages/editor/examples/shadow-dom.html)、Vue2 / Vue3 / React / Webpack 集成 demo 等外部链接入口。文件顶部还内嵌了一段来自 Polymer 项目的 native-shim(Custom Elements v1 的 ES5 兼容垫片),保证自定义元素在未原生支持的环境中也能工作。

左侧导航样式由 css/layout.css 提供:.page-container 使用 flex 布局,.page-left 固定 150px 宽,.page-right 用 flex: 1 撑满剩余宽度。想增删左侧目录项,只需编辑 custom-elem.js 中的导航数组;想调整页面骨架,则修改 layout.css 与各页面结构即可。

十二、demo 部署:deploy-demos.yml 流水线

README 指出"demo 部署参考 deploy-demos.yml 配置",该文件位于仓库根目录的 .github/workflows/deploy-demos.yml。这是一个 GitHub Actions 工作流,触发条件是推送 deploy-demos 分支(on: push: branches: [ deploy-demos ]),其部署流程分四步:

  1. 设置 SSH key:把仓库 Secrets 中的 SSH_KEY_FOR_GITHUB_WANGEDITOR 写入 ~/.ssh/id_rsa 并添加 github.com 到 known_hosts,为后续克隆与推送做准备;
  2. 下载现有文件并替换:git clone git@github.com:wangEditor/demo.git 拉取线上 demo 仓库,再用 cp -r ./packages/editor/demo/* ./demo 用最新构建产物覆盖旧文件;
  3. 上传文件:进入 demo 目录,配置 git 用户(github-actions)后 git add .、git commit、git push origin main,把更新推送到 demo 仓库的 main 分支;
  4. 清理 SSH key:任务结束后删除 ~/.ssh/id_rsa,避免凭据残留。

这条流水线本质上是一个"静态页同步"方案:demo 页面不依赖构建命令(页面直接通过 CDN 引用 @wangeditor/editor@latest 产物),因此工作流只做文件复制与推送,没有 npm 构建步骤。若你 fork 本仓库并维护自己的 demo 站点,可在 fork 的 Settings → Secrets 中配置同名 SSH key,并推送 deploy-demos 分支触发同样流程;本地直接预览则无需部署,用任意静态服务器指向 packages/editor/demo 目录即可。

结语

packages/editor/demo 用一组零构建、可直开的 HTML 页面,把 wangEditor 的高频使用场景(默认/简洁模式、HTML 读写、字数统计、目录导航、长度限制、大文档、多实例、代码高亮)与高级扩展能力(四类自定义菜单)完整串联起来。配合 README 指明的 MENU_CONF 线索与 deploy-demos.yml 部署方案,开发者既可以把它当作速查手册逐页对照学习,也可以直接复制页面骨架到自己的项目中二次开发,甚至基于 Actions 流水线搭建自己的在线 demo 站点。建议在阅读本指南时,对照上述相对路径逐一打开源码文件,将示例行为与底层实现一一印证,效果最佳。

登录后查看全文
wangEditor