javascript safari浏览器使用document.execCommend()中文兼容bug

新手上路,请多包涵

由于需求需要编写一个简易编辑器,仅仅只有加粗,倾斜和下划线,要求不能使用第三方库,所以就使用了div的contenteditable="true"属性加document.execCommend()的api来自己编写,
编写后发现了safari的中文兼容bug,查看了网上许多简易编辑的案例,发现都有这个bug,
即:safari 英文输入切换正常image.png 中文输入模式下,点击取消加粗,依然是加粗样式image.pngimage.png

<template>
  <div class="edit-container">
    <header class="upload-header">
      <div class="left" @click="returnPage">
        <i></i>
      </div>
      <div class="center">编辑</div>
      <div class="right">保存</div>
    </header>
    <div class="title-edit">
      <input type="text" v-model="htmlObj.title" />
    </div>
    <div
      v-html="htmlObj.content"
      spellcheck="false"
      class="content-edit"
      contenteditable="true"
      @input="editChange"
      ref="editor"
      @paste="pasteText"
      @click="onClick($event)"
    >
      <!-- <p>{{htmlObj.content}}</p> -->
    </div>
    <!-- <new-edit></new-edit> -->
    <div class="edit-tool">
      <div class="left">
        <input
          class="bold"
          :class="{ active: iconList[0].choose }"
          value="B"
          type="button"
          @click="iconClick($event, 'bold', 'style')"
        />
        <input
          class="bias"
          :class="{ active: iconList[1].choose }"
          value="I"
          type="button"
          @click="iconClick($event, 'italic', 'style')"
        />
        <input
          class="underline"
          :class="{ active: iconList[2].choose }"
          value="U"
          type="button"
          @click="iconClick($event, 'underline', 'style')"
        />
      </div>
      <div class="right">
        <div>
          <button
            style="background:rgba(110,110,110,1);"
            :class="{ active: iconList[3].choose }"
            @click="iconClick($event, 'foreColor1', 'style')"
          ></button>
        </div>
        <div>
          <button
            style="background:rgba(241,89,108,1);"
            :class="{ active: iconList[4].choose }"
            @click="iconClick($event, 'foreColor2', 'style')"
          ></button>
        </div>
        <div>
          <button
            style="background:rgba(241,223,3,1);"
            :class="{ active: iconList[5].choose }"
            @click="iconClick($event, 'foreColor3', 'style')"
          ></button>
        </div>
        <div>
          <button
            style="background:rgba(80,227,194,1);"
            :class="{ active: iconList[6].choose }"
            @click="iconClick($event, 'foreColor4', 'style')"
          ></button>
        </div>
      </div>
    </div>
  </div>
</template>

此处为可编辑div

