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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16835365/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 06:10:41  来源:igfitidea点击:

Set cursor to specific position in CKEditor

javascriptckeditorcontenteditable

提问by manannan

Is there a way to set the cursor position to a known index inside CKEditor?

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

I want to do this because when I change the html inside the editor it resets the cursor to the start of the inserted element, which is a problem as I'm changing the content on the fly as the user types.

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

If I know that I want to set the cursor back to a known character position, say 100, inside the editor, is this possible?

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

(I asked a related questionbut I think I was overcomplicating the issue with example code.)

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

回答by Reinmar

The basic way of setting selection is by creatinga Range, setting its position and selectingit.

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

Note: if you don't know the Range API (or at least the idea which stands behind ranges), you won't be able to use selection. Here's a pretty good introduction - DOM Range spec(yep, it is a spec, but it's good). CKEditor's Range APIis very similar, but a little bit bigger.

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

For example:

例如:

// 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>

Or other case:

或者其他情况:

// 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>

Restoring selection after changing DOM

更改 DOM 后恢复选择

But very often you don't want to select a new range, but to restore an old selection or range. First thing you need to know is that it is impossible to correctly restore selection if you made an uncontrolled DOM changes. You need to be able to keep track of the containers and offsets of the selection's start and end.

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

Range keeps the references to its start and end containers (in startContainerand endContainerproperties). Unfortunately, this references may be violated by:

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

  • overwriting innerHTML,
  • moving DOM nodes around,
  • deleting DOM nodes.
  • 覆盖innerHTML
  • 移动 DOM 节点,
  • 删除 DOM 节点。

The same may happen with offsets (startOffsetand endOffsetproperties) - if you removed one of start/end container's child nodes these offsets may need to be updated.

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

So in some situations range instance is not helpful when we want to remember a selection position. I'll explain three basic ways to deal with this problem.

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

First, this is our plan:

首先,这是我们的计划:

  1. We get the current selection position.
  2. We store it (somehow).
  3. We do the DOM changes.
  4. We restore selection.
  1. 我们得到当前的选择位置。
  2. 我们存储它(以某种方式)。
  3. 我们进行 DOM 更改。
  4. 我们恢复选择。

Note:From now on I use "ranges" in plural form because Firefox supports multiple range selections - one selection can contain more than one range (try e.g. to use CTRL key while making selections).

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

Solution 1 - by a range

解决方案 1 - 按范围

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

// Make DOM changes.

editor.getSelection().selectRanges( ranges );

This is the simplest solution. It will work only if the DOM changes which we made haven't outdated ranges or we know how to update them.

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

Solution 2 - by an intrusive bookmarks

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

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

// Make DOM changes.

editor.getSelection().selectBookmarks( bookmarks );

Bookmarks created by the createBookmarksmethod insert invisible <span>elements with special attributes (including data-cke-bookmark) at the selection's ranges start and end points.

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

If you can avoid uncontrolled innerHTMLchanges and instead append/remove/move some nodes, then just remember that you have to preserve these <span>elements and this method will work perfectly. You can also move bookmarks' elements if your modifications should change the selection as well.

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

By default bookmarks keep references to their <span>elements, but you can also create serializable bookmarks passing trueto the createBookmarksmethod. This kind of bookmarks will keep references to nodes by ids, so you can overwrite entire innerHTML.

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

Note: This method is also available in a Range API.

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

This is the most popular method, because you have the full control over selection and you can change DOM, although you need to take care of bookmarks' spans.

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

Solution 3 - by a non intrusive bookmarks

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

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

// Make DOM changes.

editor.getSelection().selectBookmarks( bookmarks );

Note: In this solution we use createBookmarks2method.

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

Here we also create an array of bookmarks objects, but we do not insert any elements into DOM. These bookmarks store their positions by the addresses. Addressis an array of ancestors' indexes in their parents.

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

This solution is very similar to solution 1, but you can overwrite entire innerHTML, because it (most likely ;>) won't change the addresses of bookmarks' nodes. Although, in such a case you should pass trueto createBookmarks2to get normalized addresses because adjacent text nodes will be joined and empty ones removed when setting innerHTML.

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

To sum up...

总结...

... Working with DOM and selection isn't trivial. You need to know what you're doing, you need to know DOM and you need to pick the right solution for your problem. Most often it will be the second one, but it depends on a case.

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

回答by codeAline

The answer from Reinmar led me to this solution

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();

NOTE: the moveToBookmark function is not documented in the api but was extremely useful and was the only solution that worked for me. I'm certainly not an expert on ckeditor and took me a few days to find a working solution. So moveToBookmark maybe a deprecated function I'm not sure.

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