☰
CKEditor 4 Table Resize 插件滚动后光标错位问题(4889)复现手册与修复原理解析
2026/10/16 6:58:59 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载

本手册针对 CKEditor 4 的 Table Resize(表格列宽拖拽调整)插件在窄容器编辑器横向滚动后,列边框上的缩放光标发生错位这一已知缺陷(issue #4889,随 4.20.0 版本修复),给出基于 Bender 手动测试(Manual Test)的完整复现步骤、预期/非预期行为判定标准,并结合插件源码与自动化测试,从"pillar(柱线)缓存失效"的角度剖析缺陷根因与修复机制。读完本文,你将掌握如何定位 Table Resize 的坐标计算核心、理解其滚动场景下的缓存失效问题,并学会用仓库内置的 manual test 与 bender 自动化用例双重验证此类交互缺陷。

一、手册定位:一份"bug 型"手动测试文档

本文所依据的文档是 scrollupdate.md,它是 CKEditor 4 测试体系中典型的manual test(手动测试)说明文档,与同目录下的 scrollupdate.html 页面配套使用。其文档头部的 Bender 指令完整描述了该用例的属性:

@bender-ui: collapsed @bender-tags: bug, 4.20.0, 4889 @bender-ckeditor-plugins: wysiwygarea, toolbar, table, tableresize, tabletools, floatingspace
  • @bender-ui: collapsed:手动测试页面以折叠(collapsed)方式嵌入 Bender 手动测试运行器 UI 中,避免页面干扰操作;
  • @bender-tags: bug, 4.20.0, 4889:明确这是一个bug 回归用例,对应 GitHub issue #4889,修复随4.20.0版本发布(见 CHANGES.md 中 "Fixed: Incorrect position of the Table Resize cursor after scrolling the editor horizontally");
  • @bender-ckeditor-plugins:声明该用例需要加载wysiwygarea、toolbar、table、tableresize、tabletools、floatingspace等插件——注意tableresize插件本身在 plugin.js 中声明了requires: 'tabletools',因此tabletools是其依赖项。

这样的文档结构意味着:它不讲解功能如何使用,而是规定了一套可执行的缺陷复现流程。这是 CKEditor 4 质量保障体系中"回归测试必须可复现、可判定"原则的直接体现。

二、缺陷背景:Table Resize 的光标与 pillar 机制

要理解这个 bug,需要先了解 Table Resize 插件的工作方式。在 plugin.js 中,插件通过buildTableColumnPillars()为表格的每一列边框计算一个"pillar"(柱线,即列边框的可拖拽命中区域),其核心字段如下(plugin.js):

pillar = { table: table, index: pillarIndex, x: pillarLeft, // 柱线左边界,基于 getDocumentPosition() 计算 y: pillarPosition.y, width: pillarWidth, // 最小为 3px(Math.max(pillarRight - pillarLeft, 3)) height: pillarHeight, rtl: rtl };

pillar 的x/y坐标全部来自CKEDITOR.dom.element.getDocumentPosition()——这是一个相对于文档的绝对坐标,而不是相对于视口的坐标。当鼠标移动时,mousemove处理器通过evt.getPageOffset()取得页面级坐标,再调用getPillarAtPosition()判断鼠标是否落在某个 pillar 范围内(plugin.js)。

为了提高性能,插件把每次计算出的 pillars 缓存到表格的自定义数据_cke_table_pillars上(plugin.js),并只在mouseout/mousedown时通过clearPillarsCache()清除缓存(plugin.js)。

滚动为何导致错位

缺陷的根源在于:pillar 坐标是相对文档的绝对坐标,而getPageOffset()取到的鼠标位置会随着编辑区域滚动而变化。

  • 当编辑区域(inline/divarea 容器或 iframe 文档)发生横向滚动后,页面级鼠标坐标已经变化;
  • 但缓存的 pillars 中记录的是滚动前计算的文档坐标,未随滚动失效;
  • 于是getPillarAtPosition()命中了一个坐标过期的 pillar,columnResizer.attachTo()据此把缩放条(resizer)定位到错误位置(plugin.js),表现为"缩放光标从列边框上偏移"。

值得注意的是,手动测试文档特别注明了一个细节:"The bug does not occur when dragging the scrollbar."(用鼠标滚轮或触控板手势滚动时出现,拖动滚动条时不出现)。这与浏览器事件模型一致——拖动滚动条不会触发容器/文档的scroll事件流(或鼠标位置计算路径不同),而滚轮/手势滚动才会让编辑器内表格的文档坐标与页面鼠标坐标产生错位。

三、复现步骤(原文档核心操作流程)

以下步骤完整继承自 scrollupdate.md,并结合 scrollupdate.html 的测试页面结构给出补充说明。测试页面包含三种编辑器形态,共 3 个待测实例:

编辑器创建方式容器特征
Inline editorCKEDITOR.inline( 'inline' )250px 宽、overflow: auto的可编辑 div
div-based editorCKEDITOR.replace( 'div', { extraPlugins: 'divarea', width: 250 } )250px 宽、overflow: auto的 div 编辑区
iframe-based editorCKEDITOR.replace( 'iframe', { width: 250 } )250px 宽的标准 iframe 编辑区

每个实例内部都是一张style="width:500px"、border="1"的 5 行 4 列表格——即表格宽度(500px)远超容器宽度(250px),必然产生横向滚动条,这是触发本 bug 的前提条件。此外 scrollupdate.html 中还有if ( bender.tools.env.mobile ) { bender.ignore(); },表示移动端环境自动跳过该用例。

操作步骤

  1. 将编辑器滚动到最右端,再稍微向左回滚一点。
    • ⚠️注意:必须使用鼠标滚轮或触控板手势滚动;拖动滚动条无法复现该 bug(原因见上文第二节)。
  2. 尝试拖拽调整最后一列(将鼠标悬停到最右侧列边框上,出现col-resize光标后按下拖拽)。
  3. 对页面上的全部 3 个编辑器(inline、div-based、iframe-based)重复上述过程。

预期结果(Expected)

缩放光标位于正确的位置(即恰好贴合最后一列的边框线),拖拽后列宽调整正常生效。

非预期结果(Unexpected)

缩放光标从列的边框上偏移——由于 pillar 缓存未随滚动失效,resizer 被定位到了滚动前的位置,鼠标指针看起来"悬空"在边框之外,无法准确拖拽最后一列。

四、修复原理:scroll 事件触发 pillar 缓存重置

针对 #4889,plugin.js 中新增了如下修复逻辑:

// Reset pillars position on scroll (#4889). editable.attachListener( listenerTarget, 'scroll', function() { var tables = editable.find( 'table' ).toArray(); CKEDITOR.tools.array.forEach( tables, CKEDITOR.tools.debounce( function( table ) { table.removeCustomData( '_cke_table_pillars' ); }, 200 ) ); } );

关键设计有三点:

  1. 监听目标的选择:listenerTarget在 init 时被确定为——inline 编辑器用editable本身,经典编辑器用editor.document(plugin.js)。这样无论滚动发生在可编辑容器还是 iframe 文档上,都能捕获到scroll事件。
  2. 批量失效:滚动发生时,遍历 editable 内的所有表格,逐一移除_cke_table_pillars缓存数据。下次鼠标移动时,buildTableColumnPillars()会重新按最新文档坐标计算 pillars,光标位置随即恢复正确。
  3. 防抖(debounce):用CKEDITOR.tools.debounce( ..., 200 )将清除动作延迟 200ms 合并执行,避免连续滚动造成频繁重算,兼顾正确性与性能。

与此同时,自动化测试 tableresize.js 中也新增了两个断言用例:

  • test pillars are resetted on scroll (inline):对 inline 编辑器触发editable.fire( 'scroll', {} ),等待 210ms(超过 200ms 防抖窗口)后断言table.getCustomData( '_cke_table_pillars' )为null;
  • test pillars are resetted on scroll (classic):对经典编辑器触发editor.document.fire( 'scroll', {} ),同样断言缓存被清空。

这两个用例与手动测试文档形成了自动化 + 人工双重验证的闭环:自动化用例用 mock 事件验证缓存失效行为,手动用例验证真实浏览器滚动交互下的最终用户体验。测试还定义了inlineOverflow/classicOverflow两个带横向溢出场景的编辑器实例(tableresize.js),与 manual test 页面的窄容器设置相互印证。

五、如何运行该手动测试

本仓库使用 Bender.js 作为测试运行框架(其tests配置中Plugins分组以tests/为 basePath 覆盖plugins/**,见 bender.js),依赖由 package.json 中的benderjs系列包提供。

  • 运行全部手动测试:bender run manual(手动测试文档通过@bender-ui等标记被 Bender 识别为 manual test 分组);
  • 定位本用例:运行后选择Plugins分组下的tableresize,即可在页面列表中找到scrollupdate用例,按第三节的步骤逐条验证。

由于仓库只读,本文仅介绍查看与运行方式;实际验证时,请在你的开发副本中安装依赖并启动 Bender 服务器,然后按上述流程在真实浏览器中操作。

六、小结:从一份 manual test 学到什么

通过这份 scrollupdate.md,可以提炼出三条可复用的工程经验:

  1. "坐标缓存类"交互缺陷的复现要点:凡是基于文档绝对坐标 + 缓存的交互(pillar、tooltip、浮动面板等),都必须验证滚动、缩放等位移场景;且复现方式(滚轮 vs 拖滚动条)可能影响 bug 是否出现,测试文档必须写清楚;
  2. 修复的标准姿势:监听scroll事件 → 失效相关缓存 → 用 debounce 控制频率,是 CKEditor 4 中处理"滚动导致缓存坐标过期"类问题的通用模式(见 plugin.js);
  3. 回归测试的双轨验证:manual test(真实交互体验)+ 自动化断言(缓存状态变化)组合,既保证"用户视角正确",又保证"实现细节正确",这在 tableresize.js 中体现得尤为清晰。

如果需要继续深入 Table Resize 插件,可阅读同目录下的其他手动测试,如 pillar.md(pillar 高度与整表等高)、mergedcells.md(合并单元格边界命中)、maximizeddivarea.md(最大化后缩放)等,它们共同覆盖了该插件的坐标计算与交互边界场景。

  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载
上一篇:PyQt-SiliconUI:基于PyQt5的终极UI框架,让你的桌面应用瞬间灵动优雅
下一篇:Django-Treebeard 完全指南:打造高效树形结构的终极解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询