export default {
  name: "EditModule",
  components: {
    NewEdit
  },
  data() {
    return {
      innerText: "",
      htmlObj: {
        title: "",
        content: ""
      },
      selectedRange: "",
      iconList: [
        {
          // hover名字
          name: "粗体",
          // 点击事件处理
          type: "bold",
          // 是否被选中
          canChoose: true,
          choose: false
        },
        {
          name: "斜体",
          type: "italic",
          canChoose: true,
          choose: false
        },
        {
          name: "下划线",
          type: "underline",
          canChoose: true,
          choose: false
        },
        {
          name: "字体颜色",
          type: "foreColor1",
          canChoose: true,
          drop: true,
          choose: false
        },
        {
          name: "字体颜色",
          type: "foreColor2",
          drop: true,
          canChoose: true,
          choose: false
        },
        {
          name: "字体颜色",
          type: "foreColor3",
          drop: true,
          canChoose: true,
          choose: false
        },
        {
          name: "字体颜色",
          type: "foreColor4",
          drop: true,
          canChoose: true,
          choose: false
        }
      ],
      activeIconList: []
    };
  },
  mounted() {
    window.localStorage.setItem(
      "moduleList",
      '[{"type":1,"title":"12","content":"<p>11111</p>"}]'
    );
    this.htmlObj = JSON.parse(window.localStorage.getItem("moduleList"))[0];
  },
  methods: {
    onClick(event) {
      let that = this;
      console.log(event);
      if(event.target.tagName=='DIV'){
        return false;
      }
      this.activeIconList = [];
      let target = event.target;
      this.changeTagStyle(target);
      let newArr = this.activeIconList.filter(function(x, index, self) {
        return self.indexOf(x) === index;
      });
      for (let i = 0; i < this.iconList.length; i++) {
        if (newArr[i] === "B") {
          this.iconList[0].choose = true;

        } else if (newArr[i] === "U") {
          this.iconList[2].choose = true;
        } else if (newArr[i] === "I") {
          this.iconList[1].choose = true;
        } else {
          this.iconList[i].choose = false;
        }
      }
    },
    isToolTag(el) {
      if (el.tagName == "B") return true;
      else return false;
    },
    changeTagStyle(el) {
      if (
        el.tagName == "B" ||
        el.tagName == "I" ||
        el.tagName == "U" ||
        el.tagName == "FONT"
      ) {
        this.activeIconList.push(el.tagName);
        console.log(el.parentNode.tagName);
      } else if (el.tagName == "DIV") {
        return;
      }
      this.changeTagStyle(el.parentNode);
    },
    changeStyle(type) {
      switch (type) {
        case "bold":
          console.log("blod");
          console.log(this.iconList[0].choose);
          if(window.navigator.userAgent.indexOf('AppleWebKit')>-1 && this.iconList[0].choose === true){
              console.log('取消选中 光标后移')
              let sel = document.getSelection();
              console.log(sel.anchorNode.nodeType);//文本节点为3,元素节点为1
              console.log(sel.anchorNode.parentNode.parentNode);
              // window.getSelection().removeAllRanges();
              // console.log(sel);
              this.keepLastIndex(sel.anchorNode.parentNode.parentNode);
              // let cursorPos = selection.anchorOffset;
              // let oldContent = selection.sel.nodeValue;
              // let newContent =
              // oldContent.substring(0, cursorPos) +
              //   toInsert +
              // oldContent.substring(cursorPos);
              // selection.anchorNode.nodeValue = newContent;
              // let range=sel.getRangeAt(0);
              // range.setStart(range.startContainer,3);
              // console.log(sel);
              // console.log(sel.getRangeAt(0));
              // console.log(sel.focusOffset);

          }
          document.execCommand("bold", false);
          // document.execCommand("insertHtml", 'a',false);
          break;
        case "underline":
        if(this.iconList[2].choose === false){
          document.execCommand("underline", false);
        }else{
          document.execCommand("removeFormat", false);
        }
          // document.execCommand("underline", false);
          break;
        case "italic":
          document.execCommand("italic", false);
          break;
        case "foreColor1":
          document.execCommand("foreColor", 0, "rgba(110,110,110,1)");
          break;
        case "foreColor2":
          document.execCommand("foreColor", 0, "rgba(241,89,108,1)");
          break;
        case "foreColor3":
          document.execCommand("foreColor", 0, "rgba(241,223,3,1)");
          break;
        case "foreColor4":
          document.execCommand("foreColor", 0, "rgba(80,227,194,1)");
          break;
        default:
          console.log("none");
      }
    },
    // insertAfter(newNode, curNode) {
    //   console.log(curNode.parentNode);
    //   curNode.parentNode.insertBefore(newNode, curNode.nextElementSibling);
    // },
    iconClick(event, type, dropType) {
      event.preventDefault();
      console.log(event);
      this.$refs.editor.focus();
      // let $el=this.$refs.editor;
      // this.keepLastIndex($el);
      this.selectedRange = this.getSelect();
      if (event.target.classList.contains("active")) {
        //解决safari中文光标位置
        //获取光标所在位置的节点,
        console.log(
          window.getSelection().getRangeAt(0).startContainer.parentElement
        );
        let el = window.getSelection().getRangeAt(0).startContainer.parentElement;
        // let span=document.createElement('span');
        // this.insertAfter(span,el);
        // console.log(el);
        // el.focus();
        // var selection = window.getSelection();
        // let span = document.createElement("span");
        // let range = document.createRange();
        // range.collapse(false);
        // range.insertNode(span);
        // selection.removeAllRanges(); /*清空所有Range对象*/
        // range.setStart(
        //   span,
        //   0
        // );
        // range.setEnd(
        //    span,
        //     1
        // );
        // selection.addRange(range);
        // this.placeCaretAtEnd(span);
      }
      // 恢复光标
      // this.restoreSelection();
      // 恢复光标
      // this.restoreSelection();
      // 修改所选区域的样式
      this.changeStyle(type);
      this.$nextTick(() => {
        // if (dropType) {//下拉菜单的type
        //   type = dropType
        // }
        //改变当前元素的样式
        let sourceArr = JSON.parse(JSON.stringify(this.iconList));
        let arr = sourceArr.map((val, index) => {
          if (type === val.type && val.canChoose) {
            val.choose = val.choose ? false : true;
          }
          // else {
          //   if (val.drop) {
          //     val.choose = false;
          //   }
          // }
          return val;
        });

        // if (type === 'clear') {
        //   var a = this.getSelect()
        //   if (a.startOffset === a.endOffset) {
        //     document.execCommand('insertHTML', false, '&nbsp')
        //     // return false
        //   }
        //   arr = arr.map((val, index) => {
        //     val.choose = false
        //     return val
        //   })
        // }
        this.iconList = arr;
      });
    },
    //获取选中
    getSelect() {
      if (window.getSelection) {
        /*主流的浏览器,包括chrome、Mozilla、Safari*/
        var sel = window.getSelection();
        console.log(sel.getRangeAt(0));
        if (sel.rangeCount > 0) {
          return sel.getRangeAt(0);
        }
      } else if (document.selection) {
        /*IE下的处理*/
        return document.selection.createRange();
      }
      return null;
    },
    placeCaretAtEnd(el) {
      //传入光标要去的jq节点对象
      el.focus();
      if (
        typeof window.getSelection != "undefined" &&
        typeof document.createRange != "undefined"
      ) {
        var range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(false);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      } else if (typeof document.body.createTextRange != "undefined") {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(false);
        textRange.select();
      }
    },
    restoreSelection() {
      var selection = window.getSelection();
      console.log(this.selectedRange);
      let range = document.createRange();
      if (this.selectedRange) {
        try {
          selection.removeAllRanges(); /*清空所有Range对象*/
          range.setStart(
            this.selectedRange.startContainer,
            this.selectedRange.startOffset
          );
          range.setEnd(
            this.selectedRange.endContainer,
            this.selectedRange.endOffset
          );
        } catch (ex) {
          /*IE*/
          document.body.createTextRange().select();
          document.selection.empty();
        }
        /*恢复保存的范围*/
        selection.addRange(this.selectedRange);
      }
    },
    returnPage() {
      this.$router.go(-1);
    },
    editChange(){
      // console.log(this.$refs.editor.children);
      let that = this;
      if (this.$refs.editor.children.length == 0) {
        this.$refs.editor.value = `<p></p>`;
        // setTimeout(() => {
        //   that.keepLastIndex(e.target);
        // }, 5);
      }

    },
    //获取光标
    getCaret() {},
    //设置光标
    setCaret() {},
    //保存光标
    saveCaret() {},
    //插入节点
    insertHtmlAtCaret(html) {
      let sel, range;
      if (window.getSelection) {
        // IE9 and non-IE
        sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          range = sel.getRangeAt(0);

          range.deleteContents();

          // Range.createContextualFragment() would be useful here but is

          // non-standard and not supported in all browsers (IE9, for one)

          let el = document.createElement("div");

          el.innerHTML = html;

          let frag = document.createDocumentFragment(),
            node,
            lastNode;

          while ((node = el.firstChild)) {
            lastNode = frag.appendChild(node);
          }

          range.insertNode(frag);

          // Preserve the selection

          if (lastNode) {
            range = range.cloneRange();

            range.setStartAfter(lastNode);

            range.collapse(true);

            sel.removeAllRanges();

            sel.addRange(range);
          }
        }
      } else if (document.selection && document.selection.type != "Control") {
        // IE < 9
        document.selection.createRange().pasteHTML(html);
      }
    },
    //光标定位在末尾
    keepLastIndex(obj) {
      if (window.getSelection) {
        //ie11 10 9 ff safari
        obj.focus(); //解决ff不获取焦点无法定位问题
        var range = window.getSelection(); //创建range
        range.selectAllChildren(obj); //range 选择obj下所有子内容
        range.collapseToEnd(); //光标移至最后
      } else if (document.selection) {
        //ie10 9 8 7 6 5
        var range = document.selection.createRange(); //创建选择对象
        //var range = document.body.createTextRange();
        range.moveToElementText(obj); //range定位到obj
        range.collapse(false); //光标移至最后
        range.select();
      }
    },
    //粘贴事件处理
    async pasteText(e) {
      e.stopPropagation();
      e.preventDefault();
      let text;
      if (navigator.clipboard) {
        text = await navigator.clipboard.readText();
      } else {
        text = e.clipboardData.getData("text/plain");
      }
      let self = this;

      // 在等待一段时间后,在当前光标位置,粘贴处理后的文本
      setTimeout(function() {
        let selection = document.getSelection();
        let cursorPos = selection.anchorOffset;
        let oldContent = selection.anchorNode.nodeValue;
        // 通过 Dom 去除所有样式
        // let oDiv = document.createElement("div");
        // oDiv.innerHTML = pasteData;
        let toInsert = text;
        let newContent =
          oldContent.substring(0, cursorPos) +
          toInsert +
          oldContent.substring(cursorPos);
        selection.anchorNode.nodeValue = newContent;
        // if(window.navigator.userAgent.indexOf('AppleWebKit')>-1){
        let rag = document.createRange();
        rag.selectNodeContents(selection.anchorNode); //必须传node
        rag.collapse(false);
        let sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(rag);

        // }
        // console.log(selection);
        // let range=selection.getRangeAt(0);
        // range.setStart(range.startContainer,parseInt(cursorPos+toInsert.length));
        // console.log(range);
      }, 200);
      return false;
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.content-edit i {
  font-style: italic !important;
}
.content-edit b {
  font-weight: bold !important;
}
.upload-header {
  width: 100%;
  box-sizing: border-box;
  padding: 43px 58px 31px 58px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.upload-header div {
  width: 156px;
  font-size: 52px;
}
.upload-header i {
  width: 37px;
  height: 69px;
  display: block;
  background: url("../../static/fanhui.svg") no-repeat;
  background-size: 100%;
}
.upload-header .center {
  text-align: center;
}
.upload-header .right {
  text-align: right;
}
.title-edit {
  padding: 54px 58px 48px 58px;
  box-sizing: border-box;
  border-bottom: 1px solid rgba(216, 216, 216, 1);
  font-size: 42px;
  font-weight: 600;
  color: rgba(54, 54, 54, 1);
}
.title-edit input {
  font-weight: 600;
  color: rgba(54, 54, 54, 1);
  width: 100%;
  border: none;
}
.content-edit {
  height: 718px;
  padding: 48px 58px;
  box-sizing: border-box;
  overflow-y: scroll;
  font-size: 42px;
  color: rgba(73, 73, 73, 1);
  -webkit-user-select:text;
}
.content-edit * {
  font-size: 42px;
  color: rgba(73, 73, 73, 1);
}
.edit-tool {
  padding: 0 58px;
  box-sizing: border-box;
  width: 100%;
  height: 144px;
  display: flex;
  justify-content: space-between;
  border: 1px solid rgba(218, 218, 218, 1);
  border-left: none;
  border-right: none;
}
.edit-tool .left .active {
  color: blue;
}
.edit-tool .right .active {
  border: 1px solid #c8c8c8;
}
.edit-tool .left {
  height: 144px;
  display: flex;
  align-items: center;
}
.edit-tool .right > div {
  float: left;
  overflow: hidden;
  margin-top: 15px;
}
.edit-tool .left input {
  display: block;
  width: 40px;
  padding: 14px 55px;
  border: none;
  font-size: 64px;
  background: none;
}
.edit-tool .left input:nth-child(1) {
  padding: 14px 55px;
  padding-left: 14px;
}
.underline {
  text-decoration: underline;
}
.edit-tool .right button {
  padding: 0;
  display: block;
  overflow: hidden;
  width: 60px;
  height: 60px;
  border-radius: 100% 100% 100% 100%;
  -webkit-appearance: none;
  margin: 24px 24px;
  border: none;
}
.edit-tool .right .active {
  border: 10px solid #c8c8c8;
}
</style>
阅读 2.1k
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题