在vue3中实现富文本--利用defineCustomElement来快速实现富文本组件(八)

一、技术点引入

前面七篇文章已经讲的差不多了,突然想起针对粘贴事件还没有谈谈解决方案,这篇文章就讲讲如何处理粘贴事件,以及自己对defineCustomElement实现富文本组件的看法。

粘贴事件中我们需要拦截一些粘贴的内容,或者是做一些内容上的替换;这里我以处理粘贴内容中携带的图片为例,探讨一下处理粘贴内容的方式

  1. 粘贴内容中的图片来源:复制的内容有可能是其他网页,这样图片的展示形式就是<img src='xxx'/>的形式;如果复制的内容来自于本地图片,则他就是一个file数组。
  2. 实现思路:在我们拿到粘贴内容的html后,我们可以直接进行字符串替换,将<img替换为<custom-img/img>替换为/custom-img>注意 ,这只是一个简单的思路,将会遇到很多bug,但是大致的方向是这样。
  3. custom-img自定义元素设计:由于采取直接替换的方式,所以一定要遵守以下原则:custom-img自定义元素传入的props键的名称一定要和img标签的属性名一致。例如:img标签有src属性,则custom-img元素也必须取这个名字,这样就能实现无感替换。

二、代码实现

首先是CustomImg.ce.vue自定义元素:

js 复制代码
<script setup lang="ts">
import { ref } from 'vue'

const props = withDefaults(
  defineProps<{ src?: string; file?: File; title?: string; alt?: string }>(),
  {
    src: '',
    title: '',
    alt: ''
  }
)

// 处理src和file两种形式的入参
const url = ref('')
if (props.src) {
  url.value = props.src
} else if (props.file) {
  try {
    const reader = new FileReader()
    reader.readAsDataURL(props.file)
    reader.onload = function () {
      if (reader.result) {
        url.value = reader.result
      }
    }
  } catch (e) {
    console.log(e)
  }
}

// 这个图片上传的函数就省略了
const uploadImg = ()=>{}

defineExpose({
  src: url,
  title: props.title,
  alt: props.alt
})
</script>
<template>
  <img
    class="image"
    crossorigin="anonymous"
    referrerpolicy="no-referrer"
    v-if="url"
    :src="url"
    :title="title"
    :alt="alt"
  />
</template>
<script lang="ts">
export default {
  name: 'custom-img',
  type: 'block'
}
</script>
<style lang="less">
.image {
  display: block;
  margin: 0 auto;
}
</style>

这个元素可以接受src或者file类型的图片类型,并做自动转换,上传接口这里就省略了,读者可以自行实现。

下面是监听粘贴事件,在RichText类的initRootNode方法中,加入监听: this.rootElement.addEventListener('paste', this.processingPasteEvt.bind(this)) 其中bind主要改变this的指向为当前RichText;

然后在RichText类中加入processingPasteEvt方法:

js 复制代码
  private processingPasteEvt(evt: ClipboardEvent) {
    if (!(evt.clipboardData && evt.clipboardData.items)) {
      return
    }

    const html = evt.clipboardData.getData('text/html')
    const text = evt.clipboardData.getData('text/plain')

    const range = this.getCursorRange()
    range.deleteContents()

    // 如果有html,则代表他是复制的页面,图片展示必定为标签的形式
    if (html) {
      const processedHtml = html.replace(/<img/g, '<custom-img').replace(/img>/g, 'custom-img>')
      // 这一句是判断html有没有变化,没有变化则代表没有图片,不用做后续操作,直接放行
      if (processedHtml !== html) {
        // 有图片则阻止默认操作
        evt.preventDefault()
        // 手动将html字符串转换为真实dom
        const parser = new DOMParser()
        const htmlDom = parser.parseFromString(processedHtml, 'text/html')
        const fragmentBody = htmlDom.querySelector('body')
        // 这里是只拿到真实dom的body内的元素,外层不需要
        if (fragmentBody) {
          const fragment = document.createDocumentFragment()
          while (fragmentBody.firstChild) {
            fragment.appendChild(fragmentBody.firstChild)
          }
          range.insertNode(fragment)
        } else {
          range.insertNode(htmlDom)
        }
      }
    } else if (!text) {
      // 这里是处理拿到的为文件数组
      evt.preventDefault()
      for (let i = 0, len = evt.clipboardData.items.length; i < len; i++) {
        const item = evt.clipboardData.items[i]
        // 判断是不是图片
        if (item.kind === 'file' && item.type.match('^image/')) {
          const pasteFile = item.getAsFile()
          // pasteFile就是获取到的文件
          const img = getCustomComponents()['custom-img']
          const imgInstance = new img.Constructor({ file: pasteFile })
          console.log(imgInstance)
          range.insertNode(imgInstance)
        }
      }
    }
  }

这个方法就能够处理所有的图片粘贴需求。

三、总结

这篇文章主要讲了在粘贴事件中如何用自定义元素来替换默认元素,最核心的点就是将自定义元素的入参设置的和默认元素的属性一模一样,这样就能单纯替换元素名而不用修改其他内容就能实现元素替换的需求。当然,这也是我的一种实现思路,可以参考借鉴,不一定是最好的。

四、写在最后

经过八篇文章的填坑,我们可以感受到在富文本中使用webcomponent目前还有许多的不足,许多坑点还不能完美的解决;同样对比将vue组件挂载到dom节点上,vue3中的createApp方法是将一个vue组件实例直接挂载到dom上,并且还能够避开webcomponent的各种坑,会不会采用这种方式会更好呢。

相关推荐
逐·風1 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫2 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦3 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子3 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山3 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享4 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf6 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨6 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL6 小时前
npm入门教程1:npm简介
前端·npm·node.js
小白白一枚1117 小时前
css实现div被图片撑开
前端·css