- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
导读
本文围绕 CKEditor 4 中 floatpanel(浮层面板)组件在窗口尺寸变化与移动端软键盘弹出/收起两种场景下的定位与显示行为展开。floatpanel 是 CKEditor 4 工具栏下拉框(Paragraph Format、Font、Color 等)与右键菜单的底层承载组件,其定位正确性直接影响编辑器的日常可用性。阅读本文后,你将理解 floatpanel 的定位原理与reposition()机制,掌握如何通过 Bender 手动测试用例验证面板在移动设备软键盘、浏览器窗口缩放下的表现,并能对照自动化测试与源码定位同类回归问题。
本文的验证要点以仓库中的手动测试说明 tests/plugins/floatpanel/manual/resize.md 为骨架,结合 floatpanel 插件源码、Bender 自动化测试与格式下拉框(format 插件)实现展开。
一、floatpanel:几乎所有下拉 UI 的底层载体
1.1 什么是 floatpanel
floatpanel 是 CKEditor 4 中的一个 UI 基础组件,用于承载那些"悬浮在文档上方、不占据正常文档流"的面板内容。插件声明极其精简:
// plugins/floatpanel/plugin.js CKEDITOR.plugins.add( 'floatpanel', { requires: 'panel' } );它本身不渲染任何 UI,而是依赖panel插件提供的面板渲染能力,对外暴露CKEDITOR.ui.floatPanel类,并在构造时强制将面板内容放入 iframe(definition.forceIFrame = 1,见 plugins/floatpanel/plugin.js),从而隔离宿主页面样式对面板内容的干扰。
从源码注释可以确认它的使用范围:
"It is reused by rich combos, color combos, menus, etc. and it renders its content using CKEDITOR.ui.panel."
也就是说,富下拉框(rich combo)、颜色下拉框、菜单等 UI 全部复用 floatpanel。
1.2 谁在用它:以 Paragraph Format 为例
本文手动测试的主角是工具栏上的Paragraph Format(段落格式)下拉框。它由format插件通过richcombo注册:
// plugins/format/plugin.js init: function( editor ) { ... editor.ui.addRichCombo( 'Format', { label: lang.label, title: lang.panelTitle, toolbar: 'styles,20', ... panel: { css: [ CKEDITOR.skin.getPath( 'editor' ) ].concat( config.contentsCss ), multiSelect: false, attributes: { 'aria-label': lang.panelTitle } }, ... } ); }而richcombo插件在声明依赖时直接指定了floatpanel:
// plugins/richcombo/plugin.js CKEDITOR.plugins.add( 'richcombo', { requires: 'floatpanel,listblock,button', ... });在createPanel()中,richcombo 会实例化一个CKEDITOR.ui.floatPanel并注册列表块(plugins/richcombo/plugin.js):
var panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), list = panel.addListBlock( this.id, panelBlockDefinition );因此,Paragraph Format 下拉框展开后,其视觉呈现与定位逻辑完全由 floatpanel 负责——这正是本测试用例要验证的对象。
二、手动测试用例全解:resize 场景
2.1 测试文件与其元数据
本用例位于tests/plugins/floatpanel/manual/目录,由一对文件组成:
- 操作说明:tests/plugins/floatpanel/manual/resize.md
- 测试页面:tests/plugins/floatpanel/manual/resize.html
resize.md头部是 Bender 手动测试的元数据声明:
@bender-tags: bug, 4.5.4, trac11724 @bender-ui: collapsed @bender-include: ../../embedbase/_helpers/tools.js @bender-ckeditor-plugins: wysiwygarea,sourcearea,htmlwriter,entities,toolbar,elementspath,undo,clipboard,format,basicstyles,font,autolink,autoembed,link逐条解读:
| 指令 | 含义 |
|---|---|
@bender-tags: bug, 4.5.4, trac11724 | 标注该用例属于 bug 回归测试,关联 4.5.4 版本中修复的 ticket #11724 |
@bender-ui: collapsed | 以折叠 UI 模式加载测试页面(仅显示编辑器本体) |
@bender-include: ../../embedbase/_helpers/tools.js | 引入 embedbase 插件的测试辅助工具 tests/plugins/embedbase/_helpers/tools.js |
@bender-ckeditor-plugins: ... | 声明测试页启用的插件集合,其中format提供 Paragraph Format 下拉框、richcombo(经 format 间接依赖)提供下拉框 UI 载体 |
测试页面本体极其简单——一个 80% 宽度的居中容器内放置经典编辑器实例:
<!-- tests/plugins/floatpanel/manual/resize.html --> <div style="width: 80%; margin: 0 auto"> <textarea cols="80" id="classic" name="classic" rows="10"> <p>Test</p> </textarea> </div> <script> CKEDITOR.replace( 'classic' ); </script>2.2 移动设备验证步骤
原文档针对移动端给出如下操作序列:
- 点击编辑区,让系统软键盘弹出(软件键盘可见);
- 点击工具栏上的 Paragraph Format 下拉框,展开样式列表;
- 预期结果:软键盘应自动收起,且下拉框保持可见、位置正确。
这条路径背后对应着一个真实的移动端 bug(ticket #11724)。软键盘的弹出会改变浏览器视口高度,触发window.resize事件;而旧实现中 floatpanel 监听窗口 resize 后直接执行hide(),导致在 Android / iOS 上"下拉框一打开就消失"。修复后,窗口 resize 不再无条件隐藏面板,而是触发重定位(见下文第三节源码分析)。
2.3 其他设备(桌面)验证步骤
原文档针对桌面浏览器给出另一组操作序列:
- 打开 Paragraph Format 下拉框;
- 检查其初始定位是否正确(应紧贴下拉按钮、完整显示在视口内);
- 缩放浏览器窗口(改变窗口尺寸);
- 预期结果:下拉框应要么被隐藏,要么被正确重定位,不允许出现"面板悬在半空、与触发按钮错位"或"面板被裁切"的情况。
注意"要么隐藏、要么重定位"是 4.5.4 修复后明确的两种合法结果——这与源码中hide()与reposition()两条路径并存的设计完全一致(详见下文)。
2.4 判定标准小结
| 设备类型 | 触发操作 | 期望结果 |
|---|---|---|
| 移动设备 | 点编辑区 → 软键盘弹出 → 点 Paragraph Format | 软键盘收起,下拉框可见且定位正确 |
| 桌面浏览器 | 打开 Paragraph Format → 缩放窗口 | 面板隐藏或正确重定位,不错位、不裁切 |
三、源码级原理:4.5.4 的修复如何生效
3.1 定位计算:showBlock 的四个角
floatpanel 的定位在showBlock()中完成(plugins/floatpanel/plugin.js)。它根据corner参数决定面板与触发元素的对齐方式:
- LTR(从左到右)编辑器下:
1=左上、2=右上、3=右下、4=左下; - RTL(从右到左)编辑器下:
1=右上、2=左上、3=左下、4=右下。
同时支持offsetX、offsetY微调偏移。定位完成后,还有一段"智能适配视口"的逻辑(plugins/floatpanel/plugin.js):通过getBoundingClientRect()与getViewPaneSize()比较面板矩形与视口尺寸,当右侧空间不足时向左翻转、下方空间不足时向上翻转,确保面板完整落在视口内——这正是桌面步骤 2"初始定位正确、不被裁切"的实现保障。
3.2 两类事件监听:hide 与 reposition
floatPanel 构造函数中注册了如下事件(plugins/floatpanel/plugin.js):
editor.on( 'mode', hide ); editor.on( 'resize', hide ); // When resize of the window is triggered floatpanel should be repositioned according to new dimensions. // https://dev.ckeditor.com/ticket/11724. Fixes issue with undesired panel hiding on Android and iOS. doc.getWindow().on( 'resize', function() { this.reposition(); }, this );- 编辑器
resize事件 → hide:编辑器本体尺寸变化(如用户拖动编辑器边框)时直接隐藏面板,避免面板与编辑器错位; - 窗口
resize事件 → reposition:浏览器窗口尺寸变化(包括移动端软键盘弹出导致的视口变化)时,调用reposition()重新摆放面板,而不是隐藏。
源码注释明确写到:"When resize of the window is triggered floatpanel should be repositioned according to new dimensions. Fixes issue with undesired panel hiding on Android and iOS."——这正是 ticket #11724 的修复内容,与本文手动测试用例的trac11724标签一一对应。
3.3 reposition 的实现:完整重放 showBlock
reposition()(plugins/floatpanel/plugin.js)是 4.5.4 新增的 API:
/** * Repositions the panel with the same parameters that were used in the last {@link #showBlock} call. * * @since 4.5.4 */ reposition: function() { var blockParams = this._.showBlockParams; if ( this.visible && this._.showBlockParams ) { this.hide(); this.showBlock.apply( this, blockParams ); } }其设计要点:
showBlock()在每次展示时会把入参快照保存到this._.showBlockParams(见 plugins/floatpanel/plugin.js);reposition()仅在面板可见(this.visible)且存在快照时生效,先隐藏再以完全相同的一组参数重新执行showBlock();- 由于窗口尺寸已经变化,
showBlock()内部的视口适配逻辑会依据新尺寸重新计算翻转方向与坐标,从而把面板"搬"到正确位置。
因此,桌面场景下缩放窗口时,如果面板仍可见,它会带着一致的触发参数被重新摆放——而不是停留在旧坐标上。
3.4 为什么移动端"软键盘收起"能保住面板
移动端软键盘弹出时,浏览器会收缩视口高度,触发窗口 resize;此时面板通过reposition()被重新计算位置,不会被直接隐藏。同时,showBlock()中还针对 iOS 做了 blur 时序保护(plugins/floatpanel/plugin.js):iframe blur 后不立即隐藏,而是用CKEDITOR.tools.setTimeout( doHide, 0, this )延迟执行,并监听touchstart/touchend取消误触发——这样"点编辑区弹出键盘 → 再点下拉框"的连续操作不会因为焦点抖动把面板关掉。
注意:
@bender-tags: ... 4.5.4, trac11724表明该用例是为验证 4.5.4 的修复而设计的手动回归用例,预期行为以 4.5.4 及以后版本的实现为准。
四、如何运行本手动测试
Bender 是 CKEditor 4 的测试运行器,其配置见 bender.js。手动测试需要指定接受手动测试的浏览器(manualBrowsers配置项,bender.js),并且测试服务器需要以 HTTPS 提供服务(privateKey/certificate配置指向 tests/_benderjs/ssl/key.pem 与 tests/_benderjs/ssl/cert.pem)。
典型运行方式:
- 在仓库根目录安装并启动 Bender 测试服务器,将浏览器加入
manualBrowsers列表(本用例需要真机或模拟器上的移动浏览器,以及一台桌面浏览器); - 打开 Bender 手动测试面板,定位到
plugins/floatpanel→resize用例; - 分别按上文第二节"移动设备"与"其他设备"两组步骤操作并核对预期结果;
- 若面板在软键盘弹出后消失、或在窗口缩放后错位/被裁切,即为回归,应关联 ticket #11724 排查。
@bender-ui: collapsed意味着测试页会以折叠 UI 呈现,便于聚焦编辑器本体;@bender-include引入的 tests/plugins/embedbase/_helpers/tools.js 为该页面提供辅助工具函数(同时它也是 autoembed 相关测试的共用助手)。
五、配套自动化测试:把行为固化为断言
除了手动用例,floatpanel 还配有 Bender 自动化测试 tests/plugins/floatpanel/floatpanel.js,其中'test panel reposition on window resize'与手动用例直接呼应:
// 模拟 window resize 事件 var e = document.createEvent( 'HTMLEvents' ); e.initEvent( 'resize', true, false ); document.body.dispatchEvent( e ); assert.isTrue( showBlockSpy.calledTwice, 'panel.showBlock should be called twice' ); assert.isTrue( repositionSpy.calledOnce, 'panel.reposition should be called once' ); // 检查第二次 showBlock 使用了与第一次完全相同的参数 args = showBlockSpy.firstCall.args; assert.isTrue( showBlockSpy.secondCall.calledWith( args[0], args[1], args[2] ), 'Second showBlock() call should use same arguments as first call.' );这段测试用sinon.spy分别监听showBlock与reposition,断言窗口 resize 触发后:
reposition()恰好被调用一次;showBlock()被调用两次(首次打开 + reposition 内部重放);- 第二次
showBlock()的参数与第一次完全一致——验证了reposition()"用相同参数重放"的实现约定。
同文件还覆盖了面板的打开/隐藏、mode切换时隐藏、编辑器resize时隐藏、第二个面板在编辑器 resize 时被关闭(ticket #9800)等行为,共同构成 floatpanel 生命周期与定位行为的回归防线。手动用例与自动化用例互补:自动化用例锁定"事件→调用"的契约,手动用例则验证真实设备上的视觉结果(软键盘交互、视口适配)。
六、版本与历史背景
- 4.5.4:修复 ticket #11724——"触屏设备上下拉框经常在打开后立即消失"。核心改动即在窗口 resize 时调用
reposition()而非隐藏,并在插件源码中保留了明确的注释说明(plugins/floatpanel/plugin.js)。 - 4.5.8:同目录下另一手动用例 tests/plugins/floatpanel/manual/rendering.md(标签
4.5.8, trac13756)验证列表项内右键菜单的渲染布局不被裁切,与本文用例同属 floatpanel 布局正确性的回归矩阵。 - 更早的 4.x 版本中,ticket #9800("Hide float panel when resizing the window")确立了"窗口 resize 时面板应隐藏或重定位"的行为边界(见 CHANGES.md),#11724 则进一步区分了移动端软键盘这一特殊 resize 来源。
这些历史 ticket 号(trac11724、trac9800、trac13756)均出现在测试元数据与 CHANGES.md 的 Fixed Issues 列表中,可作为回归排查时的检索关键词。
七、结论与实践要点
- floatpanel 是 CKEditor 4 下拉 UI 的公共底座:Paragraph Format、Font、颜色等 richcombo 下拉框及菜单都由它渲染与定位(依赖链:
format → richcombo → floatpanel → panel)。 - 窗口 resize 与编辑器 resize 是两条不同的处理路径:前者触发
reposition()重定位(4.5.4 起),后者触发hide()隐藏,二者不可混淆。 - 移动端软键盘场景的关键:软键盘弹出引起的视口变化不应导致下拉框消失;验证时重点观察"软键盘收起 + 面板保持可见且定位正确"。
- 桌面缩放场景的验收标准:缩放窗口后,面板要么被隐藏,要么被正确重定位,不得出现错位或裁切。
- 回归防线完整:手动用例(tests/plugins/floatpanel/manual/resize.md)验证真实设备表现,自动化用例(tests/plugins/floatpanel/floatpanel.js)锁定
reposition()的行为契约,配合 CHANGES.md 中的 ticket 记录,可快速定位同类问题。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
CKEditor 4 Balloon Toolbar 窗口缩放重定位机制与手动测试指南
CKEditor 4 Balloon Toolbar 窗口缩放重定位机制与手动测试指南 本篇技术指南围绕 CKEditor 4 balloontoolbar (
前端UI组件CKEditor 4 Emoji 自动补全面板视口定位验证指南:从 Bender 手动测试到源码级定位原理
CKEditor 4 Emoji 自动补全面板视口定位验证指南:从 Bender 手动测试到源码级定位原理 本文面向 CKEditor 4 的插件开发者与测试工
前端UI组件CKEditor 4 Mentions 面板视口内智能定位:手动测试用例、源码原理与回归验证
CKEditor 4 Mentions 面板视口内智能定位:手动测试用例、源码原理与回归验证 CKEditor 4 的 Mentions(提及)插件让用户通过输
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考