div可编辑,插入元素获取光标,getSelection()对象操作

本身在公司中碰到一个需求是点击按钮,在输入框中展示出按钮的文字。

 查阅网上资料,发现在这方面给出的答案很少,要么就是和我的需求不同,我是用不了,要么就是写的太过复杂,很难优化,因为我看的也是云里雾里的。

所以通过对网上的其他资料研究,自己弄了个汇总的,帮助自己以后再碰到这种需求能够更快的解决。

这里用到的就是一个选区对象和区域对象。window.getSelection()获取选区对象。

Selection 对象表示用户选择的文本范围或插入符号的当前位置。它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。要获取用于检查或修改的 Selection 对象,请调用 window.getSelection()。包括了用户选中的文明范围或光标位置

还有一个区域对象range,window.getSelection().getRangeAt(0),编辑区中的光标位置操作更多还是在这个对象中实现,这是选区中我们选择的范围对象。

如果一上来对这两个对象没有了解的话,还是不建议接着往下看,因为我讲的不是很细,并没有对这两个对象做一个全面的介绍,我的更多细节会在代码中体现。

如果是对这两个对象有了解的话,可以接着往下看,因为我在代码中给了更多的解释,相较于其他的答案更加直观易懂,写的不算很复杂。

<body>
  <!-- 实现一个功能:div变为可编辑,可向编辑框中插入元素和自定义文本,并且可以指定光标位置 -->
  <div class="edit" contenteditable="true" oninput="editInput(event)"></div>
  <!-- 该按钮指定向编辑框中插入元素 -->
  <button class="nodeButton">向编辑框中插入元素</button>
  <br>
  <input type="text" placeholder="请输入文本">
  <!-- 该按钮指定向编辑框中插入自定义文本 -->
  <button class="textButton">向编辑框中输入文本</button>
  <script>
    const div = document.getElementsByClassName("edit")[0]
    const body = document.getElementsByTagName("body")[0]
    const nodeButton = document.getElementsByClassName("nodeButton")[0]
    const textButton = document.getElementsByClassName("textButton")[0]
    const input = document.getElementsByTagName("input")[0]
    const creDOM = document.createElement("div")
    creDOM.innerText = `${div.innerText.length}/100`
    body.appendChild(creDOM)

    // 这一个方法是记录输入字符数的,不需要关心
    function editInput(e) {
      creDOM.innerText = `${div.innerText.length}/100`
    }

    let range = ""
    div.addEventListener("blur", () => {
      // 这一步是保留住edit编辑框中的选区的范围对象。否则失焦后,getSelection()方法返回的选区对象已经不再是编辑框了,已经获取不到编辑框中的范围对象了。
      range = window.getSelection().getRangeAt(0)
    })

    nodeButton.addEventListener("click", () => {
      const span = document.createElement("span")
      span.innerText = "我是一个span元素"
      // 如果在页面刷新再点击编辑框之前就点击了按钮,此时range中并没有选区范围对象
      if (range === "") {
        let selection = window.getSelection()
        selection.selectAllChildren(div) // selectAllChildren把指定元素的所有子元素设为选中区域,并取消之前的选中区域。不包括node节点本身。
        /*
          Selection.collapseToEnd() 方法的作用是取消当前选区,并把光标定位在原选区的最末尾处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。
          以上selectAllChildren方法,将div中子节点全部选中,collapseToEnd方法将选中区域取消,并且将光标定位到原区的末尾。
        */
        selection.collapseToEnd()
        range = window.getSelection().getRangeAt(0) // 无论哪一步都需要保存当前编辑框的选区对象
      }
      let sel = window.getSelection()
      range.insertNode(span) // insertNode方法,在range选区开头插入一个节点
      /*
        removeAllRanges方法:删除之前的所有选区。
        这一步的意义:因为当我们点击其他区域时,选区对象已经改变,不再是编辑框中的选区对象,这时候我们接下来的操作都不会符合我们想象中的样子
      */
      sel.removeAllRanges()
      sel.addRange(range) // 这一步就是添加当前区域对象到选区对象中,所以选区对象会再次指向编辑框中的选区,不会出现别的错误操作。
      sel.collapseToEnd()
    })


    // 只要知道如何在div编辑框中插入元素,那么文本节点也是同理的,对照上面的代码修改一点就可以完成需求
    textButton.addEventListener("click", () => {
      // 第一步获取input框中的内容,将其中的内容转为文本节点插入区域对象中
      let inputText = input.value
      const text = document.createTextNode(inputText)
      // 如果在页面刷新再点击编辑框之前就点击了按钮,此时range中并没有选区范围对象
      if (range === "") {
        let selection = window.getSelection()
        selection.selectAllChildren(div) // selectAllChildren把指定元素的所有子元素设为选中区域,并取消之前的选中区域。不包括node节点本身。
        /*
          Selection.collapseToEnd() 方法的作用是取消当前选区,并把光标定位在原选区的最末尾处,如果此时光标所处的位置是可编辑的,且它获得了焦点,则光标会在原地闪烁。
          以上selectAllChildren方法,将div中子节点全部选中,collapseToEnd方法将选中区域取消,并且将光标定位到原区的末尾。
        */
        selection.collapseToEnd()
        range = window.getSelection().getRangeAt(0) // 无论哪一步都需要保存当前编辑框的选区对象
      }
      let sel = window.getSelection()
      range.insertNode(text) // insertNode方法,在range选区开头插入一个节点
      /*
        removeAllRanges方法:删除之前的所有选区。
        这一步的意义:因为当我们点击其他区域时,选区对象已经改变,不再是编辑框中的选区对象,这时候我们接下来的操作都不会符合我们想象中的样子
      */
      sel.removeAllRanges()
      /*
        // 这一步就是添加当前区域对象到选区对象中,所以选区对象会再次指向编辑框中的选区,不会出现别的错误操作。 
         选区对象的指向如果为空,那么它将会由区域对象指定,区域对象只想哪里那么选区对象就指向哪里。这里选区对象为空了,添加编辑框中的区域对象,所以选区就指向了编辑框了。 (这里是我的猜想,我并没有找到直接证据证明是这样的,但是目前的现象好像确实如此)
      */
      sel.addRange(range)
      sel.collapseToEnd()
    })

效果图: