将光标设置到 CKEditor 中的特定位置

IT技术 javascript ckeditor contenteditable
2021-01-29 19:49:28

有没有办法将光标位置设置为 CKEditor 中的已知索引?

我想这样做是因为当我在编辑器中更改 html 时,它会将光标重置到插入元素的开头,这是一个问题,因为我在用户键入时动态更改内容。

如果我知道我想将光标设置回编辑器内的已知字符位置,例如 100,这可能吗?

(我问了一个相关的问题,但我认为我用示例代码使问题过于复杂。)

2个回答

设置选择的基本方法是创建一个Range,设置它的位置并选择它。

注意:如果您不了解 Range API(或至少不了解范围背后的想法),您将无法使用选择。这是一个很好的介绍 - DOM Range 规范(是的,这是一个规范,但它很好)。CKEditor 的 Range API非常相似,但要大一点。

例如:

// Having this HTML in editor:
// <p id="someId1">foo <em id="someId2">bar</em>.</p>

var range = editor.createRange();
range.setStart( editor.document.getById( 'someId1' ), 0 ); // <p>^foo
range.setEnd( editor.document.getById( 'someId2' ).getFirst(), 1 ); // <em>b^ar</em>

editor.getSelection().selectRanges( [ range ] );

// Will select:
// <p id="someId1">[foo <em id="someId2">b]ar</em>.</p>

或者其他情况:

// Having this HTML in editor:
// <p>foo bar.</p>
var range = editor.createRange();
range.moveToElementEditablePosition( editor.editable(), true ); // bar.^</p>

editor.getSelection().selectRanges( [ range ] );

// Will select:
// <p>foo bar.^</p>

更改 DOM 后恢复选择

但是很多时候你不想选择一个新的范围,而是要恢复一个旧的选择或范围。您需要知道的第一件事是,如果您进行了不受控制的 DOM 更改,则无法正确恢复选择您需要能够跟踪选择的开始和结束的容器和偏移量。

Range 保留对其开始和结束容器(instartContainerendContainer属性)的引用不幸的是,此引用可能被以下内容所违反:

  • 覆盖innerHTML
  • 移动 DOM 节点,
  • 删除 DOM 节点。

偏移量(startOffsetendOffset属性)也可能发生同样的情况——如果您删除了开始/结束容器的子节点之一,则这些偏移量可能需要更新。

因此,在某些情况下,当我们想要记住选择位置时,范围实例没有帮助。我将解释处理这个问题的三种基本方法。

首先,这是我们的计划:

  1. 我们得到当前的选择位置。
  2. 我们存储它(以某种方式)。
  3. 我们进行 DOM 更改。
  4. 我们恢复选择。

注意:从现在开始我使用复数形式的“范围”,因为 Firefox 支持多个范围选择 - 一个选择可以包含多个范围(尝试例如在进行选择时使用 CTRL 键)。

解决方案 1 - 按范围

var ranges = editor.getSelection().getRanges();

// Make DOM changes.

editor.getSelection().selectRanges( ranges );

这是最简单的解决方案。只有当我们所做的 DOM 更改没有过时的范围或者我们知道如何更新它们时,它才会起作用。

解决方案 2 - 通过侵入性书签

var bookmarks = editor.getSelection().createBookmarks();

// Make DOM changes.

editor.getSelection().selectBookmarks( bookmarks );

由该createBookmarks方法创建的书签在选择范围的起点和终点插入<span>具有特殊属性(包括data-cke-bookmark)的不可见元素

如果您可以避免不受控制的innerHTML更改,而是附加/删除/移动一些节点,那么请记住您必须保留这些<span>元素,并且此方法将完美运行。如果您的修改也应更改选择,您还可以移动书签的元素。

默认情况下,书签保留对其<span>元素的引用,但您也可以创建传递truecreateBookmarks方法的可序列化书签这种书签将通过 id 保留对节点的引用,因此您可以覆盖整个innerHTML.

注意:此方法在Range API 中也可用

这是最流行的方法,因为您可以完全控制选择并且可以更改 DOM,但您需要注意书签的spans.

解决方案 3 - 通过非侵入式书签

var bookmarks = editor.getSelection().createBookmarks2();

// Make DOM changes.

editor.getSelection().selectBookmarks( bookmarks );

注意:在此解决方案中,我们使用createBookmarks2方法。

这里我们还创建了一个书签对象数组,但我们没有向 DOM 中插入任何元素。这些书签通过地址存储它们的位置。地址是其父项中祖先索引的数组。

此解决方案与解决方案 1 非常相似,但您可以覆盖整个innerHTML,因为它(很可能;>)不会更改书签节点的地址。虽然,在这种情况下,您应该传递truecreateBookmarks2获取规范化地址,因为在设置innerHTML.

总结...

... 使用 DOM 和选择并非易事。您需要知道自己在做什么,需要了解 DOM,并且需要为您的问题选择正确的解决方案。大多数情况下是第二个,但这取决于具体情况。

优秀的答案 - 真的帮助我更好地理解这个问题。谢谢你,先生!
2021-04-12 19:49:28

Reinmar 的回答让我找到了这个解决方案

var selection = ed.getSelection();
var bookmarks = selection.createBookmarks(true);

//delete text from editor

var range = selection.getRanges()[0];
range.moveToBookmark(bookmarks[0]);
range.select();

注意:moveToBookmark 函数未记录在 api 中,但非常有用,并且是唯一对我有用的解决方案。我当然不是 ckeditor 的专家,我花了几天时间才找到一个可行的解决方案。所以 moveToBookmark 可能是一个不推荐使用的功能,我不确定。

那很有意思。我想知道@Reinmar 是否有任何意见?selectBookmarks更改编辑器内容后我没有取得任何成功- 也许moveToBookmark在我的情况下会更好。
2021-03-27 19:49:28
现在似乎已记录在案。docs.ckeditor.com/#!/api/...
2021-04-03 19:49:28