news 2026/9/17 5:17:51

CKEditor 5 Word Count 字数统计插件:安装、配置与源码级原理解读

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CKEditor 5 Word Count 字数统计插件:安装、配置与源码级原理解读

CKEditor 5 Word Count 字数统计插件:安装、配置与源码级原理解读

【免费下载链接】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 官方的 Word Count(字数与字符数统计)功能。该功能由packages/ckeditor5-word-count包实现,可实时统计编辑器中的单词数与字符数,适用于写作进度跟踪、内容长度校验、微博/评论类输入框的字符上限提示等场景。阅读本文后,你将掌握该插件的安装方式、全部配置项(containerdisplayWordsdisplayCharactersonUpdate)、公开 API(wordCountContainerupdate事件、words/characters属性),并能基于源码理解其统计口径与节流机制,写出可直接上线的字数统计与字符上限校验代码。

功能概览

Word Count 是 CKEditor 5 官方出品的一个独立插件包,用于在编辑过程中实时统计编辑器内容中的单词数量字符数量。它的典型价值在于:

  • 帮助写作者控制内容篇幅、跟踪写作进度;
  • 在表单场景中校验内容长度(例如社交平台发帖的 120 字符软上限);
  • 无需人工数数,即可随时拿到精确的统计数据用于业务逻辑(如草稿字数奖励、按字数计费等)。

该插件属于开源聚合包ckeditor5的一部分,同时被标记为官方插件(isOfficialPlugin)与高级插件(isPremiumPlugin),其许可校验特征码为WC(见 packages/ckeditor5-word-count/src/wordcount.ts 与 packages/ckeditor5-word-count/tests/wordcount.js 中的测试断言)。

快速上手:在页面中显示计数器

官方演示的页面结构非常简单,一个编辑器容器加上一个用于承接统计信息的div即可:

<div id="editor"> <p>Hello world.</p> </div> <div id="word-count"></div>

然后通过编辑器实例拿到WordCount插件,并将其自更新的统计容器wordCountContainer挂载到页面上:

