我想知道是否可以在关闭的HTML标签和打开的HTML标签之间设置插入符号。

这是我的HTML:



<div contentEditable="true">
  <div> Hello
    <span style="color: red;"> Foo </span>
    <span style="color: blue;">
      Bar
      <span style="color: green;">
        From Baz
      </span>
    </span>
  </div>
</div>





因此,如果光标位于绿色标签<span style="color: green;">From Baz</span>的末尾(如何将光标位置定位在anchorNode的末尾?),请按向右箭头键,将其移至具有蓝色的父跨度。

即使在绿色范围的开始处对范围变量使用setStart,它也会将光标设置为蓝色范围(Bar)的末尾,然后通过书写将其变为蓝色(预期是在绿色跨度)。

问题是,是否可以控制标签之间的光标位置?

更新资料

1-我需要纯Javascript解决方案。
2-行为也因浏览器而异。因此,它需要一个仅依赖Javascript的解决方案,换句话说,问题将是:“如何控制光标?”

最佳答案

这是使用zero width non-joiner&zwnj;)字符的方法。它解决了问题,但添加了一个额外的角色,该角色不会改变外观,但在使用箭头左右移动插入符号时有问题,希望对您有所帮助。



<div contentEditable="true">
  <div> Hello
    <span style="color: red;">Foo </span>
    <span style="color: blue;">&zwnj;Bar
      <span style="color: green;">&zwnj;From Baz</span>
    </span>
  </div>
</div>







编辑

这是没有上述问题的另一种方法:



<div contentEditable="true">
  <div> Hello<span style="color: red;">
    Foo</span><span style="color: blue;">
    Bar<span style="color: green;">
    From Baz</span>
    </span>
  </div>
</div>





编辑2

您还可以使用Javascript执行以下操作:



function RemoveSpaces(myQuery, parentNodeName){
  var myElement = document.querySelector(myQuery);
  var iterator = document.createNodeIterator(myElement, NodeFilter.SHOW_TEXT, node => {
    if (node.parentElement.nodeName == parentNodeName) {
      return NodeFilter.FILTER_ACCEPT;
    }
    return NodeFilter.FILTER_REJECT;
  });

  var toBeRemoved = [];
  let next = iterator.nextNode();
  while(next) {
    toBeRemoved.push(next);
    next = iterator.nextNode();
  }

  toBeRemoved.forEach(n => RemoveFunction(n));
}

function RemoveFunction(c){
    if (!c.nodeValue.replace(/\s/g, '').length){
    	c.remove();
    }
    else{
    	c.nodeValue = "\n" + c.nodeValue.trim();
    }
}


RemoveSpaces('div[contenteditable="true"] div', 'DIV');
RemoveSpaces('div[contenteditable="true"] div', 'SPAN');

<div contentEditable="true">
  <div>Hello
    <span style="color: red;"> Foo </span>
    <span style="color: blue;">
      Bar
      <span style="color: green;">
        From Baz
      </span>
    </span>
  </div>
</div>





编辑3

对于注释中提到的问题,我们可以将第一种方法(&zwnj;)与第三种方法(即JS)结合使用。
因此,我们的想法是在每个子跨度之后添加&zwnj;,以便我们可以利用其弱点(这是添加一个不可见的空间,需要一个额外的右箭头)作为其强度,因此,当用户点击末尾的右箭头时,绿色跨度,他将跟随父(蓝色)跨度中的&zwnj;字符。

因此我们生成的HTML应该如下所示:

<div>
Hello<span style="color: red;">
Foo</span>&zwnj;<span style="color: blue;">
Bar<span style="color: green;">
From Baz</span>&zwnj;</span>&zwnj;</div>


这是实现该目标的完整代码:



function RemoveSpaces(myQuery, parentNodeName){
  var myElement = document.querySelector(myQuery);
  var iterator = document.createNodeIterator(myElement, NodeFilter.SHOW_TEXT, node => {
    if (node.parentElement.nodeName == parentNodeName) {
      return NodeFilter.FILTER_ACCEPT;
    }
    return NodeFilter.FILTER_REJECT;
  });

  var toBeRemoved = [];
  let next = iterator.nextNode();
  while(next) {
    toBeRemoved.push(next);
    next = iterator.nextNode();
  }

  toBeRemoved.forEach(n => RemoveFunction(n));
}
function RemoveFunction(c){
  if (!c.nodeValue.replace(/\s/g, '').length){
    c.remove();
  }
  else{
    c.nodeValue = "\n" + c.nodeValue.trim();
  }
}


