关闭标签和打开标签之间的光标位置

时间:2019-05-24 10:07:16

标签: javascript html

我想知道是否可以在关闭的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的解决方案,换句话说,问题将是:“如何控制光标?”

1 个答案:

答案 0 :(得分:2)

以下是使用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,我们必须使用u200c的Unicode。

编辑4

如评论中所述,“编辑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>