ClassicEditor .create( { // Configuration details. } ) .then( editor => { const wordCountPlugin = editor.plugins.get( 'WordCount' ); const wordCountWrapper = document.getElementById( 'word-count' ); wordCountWrapper.appendChild( wordCountPlugin.wordCountContainer ); } );

wordCountContainer是一个**自更新(self-updating)**的 HTML 元素:只要编辑器内容发生变化,它内部显示的"Words"与"Characters"数值就会自动刷新,无需手动同步。你可以将这段逻辑与下方"安装"小节组合成一个可运行的页面。

安装

在基于 npm 的项目中,本功能随开源聚合包一起分发,直接安装即可:

npm install ckeditor5

安装完成后,将WordCount加入插件列表,并通过wordCount键进行配置:

import { ClassicEditor, WordCount } from 'ckeditor5'; ClassicEditor .create( { licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'. plugins: [ WordCount, /* ... */ ], wordCount: { // Configuration. } } ) .then( /* ... */ ) .catch( /* ... */ );

关于授权,WordCount是高级功能,生产环境需要有效的许可证;在本地开发与评估阶段可填写'GPL'使用 GPL 模式。类型层面,插件通过 packages/ckeditor5-word-count/src/augmentation.ts 对EditorConfig进行了模块扩展,因此wordCount配置项与editor.plugins.get( 'WordCount' )的返回值都具备完整的 TypeScript 类型提示。

配置详解

插件提供了四个配置项,全部以config.wordCount为前缀,其类型定义见 packages/ckeditor5-word-count/src/wordcountconfig.ts。

1. container:指定统计容器的挂载位置

将计数器注入页面有两种方式:

  • 编程式挂载:通过WordCount#wordCountContainer属性拿到元素后自行appendChild(见上文"快速上手");
  • 声明式配置:通过config.wordCount.container直接传入一个目标 DOM 元素,插件初始化时会自动把计数器容器追加进去:
ClassicEditor .create( { plugins: [ WordCount, /* ... */ ], wordCount: { container: document.getElementById( 'container-for-word-count' ) } } );

对应的实现逻辑位于 packages/ckeditor5-word-count/src/wordcount.ts:init()阶段如果检测到container是一个真实元素(isElement( this._config.container )),就调用appendChild( this.wordCountContainer )完成注入。测试用例 packages/ckeditor5-word-count/tests/wordcount.js 验证了传入container后目标元素会获得一个子节点,且该子节点正是wordCountContainer返回的元素。

无论采用哪种方式,插件渲染出的 DOM 结构固定如下:

<div class="ck ck-word-count"> <div class="ck-word-count__words">Words: %%</div> <div class="ck-word-count__characters">Characters: %%</div> </div>

如果希望完全自定义渲染方式,可以忽略容器,改由update事件(见下文)驱动自己的 UI。

2. displayWords / displayCharacters:控制显示哪部分统计

两个布尔配置项用于决定是否展示对应的统计行,未配置时默认同时显示

ClassicEditor .create( { plugins: [ WordCount, /* ... */ ], wordCount: { displayWords: false // 隐藏单词数 // displayCharacters: false // 隐藏字符数 } } );
  • displayWords: false时,wordCountContainer只保留字符部分:
<div class="ck ck-word-count"> <div class="ck-word-count__characters">Characters: 28</div> </div>
  • displayCharacters: false时,只保留单词部分:
<div class="ck ck-word-count"> <div class="ck-word-count__words">Words: 4</div> </div>

从源码看,packages/ckeditor5-word-count/src/wordcount.ts 在构建输出视图时逐项判断displayWordsdisplayCharactersundefined视为显示),并按需创建ck-word-count__words/ck-word-count__characters子节点;测试 packages/ckeditor5-word-count/tests/wordcount.js 分别断言了两种配置下的容器文本内容。

3. onUpdate:内容统计变化时执行回调

如果要在每次统计值变化时执行自定义逻辑(例如发送字数到后端、驱动进度条),可通过onUpdate注册回调:

ClassicEditor .create( { // ... Other configuration options ... wordCount: { onUpdate: stats => { // Prints the current content statistics. console.log( `Characters: ${ stats.characters }\nWords: ${ stats.words }` ); } } } ) .then( /* ... */ ) .catch( /* ... */ );

回调收到一个形如{ words, characters }的对象。其实现位置在 packages/ckeditor5-word-count/src/wordcount.ts:插件在init()中监听自身的update事件,并转发给onUpdate配置。

重要说明(性能相关):出于性能考虑,统计刷新与onUpdate回调是**节流(throttled)**的,因此回调拿到的数值可能不是"最新时刻"的值。如果你需要精确、即时的数字(例如提交前的严格校验),请直接读取插件的characterswords属性(见下文"Common API")。

实战案例:带 120 字符上限的"发帖编辑器"

官方提供的一个经典场景是:编辑器下方附带环形进度图,字符数接近上限时变橙色,超过上限时编辑器背景变红并禁用"发送"按钮。完整 HTML/CSS 结构如下:

<style> .demo-update { border: 1px solid var(--ck-color-base-border); border-radius: var(--ck-border-radius); box-shadow: 2px 2px 0px hsla( 0, 0%, 0%, 0.1 ); margin: 1.5em 0; padding: 1em; } .demo-update h3 { font-size: 18px; font-weight: bold; margin: 0 0 .5em; padding: 0; } .demo-update .ck.ck-editor__editable_inline { border: 1px solid hsla( 0, 0%, 0%, 0.15 ); transition: background .5s ease-out; min-height: 6em; margin-bottom: 1em; } .demo-update__controls { display: flex; flex-direction: row; align-items: center; } .demo-update__chart { margin-right: 1em; } .demo-update__chart__circle { transform: rotate(-90deg); transform-origin: center; } .demo-update__chart__characters { font-size: 13px; font-weight: bold; } .demo-update__words { flex-grow: 1; opacity: .5; } .demo-update__limit-close .demo-update__chart__circle { stroke: hsl( 30, 100%, 52% ); } .demo-update__limit-exceeded .ck.ck-editor__editable_inline { background: hsl( 0, 100%, 97% ); } .demo-update__limit-exceeded .demo-update__chart__circle { stroke: hsl( 0, 100%, 52% ); } .demo-update__limit-exceeded .demo-update__chart__characters { fill: hsl( 0, 100%, 52% ); } </style> <div class="demo-update"> <h3>Post editor with word count</h3> <div id="demo-update__editor"> <p>Tourists frequently admit that <a href="https://en.wikipedia.org/wiki/Taj_Mahal">Taj Mahal</a> “simply cannot be described with words”.</p> </div> <div class="demo-update__controls"> <span class="demo-update__words"></span> <svg class="demo-update__chart" viewbox="0 0 40 40" width="40" height="40" xmlns="http://www.w3.org/2000/svg"> <circle stroke="hsl(0, 0%, 93%)" stroke-width="3" fill="none" cx="20" cy="20" r="17" /> <circle class="demo-update__chart__circle" stroke="hsl(202, 92%, 59%)" stroke-width="3" stroke-dasharray="134,534" stroke-linecap="round" fill="none" cx="20" cy="20" r="17" /> <text class="demo-update__chart__characters" x="50%" y="50%" dominant-baseline="central" text-anchor="middle"></text> </svg> <button type="button" class="demo-update__send">Send post</button> </div> </div>

配合的编辑器初始化代码(以 BalloonEditor 为例)利用onUpdate回调完成全部 UI 联动:

const maxCharacters = 120; const container = document.querySelector( '.demo-update' ); const progressCircle = document.querySelector( '.demo-update__chart__circle' ); const charactersBox = document.querySelector( '.demo-update__chart__characters' ); const wordsBox = document.querySelector( '.demo-update__words' ); const circleCircumference = Math.floor( 2 * Math.PI * progressCircle.getAttribute( 'r' ) ); const sendButton = document.querySelector( '.demo-update__send' ); BalloonEditor .create( { root: { element: document.querySelector( '#demo-update__editor' ) }, // Editor configuration. wordCount: { onUpdate: stats => { const charactersProgress = stats.characters / maxCharacters * circleCircumference; const isLimitExceeded = stats.characters > maxCharacters; const isCloseToLimit = !isLimitExceeded && stats.characters > maxCharacters * .8; const circleDashArray = Math.min( charactersProgress, circleCircumference ); // Set the stroke of the circle to show how many characters were typed. progressCircle.setAttribute( 'stroke-dasharray', `${ circleDashArray },${ circleCircumference }` ); // Display the number of characters in the progress chart. When the limit is exceeded, // display how many characters should be removed. if ( isLimitExceeded ) { charactersBox.textContent = `-${ stats.characters - maxCharacters }`; } else { charactersBox.textContent = stats.characters; } wordsBox.textContent = `Words in the post: ${ stats.words }`; // If the content length is close to the character limit, add a CSS class to warn the user. container.classList.toggle( 'demo-update__limit-close', isCloseToLimit ); // If the character limit is exceeded, add a CSS class that makes the content's background red. container.classList.toggle( 'demo-update__limit-exceeded', isLimitExceeded ); // If the character limit is exceeded, disable the send button. sendButton.toggleAttribute( 'disabled', isLimitExceeded ); } } } );

这段代码演示了onUpdate最典型的用法:输入法联动 UI、渐进式警告、超限硬校验三合一。需要再次强调的是,onUpdate是节流的,这里展示的 UI 反馈适合"接近/超过上限"这类对实时性要求不苛刻的场景;若要在提交按钮点击时做最终校验,请读取editor.plugins.get( 'WordCount' ).characters获取精确值。

Common API:插件对外提供的三件套

WordCount插件对外暴露了三个核心能力(见 packages/ckeditor5-word-count/src/wordcount.ts 与官方文档):

  1. wordCountContainer属性:返回一个自更新 HTML 元素,内容随编辑器统计值自动刷新。可通过displayWords/displayCharacters配置隐藏其中任意一行。该容器只在首次访问时创建,重复调用返回同一个元素实例(测试 packages/ckeditor5-word-count/tests/wordcount.js 验证了这一行为)。编辑器销毁时,容器元素会被自动从 DOM 中移除(见destroy()实现 packages/ckeditor5-word-count/src/wordcount.ts)。

  2. update事件:每当插件更新统计值时触发,携带{ words, characters }参数,可用于注册自定义回调:

editor.plugins.get( 'WordCount' ).on( 'update', ( evt, stats ) => { // Prints the current content statistics. console.log( `Characters: ${ stats.characters }\nWords: ${ stats.words }` ); } );

它等价于config.wordCount.onUpdate的底层机制——事实上onUpdate正是通过监听该事件实现的(packages/ckeditor5-word-count/src/wordcount.ts)。事件同样受节流影响,统计值可能不是最新的。

  1. characterswords属性:可直接读取的精确统计数字,不受节流影响。源码中这两个属性被定义为 getter,每次访问都会基于当前模型内容即时重算(packages/ckeditor5-word-count/src/wordcount.ts),因此非常适合用于"保存前校验"等需要精确值的逻辑。测试 packages/ckeditor5-word-count/tests/wordcount.js 验证了设置模型数据后立即读取words即可拿到正确结果。此外二者都是可观察(observable)属性,可用change:words/change:characters监听变化。

源码深潜:统计口径与底层原理

理解统计口径有助于预判"为什么我的字数跟别的工具数出来不一样"。整个计数流程为:模型(Model)→ 纯文本 → 正则分词/计字符

第一步:模型转纯文本

packages/ckeditor5-word-count/src/utils.ts 中的modelElementToPlainText()递归遍历编辑器模型的所有子节点:

  • $text/$textProxy节点直接返回其文本数据(不含任何样式标记);
  • 对于块级元素(如段落、表格单元格、列表项、图片说明等),每遇到一个子元素就在文本前插入一个\n换行符作为分隔。

也就是说,每个块(段落/单元格/说明文字)之间会被换行符隔开,而内联样式(加粗、下划线、链接等)不会产生任何额外字符。例如Foo(加粗)+Bar两个段落会被转换为"Foo\nBar"。测试文件 packages/ckeditor5-word-count/tests/utils.js 用引用块、表格、软换行(<softBreak>)、混合结构等场景验证了这一转换逻辑。

插件在_getText()(packages/ckeditor5-word-count/src/wordcount.ts)中遍历文档的所有根节点(root),将各根节点的纯文本用\n连接——这也是多根编辑器(MultiRootEditor)统计的实现基础。

第二步:分词与计数字符

拿到纯文本后:

  • 字符数txt.replace( /\n/g, '' ).length—— 即去掉换行符后的字符长度,换行/回车不计入字符数(packages/ckeditor5-word-count/src/wordcount.ts)。
  • 单词数:通过正则匹配。在支持 Unicode 属性转义的环境中(现代浏览器),使用([\p{L}\p{N}]+\S?)+gu标志),其中\p{L}匹配任意语言的字母、\p{N}匹配任意文字系统中的数字;在不支持的环境下降级为([a-zA-Z0-9À-ž]+\S?)+(packages/ckeditor5-word-count/src/wordcount.ts)。

官方在类注释中给出了几个直观的例子(packages/ckeditor5-word-count/src/wordcount.ts):

<paragraph>foo</paragraph> <paragraph>bar</paragraph> // Words: 2, Characters: 7(两个段落,含 1 个换行;字符数不含换行) <paragraph><$text bold="true">foo</$text>bar</paragraph> // Words: 1, Characters: 6(内联样式不产生额外字符) <paragraph>*&^%)</paragraph> // Words: 0, Characters: 5(纯符号不算词) <paragraph>foo(bar)</paragraph> // Words: 1, Characters: 8 <paragraph>12345</paragraph> // Words: 1, Characters: 5(数字串算一个词)

第三步:节流刷新

插件在init()中监听模型文档的change:data事件(即内容数据变更,选区变化不触发),并用 250ms 的throttle包裹统计刷新逻辑(packages/ckeditor5-word-count/src/wordcount.ts)。这意味着连续输入时统计不会每击键都重算,而是以 250ms 为周期合并计算,从而保证大文档下的性能。测试 packages/ckeditor5-word-count/tests/wordcount.js 精确验证了:

  • 首次内容变更立即触发update
  • 随后 250ms 内的连续多次变更只触发一次update,且携带的是最终值;
  • 仅改变选区(不改内容)不会触发update

边界行为汇总(均有测试佐证)

以下行为全部来自 packages/ckeditor5-word-count/tests/wordcount.js:

输入场景结果
1 12 3,5 3/4 1.2 06 个词(数字均算词)
j.doe@cksource.com1 个词(邮箱算一个词)
Foo'barFoo.bar各 1 个词(撇号、点不拆分单词)
(@#$%^*()) . ??? @ --- ...0 个词(纯符号不算词)
列表项编号/项目符号不参与计数(仅计列表文字)
图片说明(caption)参与计数
表格全部单元格参与计数
段落结束、软换行(Shift+Enter)分隔两个词
希伯来/中文/日文/阿拉伯文等多语种每个词各计 1(Unicode 属性模式下)
编辑器销毁后容器元素自动从 DOM 移除

此外,插件对多根编辑器(MultiRootEditor)会累加所有根的字符与单词数(packages/ckeditor5-word-count/tests/wordcount.js);"Words: %0" 与 "Characters: %0" 标签支持翻译(测试中验证了波兰语翻译生效,packages/ckeditor5-word-count/tests/wordcount.js),语言由编辑器的language配置决定。

相关功能与延伸阅读

CKEditor 5 中与字数统计场景常搭配的功能还包括:拼写与语法检查(spelling-and-grammar-checking)、自动保存(autosave)、Markdown 风格的自动格式化(autoformat)与自动文本转换(text-transformation)。例如"写作倒计时 + 定时自动保存 + 字数超限提醒"的组合即可覆盖绝大多数内容创作工具的需求。

若想进一步阅读与验证,仓库内可直接参考的资源有:

  • 功能官方指南:packages/ckeditor5-word-count/docs/features/word-count.md
  • 插件源码:packages/ckeditor5-word-count/src/wordcount.ts
  • 配置类型定义:packages/ckeditor5-word-count/src/wordcountconfig.ts
  • 模型转纯文本工具:packages/ckeditor5-word-count/src/utils.ts
  • 单元测试:packages/ckeditor5-word-count/tests/wordcount.js、packages/ckeditor5-word-count/tests/utils.js
  • 官方演示片段:packages/ckeditor5-word-count/docs/_snippets/features/word-count.js

开发与调试阶段,建议配合官方 CKEditor 5 Inspector 使用,它可以直观展示编辑器的内部数据模型、选区与命令状态,帮助你理解统计值与所见内容之间的对应关系。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 5:17:45

FreeMoCap 安装配置完整指南:从零跑通开源无标记动作捕捉系统

FreeMoCap 安装配置完整指南&#xff1a;从零跑通开源无标记动作捕捉系统 【免费下载链接】freemocap Free Motion Capture for Everyone &#x1f480;✨ 项目地址: https://gitcode.com/GitHub_Trending/fr/freemocap FreeMoCap 是一个免费开源、不挑硬件的无标记动作…

作者头像 李华
网站建设 2026/9/17 5:16:55

Python循环结构实战:从基础到优化

1. Python循环结构基础回顾在正式进入练习题讲解之前&#xff0c;我们先快速回顾一下Python中循环结构的核心知识点。循环结构是编程中最重要的控制结构之一&#xff0c;它允许我们重复执行某段代码块&#xff0c;直到满足特定条件为止。Python主要提供了两种循环结构&#xff…

作者头像 李华
网站建设 2026/9/17 5:15:59

OSPF IP FRR技术:50ms内实现网络快速切换

1. OSPF IP FRR技术解析在网络工程师的日常运维中&#xff0c;链路故障恢复速度直接关系到业务连续性。传统OSPF的收敛时间通常在秒级&#xff0c;这对于现代数据中心和金融交易等场景是完全不可接受的。IP FRR&#xff08;Fast ReRoute&#xff09;技术正是在这种背景下诞生的…

作者头像 李华
网站建设 2026/9/17 5:11:24

PyPDF安装:3条路线,5分钟装好 pypdf

PyPDF安装&#xff1a;3条路线&#xff0c;5分钟装好 pypdf 【免费下载链接】pypdf A pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files 项目地址: https://gitcode.com/GitHub_Trending/py/pypdf pypdf 是一…

作者头像 李华