一、技术点引入
前面七篇文章已经讲的差不多了,突然想起针对粘贴事件还没有谈谈解决方案,这篇文章就讲讲如何处理粘贴事件,以及自己对defineCustomElement实现富文本组件的看法。
粘贴事件中我们需要拦截一些粘贴的内容,或者是做一些内容上的替换;这里我以处理粘贴内容中携带的图片为例,探讨一下处理粘贴内容的方式
- 粘贴内容中的图片来源:复制的内容有可能是其他网页,这样图片的展示形式就是
<img src='xxx'/>
的形式;如果复制的内容来自于本地图片,则他就是一个file数组。 - 实现思路:在我们拿到粘贴内容的html后,我们可以直接进行字符串替换,将
<img
替换为<custom-img
将/img>
替换为/custom-img>
。注意 ,这只是一个简单的思路,将会遇到很多bug,但是大致的方向是这样。 - 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的各种坑,会不会采用这种方式会更好呢。