利用contenteditable属性实现div可编辑,控制插入节点

富文本编辑器或者text是没有办法插入一个自定义的html节点,所以最后决定用div的contenteditable属性,使其变成可编辑的状态,控制光标的位置来插入。

一、html

	<div
        class="ant-input"
        id="edit"
        ref="url"
        contenteditable="true"
        placeholder="请输入链接"
        @blur="changeUrl"
        @paste="HandlePaste"
        v-html="url"
      ></div>
      <button @click="onAddItem">添加标签</button>

二、显示placeholder

因为div本身不具备placeholder的属性,所以我们要通过css来显示

  .ant-input {
    height: 140px; // 设置高度
    overflow-y: auto; // 超多的部分出现滚动条
    &:empty::before { // placeholder设置
      content: attr(placeholder);
      font-size: 14px;
      color: #CCC;
      line-height: 21px;
      padding-top: 20px;
    }
  }

三、逻辑

  • 注册全局的光标变化事件,主要记录用户最后选中的光标位置,在该位置插入自定的标签
  @Prop() value;
  private url: string = '';
  private savedRange = null; // 保留当前选择的范围
  $refs: {
    url: HTMLElement
  }

  mounted() {
    // 注册光标移动事件
    document.addEventListener('selectionchange', this.HandleSelectionChange, false);
    // 因为是组件,接收数据显示
    this.url = this.value;
  }
  • 保留用户最后光标停留处
  // 保留当前选择的范围
  HandleSelectionChange() {
    let sel = window.getSelection && window.getSelection();
    if (sel && sel.rangeCount) {
      this.savedRange = sel.getRangeAt(0);
    }
  }
  • 点击按钮添加自定义标签,input标签type是button类型的,这样删除可以整个删掉
  // 添加参数到链接中
  onAddItem() {
  	let data = '数据';
    let tag = `<input
      class="ant-tag ant-tag-blue"
      type="button"
      value=${data}
    />`;
    this.pasteHtmlAtCaret(tag);
  }
  // 获取光标,插入html
  pasteHtmlAtCaret(html) {
    let sel, range;
    // IE9 and non-IE
    if (window.getSelection) {
      sel = window.getSelection();
      if (sel && sel.rangeCount === 0 && this.savedRange !== null) sel.addRange(this.savedRange); // 保留光标在文字中间插入的最后位置
      if (sel && sel.rangeCount) range = sel.getRangeAt(0);
      if (['', null, undefined].includes(range)) {
        // 如果div没有光标,则在div内容末尾插入
        range = this.keepCursorEnd(true).getRangeAt(0);
      } else {
        const contentRange = document.createRange()
        contentRange.selectNode(this.$refs.url)
        // 对比range,检查光标是否在输入范围内
        const compareStart = range.compareBoundaryPoints(Range.START_TO_START, contentRange)
        const compareEnd = range.compareBoundaryPoints(Range.END_TO_END, contentRange)
        const compare = compareStart !== -1 && compareEnd !== 1
        if (!compare) range = this.keepCursorEnd(true).getRangeAt(0);
      }
      let input = range.createContextualFragment(html);
      let lastNode = input.lastChild; // 记录插入input之后的最后节点位置
      range.insertNode(input)
      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);
    }
  }
   /**
   * 将光标重新定位到内容最后
   * isReturn 是否要将range实例返回
   * */
  keepCursorEnd(isReturn) {
    // const div = document.getElementById('edit');
    if (window.getSelection) {
      // ie11 10 9 firefox safari
      this.$refs.url.focus();
      let sel = window.getSelection(); // 创建range
      sel.selectAllChildren(this.$refs.url); // range 选择obj下所有子内容
      sel.collapseToEnd(); // 光标移至最后
      if (isReturn) return sel;
    } else if (document['selection']) {
      // ie9以下
      let sel = document['selection'].createRange(); // 创建选择对象
      sel.moveToElementText(this.$refs.url); // range定位到编辑器
      sel.collapse(false);// 光标移至最后
      sel.select();
      if (isReturn) return sel;
    }
  }
  • 获取数据,发送到父组件保存
  changeUrl(e) {
  	// 去除用户在输入时,主动输入的换行符
    const url = e.target.innerHTML.toString().replace(/<br>/g, '')
    this.$emit('input', url);
  }
  • 由于编辑框是可以粘贴从其他地方复制来的数据,但是会带着数据之前的样式,所以我们需要去除粘贴时的格式
  // 处理粘贴时消除样式
  HandlePaste(e) {
    e.stopPropagation();
    e.preventDefault();
    var text = ''; var event = (e.originalEvent || e);
    if (event.clipboardData && event.clipboardData.getData) {
      text = event.clipboardData.getData('text/plain');
    } else if (window['clipboardData'] && window['clipboardData'].getData) {
      text = window['clipboardData'].getData('Text');
    }
    if (document.queryCommandSupported('insertText')) {
      document.execCommand('insertText', false, text);
    } else {
      document.execCommand('paste', false, text);
    }
  }

效果图:
在这里插入图片描述

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值