RemoveSpaces('div[contenteditable="true"] div', 'DIV');
RemoveSpaces('div[contenteditable="true"] div', 'SPAN');

//Added Code

var childrenspans = document.querySelector("div[contenteditable='true']").getElementsByTagName("span");
var newNode = document.createTextNode('\u200c');
childrenspans[childrenspans.length-1].parentNode.insertBefore(newNode, childrenspans[childrenspans.length-1].nextSibling);

<div contentEditable="true">
  <div>Hello
    <span style="color: red;"> Foo </span>
    <span style="color: blue;">
      Bar
      <span style="color: green;">
        From Baz
      </span>
    </span>
  </div>
</div>






  请注意,我们使用的是'\u200c'而不是'&zwnj;',这是由于createTextNode的缘故,我们必须使用它的Unicode,即u200c


编辑4

如评论中所述,Edit 3代码可以工作,但是在绿色跨度之后添加文本之后,第一种方法将具有与以前相同的问题
这意味着在文本中间还有一个额外的&zwnj;

我们可以在用户每次输入内容时跟踪输入,并且可以删除&zwnj;字符(如果它在空格字符旁边)。

它不能完全解决问题,但可以帮助您。
这是代码:



function RemoveSpaces(myQuery, parentNodeName){
  var myElement = document.querySelector(myQuery);
  var iterator = document.createNodeIterator(myElement, NodeFilter.SHOW_TEXT, node => {
    if (node.parentElement.nodeName == parentNodeName) {
      return NodeFilter.FILTER_ACCEPT;
    }
    return NodeFilter.FILTER_REJECT;
  });

  var toBeRemoved = [];
  let next = iterator.nextNode();
  while(next) {
    toBeRemoved.push(next);
    next = iterator.nextNode();
  }

  toBeRemoved.forEach(n => RemoveFunction(n));
}
function RemoveFunction(c){
  if (!c.nodeValue.replace(/\s/g, '').length){
    c.remove();
  }
  else{
    c.nodeValue = "\n" + c.nodeValue.trim();
  }
}


RemoveSpaces('div[contenteditable="true"] div', 'DIV');
RemoveSpaces('div[contenteditable="true"] div', 'SPAN');


var childrenspans = document.querySelector("div[contenteditable='true']").getElementsByTagName("span");
var newNode = document.createTextNode('\u200c');
childrenspans[childrenspans.length-1].parentNode.insertBefore(newNode, childrenspans[childrenspans.length-1].nextSibling);



//Added Code

function saveCaretPosition(context){
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    range.setStart(  context, 0 );
    var len = range.toString().length;

    return function restore(x){
        var pos = getTextNodeAtPosition(context, len);
        selection.removeAllRanges();
        var range = new Range();
        if(x)
          range.setStart(pos.node ,pos.position-1);
        else
          range.setStart(pos.node ,pos.position);
        selection.addRange(range);
    }
}
function getTextNodeAtPosition(root, index){
    var lastNode = null;
    var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT,function next(elem) {
        if(index >= elem.textContent.length){
            index -= elem.textContent.length;
            lastNode = elem;
            return NodeFilter.FILTER_REJECT
        }
        return NodeFilter.FILTER_ACCEPT;
    });
    var c = treeWalker.nextNode();
    return {
        node: c? c: root,
        position: c? index:  0
    };
}

function RemoveCharCodeIfBesideCharacter(str, charCode, besideCharacter){
  function SortNumber(a, b) {return a - b;}
  var indexesToRemove = [];
  for (var i = 0; i < str.length; i++) {
  	var strchar = str.charCodeAt(i);
    if(strchar == charCode && i != 0 && i != str.length-1){
      if(str.charAt(i-1) == besideCharacter || str.charAt(i+1) == besideCharacter)
        indexesToRemove.push(i);
    }
  }
  indexesToRemove.sort(SortNumber);
  var removeCount = 0;
  for(var i=0; i < indexesToRemove.length; i++){
  	str = str.slice(0, indexesToRemove[i]-removeCount) + str.slice(indexesToRemove[i]+1-removeCount);
    removeCount++;
  }
  return str;
}

document.querySelector("div[contenteditable='true']").addEventListener("input", function() {
    var restore = saveCaretPosition(this);
    var before = this.innerHTML;
    this.innerHTML = RemoveCharCodeIfBesideCharacter(this.innerHTML, "8204", " ");
    restore(before != this.innerHTML);
}, false);

<div contentEditable="true">
  <div>Hello
    <span style="color: red;"> Foo </span>
    <span style="color: blue;">
      Bar
      <span style="color: green;">
        From Baz
      </span>
    </span>
  </div>
</div>

08-18 22:43