相关文章推荐
忐忑的登山鞋  ·  Springboot ...·  6 月前    · 

数据是一串数组,要把数组中的内容按照如上图所示展示,同时像textArea一样编辑,编辑后的结果以数组的形式给后端。

  • 给多行p标签添加contentEditable='true'属性,实现对应界面的展示
  • 键盘事件的点击的操作,实现对光标以及数据的处理:如点击删除、上、下、enter等按键
  • 实现(此处只展示部分关键代码)

    1.拿到对应的数据并渲染到界面上,react中需要suppressContentEditableWarning='true'排除警告,关键代码如下所示:

    this.state = {
      wordsList: [
        '好无聊啊,我们玩点游戏吧',
        '嗯好啊,玩什么呢?',
        '我夸你一句,你夸我一句怎么样?',
        '可以啊!你先说',
        '你真漂亮!',
        '你眼光真好!',
        '!!!',
    {this.state.wordsList.map((value, index) => {
        return (
              {index}
            </span>
              id={index}
              contentEditable="true"
              suppressContentEditableWarning="true"
              onKeyDown={(e) => {
                this.onKeyDown(e);
              onKeyUp={(e) => {
                this.onKeyUp(e);
              onMouseDown={() => {
                this.setIndex(index);
              onClick={(e) => {
                this.onClick(e);
              {value}
            </div>
          </div>
    

    2.当键盘点击时,根据keycode的值判断点击的哪个按键并处理数据和光标。
    点击某一行文本时,记录当前点击时光标所在位置(为后续键盘处理上下按键时所需要的)
    比如:当我点击到第四行的‘先’和‘说’中间时
    再去按两次键盘‘下’方向键时,若该行文字长度小于光标所在位置,则光标处于最末尾,若该行文本内容大于所记录的光标位置,则光标处于光标所记录的位置。

      onClick(e) {
        let sel = window.getSelection(); // 获取光标
        let range = sel.getRangeAt(0);
        this.setState({
          recordIndex: range.startOffset
    

    当点击上下(键盘码:38、40)按键时,需要获取上/下行dom元素,创建range范围对象,上/下行文本长度与当前光标所在位置进行比较,并设置光标所在行和位置

    if (e.keyCode == 38) {
      e.preventDefault(); // 阻止向上箭头按键的默认事件
      this.setCursor(index - 1)
    } else if (e.keyCode == 40) {
      e.preventDefault(); // 阻止向上箭头按键的默认事件
      this.setCursor(index + 1)
    setCursor(domIndex) {
        let txtFocus = document.getElementById(domIndex); // 获取上/下行id的dom,并判断是否存在
        if (!txtFocus) {
          return
        const range = document.createRange()
        // 上/下行文本是否小于当前光标所在位置,并设置光标位置
        if (txtFocus.innerText.length < this.state.recordIndex) {
          range.setStart(txtFocus.firstChild, txtFocus.innerText.length)
          range.setEnd(txtFocus.firstChild, txtFocus.innerText.length)
        } else {
          range.setStart(txtFocus.firstChild, this.state.recordIndex)
          range.setEnd(txtFocus.firstChild, this.state.recordIndex)
        const sel = window.getSelection()
        sel.removeAllRanges()
        sel.addRange(range)
        this.setState({
          // 更新index的指向第几行
          index: domIndex,
    

    同理,当点击左/右键(键盘码:37、39)时,需要设置对应光标所在位置。此时需要特别留意光标在该行首尾时,再次点击左/右按键需要换行

    当点击enter按键时(键盘码:13),需要判断光标位置是否在这行文字的最末尾,若是在最末尾,则需在后面增加一行,若在改行文本中间,需要把光标后面的内容放到下一行,并处理光标。部分代码如下

    let sel = window.getSelection(); // 获取光标
    let range = document.createRange();
    range = sel.getRangeAt(0);
    if (e.keyCode == 13) {
      // 对enter按键进行处理,enter按键码:13
      e.preventDefault(); // 阻止enter按键的默认事件
      if (range.startOffset < innerText.length) {
        // 判断光标位置是否在这行文字的最末尾
        wordsList.splice(nextIndex, 0, innerText.substring(range.startOffset));
        wordsList[index] = innerText.substring(0, range.startOffset);
      } else {
        wordsList.splice(nextIndex, 0, ""); // 向wordsList数组中添加一项
      let txtFocus = document.getElementById(nextIndex); // 获取id下一行的dom
      txtFocus.focus(); // 光标显示在下一行,聚焦的作用
      this.forceUpdate(); // state修改,强制刷新页面数据
      this.setState({
        // 更新index的指向位置
        index: nextIndex,
    

    当点击删除按键(按键码:8)时,需要判断当前光标所在位置是否为行首,若为行首,则需要对改行内容进行换行处理,若不在行首则删除对应的文字并处理光标,切勿照搬照抄

    if (e.keyCode == 8) {
          // 对backspace按键进行处理,按键码:8
          if (range.startOffset == 0) {
            e.preventDefault(); // 阻止enter按键的默认事件
            if (innerText.length == 0) {
              // 如果这一行文本的内容为空时
              wordsList.splice(index, 1);
              // txtFocus.focus(); // 光标显示在下一行,聚焦的作用
              // 处理光标问题
              if (window.getSelection) { //ie11 10 9 ff safari
                txtFocus.focus(); //解决ff不获取焦点无法定位问题
                let range1 = window.getSelection(); //创建range
                range1.selectAllChildren(txtFocus); //range 选择obj下所有子内容
                // range1.modify('extend','right',2)
                range1.collapseToEnd(); //光标移至最后
              } else if (document.selection) {//ie10 9 8 7 6 5
                let range1 = document.selection.createRange(); //创建选择对象
                range1.moveToElementText(range1); //range定位到obj
                range1.collapse(false); //光标移至最后
                range1.select();
              this.forceUpdate(); // state修改,强制刷新页面数据
              this.setState({
                // 更新index的指向位置
                index: prIndex,
            } else {
              innerText1 = innerText1.concat(innerText);
              // 如果这一行文本的内容不为空时
              wordsList.splice(index, 1);
              this.forceUpdate(); // state修改,强制刷新页面数据
              wordsList[prIndex] = innerText1;
              this.setState({
                // 更新index的指向位置
                index: prIndex,
              }, () => {
                // 处理光标问题, 此处必须在设置state以后方可
                range.setStart(txtFocus.firstChild, innerText1.length - innerText.length)
                range.setEnd(txtFocus.firstChild, innerText1.length - innerText.length)
                sel.removeAllRanges()
                sel.addRange(range)
    

    3.其他按键处理:后续可以对del,pgUp,pgDn,ctrl+s,ctrl+v等键盘事件进行相应处理;ctrl+s这种多键盘事件,可以根据键盘事件中event的ctrlKey来判断是否点击了ctrl。

    有些富文本编辑器的光标采用自定义光标的方式,比如掘金写文章这里。这些等待后续研究,若哪位大佬有更好的见解,也请不吝赐教!

    分类:
    前端
    标签: