js 实现防微信聊天输入框功能

前段儿时间有个需求,实现输入框既能输入文字又能输入图片,当时想到了微信的聊天输入框,于是经过查阅资料,使用div的contenteditable属性,将其值设为true,就可以实现类似输入框的功能,输入文字,而图片也可以通过html的innerHTML解析功能直接回显,详细代码如下:

<template>
  <a-form-model ref="form" :model="form" :label-col="{ span: 5 }" :wrapper-col="{ span: 16 }">
    <a-form-model-item prop="comment">
      <div class="flex flexDiv">
        <div ref="editBox" class="divInput" contenteditable="true" id="editBox"></div>
        <img
          @mouseenter.stop="handleMouseEnter"
          @mouseleave.stop="handleMouseLeave"
          :src="smile"
          width="28"
          class="smileContainer cursorhand"
        />
        <div v-show="showTooltip" ref="popoverContent" @mouseenter="clearCloseTimeout" @mouseleave="handleMouseLeave">
          <div class="triangle"></div>
          <div
            class="flex emojiStyle emojiContainer"
            v-infinite-scroll.stop="handleInfiniteOnLoad"
            :infinite-scroll-disabled="busy"
            :infinite-scroll-distance="10"
          >
            <a-avatar
              v-for="(v, i) in emojiList"
              :key="i"
              :src="v.emojiIcon"
              shape="square"
              class="avatar"
              @click.stop="insertImg(i)"
            />
          </div>
        </div>
      </div>
    </a-form-model-item>
    <div class="bottom-control">
      <a-space>
        <a-button type="primary" @click="submitForm"> 保存 </a-button>
      </a-space>
    </div>
  </a-form-model>
</template>

<script>
import smile from '@/assets/images/smile.png'
import infiniteScroll from 'vue-infinite-scroll'

export default {
  directives: { infiniteScroll },
  data() {
    return {
      smile,
      actived: -1,
      emojiList: [],
      form: {
        comment: ''
      },
      busy: false,
      showTooltip: false,
      pageNum: 0
    }
  },
  methods: {
    handleMouseEnter() {
      this.clearCloseTimeout()
      this.showTooltip = true // 鼠标进入时显示弹出内容
    },
    handleMouseLeave(event) {
      this.closeTimeout = setTimeout(() => {
        this.showTooltip = false
      }, 300) // 鼠标离开时延时300毫秒关闭
    },
    clearCloseTimeout() {
      // 清除已设定的延时关闭,防止弹窗意外关闭
      if (this.closeTimeout) {
        clearTimeout(this.closeTimeout)
        this.closeTimeout = null
      }
    },
    handleInfiniteOnLoad() {
      if (this.emojiList.length == this.total) return
      this.pageNum++
      this.getList()
    },
    // 获取图片数据
    getList() {
      // 这里替换成自己的接口请求
      setTimeout((response) => {
        this.emojiList = this.emojiList.concat(response.data.records)
        this.total = response.data.total
      })
    },
    // 插入按钮
    insertImg(i) {
      this.actived = i
      this.$refs.editBox.focus()
      const imgStr = `<img src="${this.emojiList[i].emojiIcon}" style="width:20px"/>`
      document.execCommand('insertHTML', true, imgStr)
      this.$nextTick(() => {
        const innerHTML = this.$refs.editBox.innerHTML
        this.$refs.editBox.innerHTML = innerHTML.replace('<br>', '')
        if (innerHTML.indexOf('</span>') != -1) {
          const divHtml =
            innerHTML.substring(0, innerHTML.indexOf('<span')) +
            innerHTML.substring(innerHTML.indexOf('</span>') + 7, innerHTML.length)
          this.$refs.editBox.innerHTML = divHtml
        }
        setTimeout(() => {
          this.keepCursorEnd(this.$refs.editBox)
        }, 1)
      })
    },
    keepCursorEnd(obj) {
      // ie11 10 9 firefox safari
      if (window.getSelection) {
        // 解决firefox不获取焦点无法定位问题
        obj.focus()
        // 创建range
        const range = window.getSelection()
        // range 选择obj下所有子内容
        range.selectAllChildren(obj)
        // 光标移至最后
        range.collapseToEnd()
      } else if (document.selection) {
        // ie10 9 8 7 6 5
        // 创建选择对象
        const range = document.selection.createRange()
        // range定位到obj
        range.moveToElementText(obj)
        // 光标移至最后
        range.collapse(false)
        range.select()
      }
    },
    filterImg(val) {
      // 处理评论消息中的表情
      const reg = /\[\!(http.*?)\!\]/g // 带http的捕获
      const reg2 = /\[\!(.*?)\!\]/g // 不带http的捕获
      const str = val.replace(reg, `<img src="$1" style="width:20px">`)
      return str.replace(reg2, `<img src="$1" style="width:20px">`)
    },
    filterStr(str) {
      // 正则表达式匹配文本和 <img> 标签
      const regex = /(<img[^>]*src="([^"]+)"[^>]*>|[^<]+)/g
      let match
      let result = ''
      while ((match = regex.exec(str)) !== null) {
        if (match[1].startsWith('<img')) {
          // 如果是 <img> 标签,提取 src 属性值
          result += `[!${match[2]}!]`
        } else {
          // 否则是纯文本部分
          result += match[0]
        }
      }
      return result
    },
    /** 提交按钮 */
    submitForm: function () {
      this.form.comment = this.filterStr(this.$refs.editBox.innerHTML)
      console.log('输出最终的内容', this.form.comment)
    }
  }
}
</script>
<style lang="less" scoped>
.flexDiv {
  position: relative;
}
.divInput {
  flex: 1;
  overflow: hidden;
  outline: none;
  border: 1px solid #d9d9d9;
  padding-left: 11px;
  padding-right: 36px;
  border-radius: 2px;
}
.divInput:hover {
  border-color: #40a9ff;
}
.divInput:focus {
  border-color: #40a9ff;
  border-right-width: 1px !important;
  outline: 0;
  box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.emojiStyle {
  flex-wrap: wrap;
  max-height: 200px;
  overflow: auto;
  .avatar {
    margin: 0 10px 10px;
    cursor: pointer;
  }
  .avatar:hover {
    border: 2px solid red;
  }
}
.emojiContainer {
  position: absolute;
  top: 51px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 3;
  padding: 14px 16px;
  width: 93.5%;
  right: 0;
  background: #fff;
}
.smileContainer {
  position: absolute;
  right: 8px;
  top: 6.5px;
}
.triangle {
  right: 18px;
  transform: translateX(-50%) rotate(45deg);
  top: 48px;
  border-top-color: #fff;
  border-right-color: transparent;
  border-bottom-color: transparent;
  border-left-color: #fff;
  box-shadow: -2px -2px 5px rgba(0, 0, 0, 0.06);
  position: absolute;
  display: block;
  width: 8.48528137px;
  height: 8.48528137px;
  background: transparent;
  border-style: solid;
  border-width: 4px;
  transform: rotate(45deg);
  z-index: 4;
}
</style>

该功能还有些小缺陷,欢迎大佬们完善。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值