CKEditor 5 媒体嵌入样式(Media Embed Styles):从内置对齐到自定义样式的完整配置指南
【免费下载链接】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 的 media embed styles 功能展开:讲解如何通过MediaEmbedStyle插件为 YouTube、Vimeo、Spotify 等媒体嵌入应用对齐与自定义样式,覆盖内置 5 种对齐样式、config.mediaEmbed.styles与config.mediaEmbed.toolbar的完整配置项、mediaStyle命令的编程用法,并结合开源仓库源码揭示「默认样式 = 模型属性缺省」这一核心设计及其在 downcast/upcast 转换中的实现细节。读完后你可以直接在项目中启用该功能、裁剪或重定义样式集合、注册纯语义化的自定义样式,并理解样式类名是如何从编辑器内部模型写入最终 HTML 的。
功能概述与插件架构
媒体嵌入样式功能允许你对 media embed 应用一种「样式」(比如对齐方式)。它由MediaEmbedStyle插件实现,并且默认不会加载,需要显式添加到插件列表。
从源码结构看,MediaEmbedStyle本身是一个「胶水」插件,它只声明依赖两个子插件(见 mediaembedstyle.ts):
MediaEmbedStyleEditing:负责引擎层工作——扩展模型 schema、注册mediaStyle命令、注册样式类名的 downcast/upcast 转换器(见 mediaembedstyleediting.ts);MediaEmbedStyleUI:负责界面层工作——为每个样式注册按钮、构建内置与自定义的 split-button 下拉分组(见 mediaembedstyleui.ts)。
两者的衔接点在于MediaEmbedStyleEditing在初始化时解析一次配置,得到normalizedStyles(解析后的样式选项列表),命令与 UI 都消费这同一份数据,保证「工具栏上有哪些按钮」与「命令接受哪些值」严格一致。
安装
MediaEmbedStyle插件不默认加载,需要与MediaEmbed一起显式添加:
import { ClassicEditor, MediaEmbed, MediaEmbedToolbar, MediaEmbedStyle } from 'ckeditor5'; ClassicEditor .create( { attachTo: document.querySelector( '#editor' ), licenseKey: '<YOUR_LICENSE_KEY>', // Or 'GPL'. plugins: [ MediaEmbed, MediaEmbedToolbar, MediaEmbedStyle, /* ... */ ], toolbar: [ 'mediaEmbed', /* ... */ ] } ) .then( /* ... */ ) .catch( /* ... */ );需要注意的一个易错点:MediaEmbed同样不会默认加载MediaEmbedToolbar。媒体特性(包括样式按钮)的按钮都注册在媒体部件的上下文工具栏上,只有添加了MediaEmbedToolbar,你在config.mediaEmbed.toolbar中写的条目才能真正出现在媒体 widget 的工具栏里。
内置的 5 种样式
插件开箱提供 5 种对齐样式,定义在 constants.ts 的DEFAULT_OPTIONS中。每种样式都会在组件工厂中注册一个名为mediaEmbed:<style-name>的按钮(用于放入config.mediaEmbed.toolbar),并且对非默认样式,会在媒体<figure>元素上写入对应的 CSS 类;默认的alignCenter不写任何类。
块级对齐(Break text)——媒体独占一行,上下出现文字:
| 样式名称 | 标题 | 工具栏按钮 | 写入的 CSS 类 |
|---|---|---|---|
alignBlockLeft | Left aligned media | mediaEmbed:alignBlockLeft | media-style-block-align-left |
alignCenter | Centered media | mediaEmbed:alignCenter | 默认,无类 |
alignBlockRight | Right aligned media | mediaEmbed:alignBlockRight | media-style-block-align-right |
环绕对齐(Wrap text)——媒体浮动到一侧,文字环绕它:
| 样式名称 | 标题 | 工具栏按钮 | 写入的 CSS 类 |
|---|---|---|---|
alignLeft | Left aligned media | mediaEmbed:alignLeft | media-style-align-left |
alignRight | Right aligned media | mediaEmbed:alignRight | media-style-align-right |
其中alignCenter在源码中被标记为isDefault: true。这引出了该功能一个重要的设计:
默认样式在模型上编码为
mediaStyle属性的「缺失」。因此默认样式不需要className,应用默认样式等价于清除mediaStyle属性;downcast 时也就不会向 view 写入任何类。
另外要特别强调一点:真正的视觉样式由集成方负责。编辑器自带的一些默认样式只作用于编辑器内的媒体;你在目标页面上需要自行编写相应 CSS。编辑器内默认样式的源码可以在 theme/index-content.css 中找到,其中环绕类样式的核心规则大致是:
/* 环绕:浮动到一侧,文字环绕 */ .ck-content .media.media-style-align-left { float: left; margin-right: var(--ck-content-media-style-spacing); } .ck-content .media.media-style-align-right { float: right; margin-left: var(--ck-content-media-style-spacing); } /* 块级:靠 margin auto 在行内偏移(对全宽 figure 无效,宽度受限时才可见) */ .ck-content .media.media-style-block-align-left { margin-left: 0; margin-right: auto; } .ck-content .media.media-style-block-align-right { margin-left: auto; margin-right: 0; }--ck-content-media-style-spacing(默认1.5em)控制浮动媒体与文字之间的侧边距,集成方可以通过覆盖该 CSS 变量调整。
配置样式集合:config.mediaEmbed.styles
样式集合通过config.mediaEmbed.styles自定义。配置接受一个options数组,每个条目可以是三种形态之一:
- 字符串:按名称引用内置样式(
'alignLeft'、'alignBlockLeft'、'alignCenter'、'alignBlockRight'、'alignRight'); - 对象且
name命中内置样式:其字段会浅合并(shallow-merge)在内置默认值之上——设置的字段替换默认值,省略的字段继承默认值; - 对象且为全新
name:即完全自定义的样式(必填/可选字段见MediaStyleOptionDefinition类型定义,位于 mediaembedconfig.ts)。
当不提供config.mediaEmbed.styles时,全部 5 种内置样式可用。这一点在源码中可以直接印证——MediaEmbedStyleEditing.init()里定义了配置的默认值(见 mediaembedstyleediting.ts):
editor.config.define( 'mediaEmbed.styles', { options: Object.keys( DEFAULT_OPTIONS ) } );配置解析由 utils.ts 中的normalizeStyles()完成。解析规则值得注意:
- 字符串条目先被提升为
{ name }对象,再与匹配的内置默认值做浅合并;不匹配任何内置名称的条目原样通过,若缺少必填字段则被isValidOption()丢弃; icon字段除了完整的 SVG XML 字符串外,还支持 5 个短别名:'inlineLeft'、'left'、'center'、'right'、'inlineRight',别名映射来自 constants.ts 的DEFAULT_ICONS;- 必填校验(
isValidOption,见 utils.ts):name、title、icon永远必填;className在必选,除非该条目是isDefault: true(默认样式编码为属性缺省,天然没有类名)。
失效条目的行为:当某个配置条目缺少必填字段(非默认样式缺className也算),或引用了不存在的内置名称时,该条目会从解析结果中被剔除,并在控制台以media-style-configuration-definition-invalid错误码发出警告;其余合法条目继续按配置生效。
挑选内置样式子集
只传你想暴露的样式名。被过滤掉的样式会从工具栏消失,并且无法再通过mediaStyle命令应用:
mediaEmbed: { styles: { options: [ 'alignBlockLeft', 'alignCenter', 'alignBlockRight' ] } }上例中环绕浮动(alignLeft、alignRight)被剔除。此时mediaEmbed:wrapText下拉会因两个子项都被过滤而自动跳过,只留下三种块级对齐。
覆盖内置样式
要定制某个内置样式,传入一个name与内置样式匹配、外加你想改的字段的对象。设置的字段替换内置默认值,省略的字段继承:
mediaEmbed: { styles: { options: [ 'alignLeft', { name: 'alignCenter', title: 'Center' }, 'alignRight' ] } }添加自定义样式
添加自定义样式时,提供一个全新name、title、icon和className的对象。CSS 由你自己负责,插件只在样式被应用时把类名写到 figure 上:
import sideMediaIcon from 'path/to/side-media.svg'; ClassicEditor .create( { // ... Other configuration options ... mediaEmbed: { toolbar: [ 'mediaEmbed:alignCenter', 'mediaEmbed:side' ], styles: { options: [ 'alignCenter', { name: 'side', title: 'Side media', icon: sideMediaIcon, className: 'media-style-side' } ] } } } );/* 自定义样式对应的 CSS。 */ .ck-content .media.media-style-side { float: right; margin: 0 0 1em 1.5em; clear: none; box-shadow: 0 4px 16px rgba( 0, 0, 0, 0.2 ); }同一套机制也支持纯语义化样式——自定义样式不必与「对齐」有关。比如「精选媒体」加边框阴影、「侧栏媒体」收窄宽度,都可以走同样的name + title + icon + className通道。仓库自带的演示片段 media-embed-styles-custom.js 就是一个完整实例:它用三个纯自定义样式(featured、asideLeft、asideRight)替换了全部内置对齐,并把两个 aside 样式分组进一个自定义 split-button 下拉。
自定义默认样式
将某个样式标记为默认,设置isDefault: true即可。默认样式不需要className——默认状态在模型上就是mediaStyle属性的缺失,因此 downcast 时不会写任何类。应用默认样式会清除之前设置的任何其它样式。
import naturalIcon from 'path/to/natural.svg'; mediaEmbed: { styles: { options: [ 'alignBlockLeft', { name: 'natural', title: 'Natural position', icon: naturalIcon, isDefault: true }, 'alignBlockRight' ] } }警告:只应把一个样式标记为默认。多个都标记时,解析顺序中第一个生效;一个都不标记时,命令没有默认值——此时被选媒体没有mediaStyle属性,command.value就是false。
工具栏配置:config.mediaEmbed.toolbar
config.mediaEmbed.toolbar的每个条目要么是内置组件名(字符串),要么是内联的 split-button 下拉定义(对象),两者可自由混用。
内置下拉:mediaEmbed:wrapText分组环绕对齐,mediaEmbed:breakText分组块级对齐。两个内置下拉的定义见 constants.ts。每个下拉的主按钮会反映当前实际应用的子项(图标、文案都实时镜像当前选中的子按钮),没有应用任何子项时回退到下拉自身的默认项(wrap 回退alignLeft,break 回退alignCenter)。当你的样式配置使某个下拉存活子项少于 2 个时,该下拉会被自动跳过。
mediaEmbed: { toolbar: [ 'mediaEmbed:wrapText', 'mediaEmbed:breakText' ] }平铺按钮:每个样式同时也暴露为独立按钮mediaEmbed:<style-name>:
mediaEmbed: { toolbar: [ 'mediaEmbed:alignLeft', 'mediaEmbed:alignBlockLeft', 'mediaEmbed:alignCenter', 'mediaEmbed:alignBlockRight', 'mediaEmbed:alignRight' ] }自定义 split-button 下拉:与内置条目并排内联声明自己的分组。定义遵循MediaStyleDropdownDefinition形态——name、title、items、defaultItem——且所有名称都必须使用完整的mediaEmbed:前缀:
mediaEmbed: { toolbar: [ 'mediaEmbed:alignCenter', { name: 'mediaEmbed:myAlignments', title: 'Alignment', items: [ 'mediaEmbed:alignBlockLeft', 'mediaEmbed:alignBlockRight' ], defaultItem: 'mediaEmbed:alignBlockLeft' } ] }自定义下拉继承与内置下拉相同的过滤与跳过行为,源码中的处理逻辑(mediaembedstyleui.ts)可以归纳为:
- 引用了不在解析后
options列表中的样式的条目会在注册时被过滤;自定义下拉因此还会触发media-style-configuration-definition-invalid警告(说明配置未完全生效),内置下拉则静默自动跳过; - 存活子项少于 2 个的下拉整体跳过——单子项下拉没有价值,平铺按钮更好;
- 若配置的
defaultItem被过滤掉了,第一个存活子项成为新默认。
下拉定义本身在结构非法时也会被丢弃(同样带警告)。isValidCustomDropdown()(见 mediaembedstyleui.ts)的检查规则为:name必须以mediaEmbed:开头;title必须是非空字符串;items必须非空且每项都是mediaEmbed:前缀的字符串;defaultItem必须包含在items中。另外,插件区分「样式下拉」与通用工具栏分组用的判别字段是defaultItem——通用分组用items + label,不会带defaultItem(见 utils.ts 的isMediaStyleDropdown类型守卫)。
公共 API 与底层原理
MediaEmbedStyle插件注册了以下内容:
- 每个样式选项一个按钮,例如
'mediaEmbed:alignLeft'、'mediaEmbed:alignCenter'(用于媒体嵌入的上下文工具栏); - 两个内置 split-button 下拉:
'mediaEmbed:wrapText'与'mediaEmbed:breakText'(均会在存活子项少于 2 个时自动跳过); - 你在
config.mediaEmbed.toolbar中内联声明的所有自定义下拉; mediaStyle命令,接受解析后样式选项之一的值:
// 让选中的媒体浮动到左侧,文字环绕。 editor.execute( 'mediaStyle', { value: 'alignLeft' } ); // 清除样式,回到默认状态。 editor.execute( 'mediaStyle', { value: null } );解析后选项之外的值会被静默拒绝;传默认样式名(或null)都会清除mediaStyle属性。
命令的完整行为在 mediaembedstylecommand.ts 中可以直接验证,execute()的处理分支为:
value为 falsy,或该样式isDefault: true→writer.removeAttribute( 'mediaStyle', element ),即回到默认状态;value不在解析后的样式集合中 → 直接返回(静默拒绝);- 否则 →
writer.setAttribute( 'mediaStyle', requestedStyle, element )。
refresh()则保证 UI 状态与模型同步:没有选中媒体时value为false;选中媒体有mediaStyle属性时回显属性值(若该名称后来被配置移除了,会回退到有效默认或false,与 downcast 的实际渲染保持一致);没有属性时回显默认样式名。
样式如何变成 HTML 类名
引擎侧的转换逻辑在 mediaembedstyleediting.ts:
- Downcast(模型 → 视图):监听
attribute:mediaStyle:media,按「样式名 → 类名」映射对 figure 做removeClass/addClass;映射表在构建时就排除了默认样式(它不产生类)。该转换同时覆盖编辑与数据两条管线,所以你导出的 HTML 也会带上这些类; - Upcast(HTML → 模型):以
low优先级监听element:figure(确保主 media upcast 先创建media模型元素),按插入顺序消费类名并还原mediaStyle属性;当一个 figure 上同时出现多个对齐类时,最后一个被消费的类生效。
也就是说,样式数据的「单一事实来源」是模型上的mediaStyle属性,类名只是它在 view 层的投影。
与调整大小(Resize)功能的配合
建议把内置对齐样式与可选的媒体嵌入 resize 功能组合使用,因为两者在设计上就是配套的:resize 控制宽度,对齐控制位置。
没有 resize 功能时,嵌入默认占满编辑器全宽,对齐类不会产生可见效果——figure 已经占据整行了。只有当 figure 比容器窄时(通过 resize 功能、你自己的 CSS、或以其它方式保留下来的style),对齐才开始产生可见变化。自定义的非对齐类样式(如投影、边框处理)不依赖宽度,无论是否启用 resize 都有效。
一个同时应用了对齐和 resize 的媒体嵌入,其 HTML 表示形如:
<figure class="media media_resized media-style-align-left" style="width:50%;">...</figure>开发调试时推荐配合使用官方 CKEditor 5 inspector,它可以展示编辑器内部数据结构、选区、命令状态等大量有用信息。
小结
媒体嵌入样式功能的设计可以概括为三点:其一,样式集合(styles.options)是唯一的真源,命令、按钮、下拉全部由解析后的同一份列表驱动,配置裁剪会在全链路上保持一致;其二,默认样式编码为模型属性的「缺失」而非某个特殊类名,这使「清除样式」与「应用默认」语义统一;其三,编辑器只负责写类名,视觉呈现交给集成方的 CSS,内置类名(media-style-align-left等)与 theme/index-content.css 中的默认规则可作为起点。相关行为还有完善的测试覆盖,可参考 tests/mediaembedstyle/ 目录下的命令、编辑、UI 与集成测试。
【免费下载链接】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),仅供参考