【问题标题】:Can't restore selection after HTML modify, even if it's the same HTML修改 HTML 后无法恢复选择,即使是相同的 HTML
【发布时间】:2013-07-14 18:28:27
【问题描述】:

我正在尝试存储一个 contentEditable 元素的选择并稍后恢复它。

我想观察paste事件并像以前一样存储HTML,清除html然后在所选位置手动插入粘贴的文本并进行一些更改。

看看这个例子:jsfiddle.net/gEhjZ

当您选择文本的一部分时,点击store,再次删除选择并点击restore,它按预期工作。

但是当你第一次点击store,然后点击overwrite html将HTML替换为完全相同的HTML,然后尝试restore,什么都没有发生。

我认为使用.cloneRange() 会有所作为,但事实并非如此。即使是对象的深层副本 ($.extend(true, {}, oldRange)) 也无法解决问题。一旦我覆盖了 HTML,选择对象 sel 也会被更改。更改选择上下文将擦除范围对我来说很有意义,但我正在尝试将其恢复为完全相同的 HTML。

我知道我可以使用rangy,但我真的不想为了这个小功能而使用一个庞大的库。我错过了什么?任何帮助将不胜感激!

注意:仅适用于 Firefox/Chrome,因此无需跨浏览器破解。

更新:

@Tim Down 的答案在使用 div 时有效,但我实际上使用的是 iframe。当我做这个例子时,我认为它不会有任何区别。

现在,当我尝试恢复 iframe 的主体时,出现以下错误:TypeError: Value does not implement interface Node. 在以下行 preSelectionRange.selectNodeContents(containerEl);。我没有从谷歌搜索中得到太多。我试图包装正文的内容并恢复包装的html,但我得到了同样的错误。

jsfiddle 在这种情况下不起作用,因为它使用 iframe 来显示结果本身,所以我在这里举个例子:snipt.org/AJad3

没有包装也一样:snipt.org/AJaf0

更新 2: 当然,我想我必须使用editable.get(0)。但是现在 iframe 选择的startend 是0。见snipt.org/AJah2

【问题讨论】:

    标签: javascript jquery range selection contenteditable


    【解决方案1】:

    提供的解决方案效果很好。

    替换该行

    if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
    

    通过

    if (!foundStart && savedSel.start >= charIndex && savedSel.start < nextCharIndex) {
    

    防止 Chrome / Edge 选择上一行的结尾

    【讨论】:

      【解决方案2】:

      您可以使用以下函数保存和恢复字符位置:

      https://stackoverflow.com/a/13950376/96100

      我已经稍微调整了这些函数以适用于 iframe 中的元素。

      演示:http://jsfiddle.net/timdown/gEhjZ/4/

      代码:

      var saveSelection, restoreSelection;
      
      if (window.getSelection && document.createRange) {
          saveSelection = function(containerEl) {
              var doc = containerEl.ownerDocument, win = doc.defaultView;
              var range = win.getSelection().getRangeAt(0);
              var preSelectionRange = range.cloneRange();
              preSelectionRange.selectNodeContents(containerEl);
              preSelectionRange.setEnd(range.startContainer, range.startOffset);
              var start = preSelectionRange.toString().length;
      
              return {
                  start: start,
                  end: start + range.toString().length
              };
          };
      
          restoreSelection = function(containerEl, savedSel) {
              var doc = containerEl.ownerDocument, win = doc.defaultView;
              var charIndex = 0, range = doc.createRange();
              range.setStart(containerEl, 0);
              range.collapse(true);
              var nodeStack = [containerEl], node, foundStart = false, stop = false;
      
              while (!stop && (node = nodeStack.pop())) {
                  if (node.nodeType == 3) {
                      var nextCharIndex = charIndex + node.length;
                      if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
                          range.setStart(node, savedSel.start - charIndex);
                          foundStart = true;
                      }
                      if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
                          range.setEnd(node, savedSel.end - charIndex);
                          stop = true;
                      }
                      charIndex = nextCharIndex;
                  } else {
                      var i = node.childNodes.length;
                      while (i--) {
                          nodeStack.push(node.childNodes[i]);
                      }
                  }
              }
      
              var sel = win.getSelection();
              sel.removeAllRanges();
              sel.addRange(range);
          };
      } else if (document.selection) {
          saveSelection = function(containerEl) {
              var doc = containerEl.ownerDocument, win = doc.defaultView || doc.parentWindow;
              var selectedTextRange = doc.selection.createRange();
              var preSelectionTextRange = doc.body.createTextRange();
              preSelectionTextRange.moveToElementText(containerEl);
              preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
              var start = preSelectionTextRange.text.length;
      
              return {
                  start: start,
                  end: start + selectedTextRange.text.length
              };
          };
      
          restoreSelection = function(containerEl, savedSel) {
              var doc = containerEl.ownerDocument, win = doc.defaultView || doc.parentWindow;
              var textRange = doc.body.createTextRange();
              textRange.moveToElementText(containerEl);
              textRange.collapse(true);
              textRange.moveEnd("character", savedSel.end);
              textRange.moveStart("character", savedSel.start);
              textRange.select();
          };
      }
      

      【讨论】:

      • 非常感谢,范围现在已正确保存,但不知何故它默默地无法恢复选择。 snipt.org/AJaj0
      • @koko:初始脚本运行时,iframe 文档可能未完全加载。我建议在您的点击处理程序中使用 $('iframe').contents().find('body')[0] 而不是 editable.get(0)
      • 文档已完全加载,我在paste 事件上调用此函数。但是,doc 变量未定义 (containerEl.ownerDocument)。 Chrome 不介意,而 Firefox 则介意。这个我以后再看,我想剩下的会比较容易解决。
      • @koko: containerEl.ownerDocument 未定义表示containerEl 有问题,例如已从文档中删除。
      • 虽然我猜有一个限制。将光标放在下一个空行的开头并保存选择,并在恢复选择时将光标放在上一行的末尾。有办法克服吗??
      猜你喜欢
      • 2012-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-14
      • 2018-06-09
      • 2011-01-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多