简介:这份资源是一套功能全面、多端支持的汉字拼音笔画 JavaScript 工具包,对应开源库 cnchar,面向 Web 应用开发者,尤其适合中文教育、文化展示和语言学习类项目。核心能力覆盖汉字拼音(全拼、简拼、声调标注)、笔画数与笔画顺序、偏旁部首、成语拼音解释,并提供语音合成与可视化绘制,方便在浏览器和 Node.js 环境下无缝调用。压缩包共 304 个文件,大小 1.51MB,主要包含 TypeScript 源码(126 个 ts)、JavaScript 示例与构建文件(49 个 js)、配置与元数据(53 个 json)、Markdown 文档(28 个 md),另有 HTML 演示页面、Vue 组件及少量样式和字体资源,目录结构清晰,便于开发者阅读源码、查看示例或直接集成到工程中。包内还附有完整的 API 说明和扩展插件机制,适合需要深度定制中文处理功能的开发者参考。目前已有 216 人浏览学习,作为轻量级工具包,能减少从零实现拼音、笔画等功能的成本,是一份实用且具备教学价值的中文前端开发资源。
1. 为什么把拼音、笔画、成语交给 cnchar 而不是自己造轮子
在 Web 应用里做中文搜索、拼音标注或者生字卡片时,很多团队的第一反应是拿 Unicode 码表自己拼:从\u4e00到\u9fa5判断汉字范围,再手抄一份拼音对照表。这种办法在小页面上够用,一旦要处理多音字、输出带声调的全拼、统计繁体笔画,数组和正则就会把业务代码撑得很难看。cnchar 是一个把汉字拼音、笔画、偏旁、成语、语音合成和笔画动画都收纳进来的 JavaScript 工具包,浏览器和 Node.js 都能跑。对做 web 应用开发的工程师来说,它的价值不是省掉一张码表,而是把中文数据拆成可组合的 API,让拼音检索、识字教学、成语解释这类功能直接进业务代码,而不是散落在十几个工具函数里。
2. 拼音 API:从全拼到多音字,参数要这样用
2.1 先用最少代码跑通全拼和简拼
cnchar 的拼音能力可以只靠三个方法覆盖大部分场景:pinyin()是统一入口,pinyinFull()输出带声调全拼,pinyinSimple()输出去掉声调后的拼音。先看最小可运行示例:
import cnchar from 'cnchar'; import 'cnchar-pinyin'; const full = cnchar.pinyinFull('中文'); console.log(full); // ['zhōng', 'wén'] const simple = cnchar.pinyinSimple('中文'); console.log(simple); // ['zhong', 'wen']这段代码里,cnchar.pinyinFull()返回的是数组,每个元素对应输入字符串中的一个汉字,声调以ā á ǎ à这种形式直接附着在韵母上。cnchar.pinyinSimple()则把声调符号剥掉,适合做拼音搜索索引。要注意返回值始终是数组,不是字符串,所以直接拼接时记得调用.join('');如果需要逐字渲染注音卡片,就按数组下标与文字逐一对位。
这三个方法的使用差异不只在声调,还在你后续打算怎么消费数据。我通常把它们的调用约定固定成下面这张表,避免团队成员在代码里各写各的风格:
| 函数 | 返回内容 | 典型用途 |
|---|---|---|
cnchar.pinyin(text) | 按配置输出拼音数组 | 统一入口,后续按参数切换格式 |
cnchar.pinyinFull(text) | 带声调全拼 | 注音卡片、TTS 前置处理 |
cnchar.pinyinSimple(text) | 去声调拼音 | 搜索索引、URL 别名、内部匹配 |
cnchar.pinyin()本身支持通过参数改变输出形态,常见做法是在函数第二个参数里传一个对象,控制是否返回数组、是否保留声调、是否只取声母。比如一段文本既要在页面上展示完整拼音,又要生成搜索用的简写,可以用同一个入口切换两次,而不是维护两套拼音表。
注意:不要用字符串拼接代替数组操作。中文分词后每个字的位置很关键,一旦把拼音和原文字数对不上,后面做搜索结果高亮会很痛苦。
2.2 多音字:让词组先于单字进入 API
拼音库最容易翻车的地方就是多音字。“重庆”的“重”读chóng,“重量”的“重”读zhòng,如果只把单字丢进 API,结果大概率不是你想要的那个。cnchar 的处理方式依赖词表,把多音字放到常见词里识别,命中率会高很多。一个实际例子:
const text = '重庆的解放碑'; const fullList = cnchar.pinyinFull(text); console.log(fullList); // ['chóng', 'qìng', 'de', 'jiě', 'fàng', 'bēi']输入是整句而不是单字,cnchar会按内部词典切分词边界,“重庆”作为一个整体被识别,所以“重”拿到了chóng这个读音。如果业务里有明确的地名词典,比如“重庆”必须读chóng,我建议在进 cnchar 之前先把词组边界切好,或者在得到结果后用业务词典覆盖,而不是依赖库去猜。
部分版本还暴露了类似poly的扩展参数,用来返回一个多音字的全部读音。这个功能适合做离线注音工具,不适合直接进线上搜索流程,因为把多个读音都返回后,你仍然需要自己选一个作为主读音。生产环境更可控的做法是:优先传完整词语,让库的主词典决定读音;遇到低频地名和人名时,单独维护一个小字典,在 cnchar 结果上做二次修正。
2.3 拼音索引清洗和搜索场景的落地
处理完读音之后,真正进搜索库的常常是一串没有声调、没有特殊符号的纯字母。拼音里还有ü这类字符,在 URL 和数据库索引里容易出问题。下面这个函数是我在项目里常用的清洗方式:
function toSearchIndex(text) { return cnchar .pinyinSimple(text) .join('') .replace(/ü/g, 'v') .replace(/[^a-z]/g, '') .toLowerCase(); } console.log(toSearchIndex('长沙')); // changsha这里先调用pinyinSimple()拿到去声调拼音数组,再join('')变成连续字符串。replace(/ü/g, 'v')是为了解决“绿、吕”这类拼音在输入法里习惯写作lv的问题。随后把所有非a-z字符过滤掉,最后统一转小写。这样清洗出来的索引适合放到内存 Map、Redis 或者 Elasticsearch 里做前缀匹配。
如果你的搜索要支持中文和拼音混输,比如用户输入changsha也能搜到“长沙”,那就在建索引时同时保存中文原文字段和拼音索引字段。查询时用输入关键字分别匹配这两个字段,同时把用户输入的字母全部转小写,不需要在查询阶段再做繁体转换,因为拼音索引已经帮你绕开了繁简体差异。
3. 笔画、偏旁与成语:cnchar 的数据形态和调用范式
3.1 笔画数查询和繁简入口
笔画数据是汉字库里的另一层信息,cnchar 把它和拼音放在同一套 API 体系里。直接传入文字,就能拿到笔画数。多字输入返回数组,单字输入通常直接返回数字:
import cnchar from 'cnchar'; import 'cnchar-trad'; const multiCounts = cnchar.stroke('中文'); console.log(multiCounts); // [4, 4] const singleCount = cnchar.stroke('中'); console.log(singleCount); // 4代码里multiCounts是一个数组,顺序与输入字符一一对应。“中”是 4 画,“文”也是 4 画,所以得到[4, 4]。这里要注意的是:不要默认所有版本都返回数字,某些封装可能会把单字结果也包成数组。稳妥做法是先在控制台打印一次返回值,再决定后续是用reduce求和还是直接展示。
cnchar 还通过trad()和simp()分别提供繁体字与简体字的笔画数据入口。比如繁体页面里查询“龍”的笔画,可以直接调用cnchar.trad('龍');简体页面用cnchar.simp('龙')。这听起来只是换了一个查询函数,实际价值是繁简两套数据不会互相污染,页面切换语言时,不需要把文本先转码再查笔画。
| 调用 | 预期返回 | 使用场景 |
|---|---|---|
cnchar.stroke('中') | 4 | 生字卡显示总笔画 |
cnchar.stroke('中文') | [4, 4] | 批量文字排版 |
cnchar.trad('龍') | 繁体笔画数据 | 繁体内容处理 |
cnchar.simp('龙') | 简体笔画数据 | 简体内容处理 |
这套接口的边界要分清楚:stroke()负责查当前传入文字的笔画,trad()与simp()负责指定数据方向。不要在调用stroke()时同时传繁简标识,那样会让返回值变得不可预测,排错时也很难判断是字形问题还是插件缺失。
3.2 part 部首查询:从“字”到“部件”的拆解
cnchar.part()返回汉字的偏旁部首信息,这是识字类 Web 应用很依赖的能力。比如用户在查“想”字时,我们希望展示“心字底”,同时告诉用户这个字由“相”和“心”组成。代码层面只需要一次调用:
const partInfo = cnchar.part('想'); console.log(partInfo);输出结果里通常包含部首本身、剩余部件以及它们的位置关系。不同版本返回的结构可能不完全一样,所以接入时别急着写死字段,先把返回对象打印出来,确认part、rest这些字段存在后再做渲染。
在实际项目里,我会用它做两个方向的扩展:一是把部首作为汉字卡片的分组索引,例如将所有心字底的汉字归到一组;二是把返回的部件信息转成 SVG 结构,方便前端拼字。后者对数据结构要求更高,建议在 cnchar 返回结果之上再加一层自己的映射,不要直接依赖库内部字段名。
3.3 idiom 成语数据:结构化的解释比字符串更值钱
成语接口让 cnchar 从单字处理工具变成内容数据源。cnchar.idiom()可以直接拿到成语的拼音和解释,配合插件机制注册后,在 Node 和浏览器里都能使用:
import 'cnchar-idiom'; const idiom = cnchar.idiom('一石二鸟'); console.log(idiom.pinyin); console.log(idiom.explanation);这里pinyin是成语整句的拼音,explanation是对应的中文解释。两者都是结构化字段,适合直接展示在学习页面上。如果你要做成语填空或成语接龙,也可以把成语先拆成单个汉字,再分别调用pinyinFull()和stroke(),实现多维度索引。
这个接口提醒我一点:成语数据尽量保持整词单元,不要在一开始就把成语拆成单字再拼回来。像“一石二鸟”这类成语里有很多多音字和变调,整词查询的准确性远高于逐字拼接。
4. 语音合成、笔画绘制与多端兼容:把 cnchar 接入 Web 应用的最后一步
4.1 在浏览器里接 TTS 和笔画动画
cnchar 的可视化和语音能力需要配合 DOM 使用。cnchar.tts()依赖浏览器底层的语音合成能力,cnchar.draw()则负责把汉字笔画渲染成可播放的动画。一个典型调用如下:
import cnchar from 'cnchar'; import 'cnchar-draw'; import 'cnchar-tts'; const box = document.querySelector('#strokeBox'); cnchar.draw('永', { el: box, animation: true }); cnchar.tts('你好');cnchar.draw()要传入一个已经挂载的 DOM 节点,动画会直接渲染在节点内部。移动端浏览器对自动播放有限制,cnchar.tts()最好放在用户点击按钮的事件回调里调用,否则可能没有声音输出。draw()也是一样,不要在数据请求还没回来时就执行,避免拿到空容器。
4.2 Node 侧只留数据能力
在 Node.js 环境里,拼音和笔画的功能可以照常使用,但draw和tts不应该出现在服务端代码里。服务端更适合用 cnchar 做批量数据清洗,比如给文章批量生成拼音索引。安装时按需引入:
npm install cnchar cnchar-pinyin然后在一个 Node 脚本里调用:
const cnchar = require('cnchar'); require('cnchar-pinyin'); const pinyin = cnchar.pinyinSimple('中文').join(''); console.log(pinyin); // zhongwen这里只加载了拼音插件,没有加载需要 DOM 的模块。好处是服务端打包体积更小,也不会因为缺少window对象而报错。如果服务端和浏览器端共用同一份拼音结果,建议把结果序列化后放入缓存,而不是每次请求都重新计算。
4.3 用断言锁住核心行为
升级 cnchar 或调整插件顺序后,最容易出现的问题是结果结构变化。简单写几个断言可以提前发现问题:
const assert = require('assert'); assert.ok(Array.isArray(cnchar.pinyinFull('中文'))); assert.ok(cnchar.stroke('中') > 0);第一行确认拼音返回数组,第二行确认笔画数存在。注意不要写成cnchar.pinyinFull('中文') === ['zhōng', 'wén'],因为数组比较需要先JSON.stringify或者用deepStrictEqual。一旦断言失败,先检查插件是否注册完整,再打印返回值确认数据结构。最后,接cnchar.draw()时给容器固定宽高并用overflow: hidden包一层,可以避免低版本 WebView 在笔画动画播放时把布局撑跳;动画结束后移除遮罩,比直接监听内部回调更稳。
本文还有配套的精品资源,点击获取