likes
comments
collection
share

vue3 把富文本中的图片替换为 el-image

作者站长头像
站长
· 阅读数 62

有时候我们需要对富文本中的图片、视频或者其他元素添加更加丰富的功能,比如在图片上增加el-image 的预览功能。

你可能会想去操作 DOM, 然后为图片写一些自己的方法来实现预览功能,这个还不算太难,但是如果想把 html video 标签替换为自己封装的 VideoPlayer 组件呢? 这好像又麻烦了一点。

其实,vue 提供了渲染函数,通过h()、render()可以用少量的代码,非常方便的把元素替换为 Element Plus 的组件,或者是自己封装的组件。

在此,我们尝试把 img 元素 替换为 el-image

渲染函数

在开始之前,需要了解 vue3 的 渲染函数 cn.vuejs.org/guide/extra…

Vue 提供了一个 h() 函数用于创建 vnodes,它有点像操作 DOM 的 createElement,不过h()函数创建的是 vnodes,虚拟 DOM 最终通过渲染器构建成真实的 DOM 树。

import { h } from 'vue' 
const vnode = {
  type: 'div',
  props: {
    id: 'hello'
  },
  children: [
    /* 更多 vnode */
  ]
}

如果我们用JS 去操作真实 DOM,要写的东西还挺多(回忆一下 jQuery),但是用h()函数,在渲染 Vue 组件上这件事上,几句话就可以实现。不管是什么类型的文件,只要从中导入的是有效的 Vue 组件,h 就能正常运作。

render()函数,就是帮我们把 h()函数创建的 vnodes 渲染到 DOM 上的关键。

具体实现

  1. 找到指定范围内的 img 元素们
  2. 遍历 img 节点数组
  3. 在 当前img 附近创建一个新 div(或者你喜欢的其他HTML节点),使用h()函数生成 ELImage 的vnode,然后使用render()函数塞进刚才创建的 div,
  4. 删掉原来的 img 元素

h(ElImage, {...})可以加上你想要的 el-image 的属性

直接贴代码

<template>
  <div class="m-4">
    <div class="mb-4">替换富文本的内容为 Vue 组件</div>
    <div>
      <el-input
        type="textarea"
        v-model="content"
        autosize="{minRows:10, maxRows:20}"
        disabled
      ></el-input>
    </div>

    <div class="rich-content" v-html="content"></div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, h, render } from 'vue'
import { ElImage } from 'element-plus'

const content = ref(`
<div>
  <p>海洋 1</p>
  <img src="https://images.pexels.com/photos/189349/pexels-photo-189349.jpeg" width="800"/>
  <p>海洋 2</p>
  <img src="https://images.pexels.com/photos/1001682/pexels-photo-1001682.jpeg" width="800"/>
  <p>海洋 3</p>
  <img src="https://images.pexels.com/photos/189349/pexels-photo-189349.jpeg" width="800"/>
</div>
`)

onMounted(() => {
  const els = document
    .querySelector('.rich-content')
    ?.querySelectorAll('img') as NodeListOf<HTMLImageElement>

  if (!els || els.length === 0) return

  els?.forEach((el) => {
    const divElement = document.createElement('div')
    el.after(divElement)

    const imgSrc = el.getAttribute('src')
    if (imgSrc) {
      const vNode = h(ElImage, {
        src: imgSrc,
        style: { width: el.getAttribute('width') + 'px' },
        lazy: true,
        previewSrcList: [imgSrc]
      })
      render(vNode, divElement)
      el.remove()
    }
  })
})
</script>

这个需求的实现确实不难,重点是理解 Vue 的渲染机制,可以灵活地使用渲染函数实现更加丰富的功能。

有任何问题或者兴趣也可以在公众号:自由前端之路 找到我。

转载自:https://juejin.cn/post/7393191747930685490
评论
请登录