不用点击也能预览图片:Element UI ImageViewer 命令式调用方案

前言

做前端开发这么多年,图片预览功能几乎是每个项目都要用到的。Element UI 虽然提供了 el-image 组件,点击图片就能预览,但实际项目中总有一些场景,不是用户点击图片触发预览,而是需要代码来控制。比如表单提交后自动预览上传的图片,或者列表项点击后预览相关图片。

最近我在项目中就遇到了这个问题,查了一下 Element UI 的文档,发现 ImageViewer 组件虽然功能强大,但只能通过 el-image 组件间接使用,没有提供直接的 API 来调用。于是我就封装了一个 previewImages 工具函数,实现了命令式调用 ImageViewer 的功能。今天就来分享一下这个方案,希望对大家有所帮助。

问题背景

Element UI 图片预览的缺点

Element UI 的 el-image 组件虽然好用,但在实际项目中也存在一些局限性:

  1. 必须通过点击触发:只能通过用户点击图片来触发预览,无法通过代码控制。
  2. 依赖 DOM 结构 :需要在模板中声明 el-image 组件,无法在运行时动态创建。
  3. 灵活性不足:无法根据需要随时显示或隐藏预览,必须与图片元素绑定。
  4. 功能受限 :只能预览与 el-image 组件关联的图片,无法预览任意图片列表。

传统实现方式的局限性

传统的图片预览实现方式主要有以下几种,各有各的问题:

  1. 使用 el-image 组件:只能通过用户点击触发,无法满足代码控制的需求。
  2. 自定义预览组件 :需要自己实现预览逻辑,工作量大,且功能可能不如 Element UI 的 ImageViewer 完善。
  3. 使用第三方库:增加了项目依赖,可能与现有技术栈不兼容,学习成本高。

这些方式都无法很好地满足在特定逻辑后自动触发图片预览的需求,比如表单提交后预览上传的图片,或者列表项点击后预览相关图片。

解决方案

为什么要封装 previewImages 工具

基于以上问题,我封装了 previewImages 工具,主要原因有:

  1. 解决代码触发预览的问题:实现了通过代码控制图片预览的功能,满足各种场景的需求。
  2. 复用 Element UI 的 ImageViewer:充分利用 Element UI 现有的组件,避免重复造轮子。
  3. 简化使用方式:提供了简洁的 API,使用起来非常方便。
  4. 提高代码可维护性:将图片预览逻辑封装成工具函数,便于在项目中复用和维护。

previewImages 工具的实现原理

previewImages 工具通过命令式的方式调用 Element UI 的 ImageViewer 组件,实现了代码触发的图片预览功能。它的核心思想是:

  1. 动态创建 ImageViewer 组件实例
  2. 将组件挂载到 DOM 中
  3. 提供关闭预览的方法

核心代码解析

让我们来看一下 previewImages 工具的核心代码:

javascript 复制代码
import Vue from 'vue'
import ImageViewer from 'element-ui/packages/image/src/image-viewer.vue'

/**
 * 命令式图片预览
 * methods: previewImages({urlList:[url1,url2...]})
 * @param {Object} props - 选项参数
 * @param {Array} props.urlList - 图片地址列表
 */

function previewImages(props) {
  const { urlList } = props
  if (!Array.isArray(urlList) || urlList.length === 0) {
    console.error(
      '[Vue Element Error][ImageViewer] urlList should be a non-empty array'
    )
    return
  }
  buildComponentInBody(ImageViewer, props)
}

function buildComponentInBody(component, props) {
  let disposer = null
  const onClose = () => disposer && disposer()
  const mountComponent = (component) => {
    const Component = Vue.extend(component)
    const instance = new Component({
      propsData: { ...props, onClose },
    }).$mount()
    document.body.appendChild(instance.$el)
    return () => {
      instance.$destroy()
    }
  }

  disposer = mountComponent(component)
}

export { previewImages, buildComponentInBody }

这段代码的实现原理非常简洁:

  1. 参数验证 :检查 urlList 是否为非空数组,确保预览功能能够正常工作。
  2. 组件创建 :使用 Vue.extend 创建组件构造函数,然后实例化组件并传入 propsonClose 方法。
  3. 挂载组件:将组件实例挂载到 DOM 中,使其显示出来。
  4. 资源清理 :提供 disposer 函数,用于在预览关闭时销毁组件实例,避免内存泄漏。

使用方法

使用 previewImages 工具非常简单,只需要导入并调用即可:

1. 导入工具

javascript 复制代码
import { previewImages } from '@/components/DialogPicker/previewImages'

2. 调用预览

javascript 复制代码
// 预览单张图片
previewImages({ urlList: ['https://example.com/image1.jpg'] })

// 预览多张图片
previewImages({
  urlList: [
    'https://example.com/image1.jpg',
    'https://example.com/image2.jpg',
    'https://example.com/image3.jpg'
  ]
})

// 带初始索引的预览
previewImages({
  urlList: [
    'https://example.com/image1.jpg',
    'https://example.com/image2.jpg',
    'https://example.com/image3.jpg'
  ],
  initialIndex: 1 // 从第二张图片开始预览
})

实际应用场景

previewImages 工具在以下场景中特别有用:

1. 表单提交后预览

当用户提交表单后,我们可以自动预览上传的图片:

javascript 复制代码
// 表单提交处理
async function handleSubmit() {
  try {
    // 提交表单
    const response = await submitForm(this.form)
    
    // 预览上传的图片
    if (response.data.images && response.data.images.length > 0) {
      previewImages({ urlList: response.data.images })
    }
    
    // 显示成功消息
    this.$message.success('表单提交成功')
  } catch (error) {
    this.$message.error('表单提交失败')
  }
}

2. 列表项点击预览

当用户点击列表项时,预览相关的图片:

javascript 复制代码
// 列表项点击处理
handleItemClick(item) {
  // 预览与该项相关的图片
  if (item.images && item.images.length > 0) {
    previewImages({ urlList: item.images })
  }
}

3. 条件触发预览

在特定条件下触发图片预览:

javascript 复制代码
// 条件触发预览
checkAndPreview() {
  // 检查条件
  if (this.condition) {
    // 预览图片
    previewImages({ urlList: this.imageList })
  }
}

扩展和优化

我们可以对 previewImages 工具进行一些扩展和优化,使其更加灵活和强大:

1. 支持更多配置选项

我们可以扩展 previewImages 函数,支持更多 ImageViewer 组件的配置选项:

javascript 复制代码
function previewImages(props) {
  const { urlList, ...restProps } = props
  if (!Array.isArray(urlList) || urlList.length === 0) {
    console.error(
      '[Vue Element Error][ImageViewer] urlList should be a non-empty array'
    )
    return
  }
  buildComponentInBody(ImageViewer, { ...restProps, urlList })
}

这样,我们就可以传递更多配置选项,如 initialIndexzIndex 等:

javascript 复制代码
previewImages({
  urlList: ['https://example.com/image1.jpg', 'https://example.com/image2.jpg'],
  initialIndex: 0,
  zIndex: 9999
})

2. 添加动画效果

我们可以为预览组件添加动画效果,提升用户体验:

javascript 复制代码
function buildComponentInBody(component, props) {
  let disposer = null
  const onClose = () => {
    // 添加关闭动画
    const instance = document.querySelector('.el-image-viewer')
    if (instance) {
      instance.style.transition = 'opacity 0.3s'
      instance.style.opacity = '0'
      setTimeout(() => {
        disposer && disposer()
      }, 300)
    } else {
      disposer && disposer()
    }
  }
  const mountComponent = (component) => {
    const Component = Vue.extend(component)
    const instance = new Component({
      propsData: { ...props, onClose },
    }).$mount()
    // 添加打开动画
    instance.$el.style.opacity = '0'
    document.body.appendChild(instance.$el)
    setTimeout(() => {
      instance.$el.style.transition = 'opacity 0.3s'
      instance.$el.style.opacity = '1'
    }, 10)
    return () => {
      instance.$destroy()
    }
  }

  disposer = mountComponent(component)
}

3. 支持键盘导航

我们可以添加键盘导航支持,使用户可以通过键盘控制图片预览:

javascript 复制代码
function buildComponentInBody(component, props) {
  let disposer = null
  const onClose = () => {
    // 移除键盘事件监听器
    document.removeEventListener('keydown', handleKeydown)
    disposer && disposer()
  }
  
  // 键盘事件处理
  const handleKeydown = (e) => {
    switch (e.key) {
      case 'Escape':
        onClose()
        break
      case 'ArrowLeft':
        // 切换到上一张图片
        const viewer = document.querySelector('.el-image-viewer__btn.el-image-viewer__btn--prev')
        viewer && viewer.click()
        break
      case 'ArrowRight':
        // 切换到下一张图片
        const nextViewer = document.querySelector('.el-image-viewer__btn.el-image-viewer__btn--next')
        nextViewer && nextViewer.click()
        break
    }
  }
  
  const mountComponent = (component) => {
    const Component = Vue.extend(component)
    const instance = new Component({
      propsData: { ...props, onClose },
    }).$mount()
    document.body.appendChild(instance.$el)
    // 添加键盘事件监听器
    document.addEventListener('keydown', handleKeydown)
    return () => {
      instance.$destroy()
    }
  }

  disposer = mountComponent(component)
}

技术要点

  1. 命令式组件调用 :通过 Vue.extend$mount 实现组件的命令式调用,而不是通过模板声明式使用。

  2. 动态组件挂载:将组件实例动态挂载到 DOM 中,实现按需显示。

  3. 资源管理 :提供 disposer 函数,确保组件在不需要时能够被正确销毁,避免内存泄漏。

  4. 参数验证:对输入参数进行验证,确保函数能够正常工作,提高代码的健壮性。

  5. 事件处理 :通过 onClose 回调函数处理预览关闭事件,实现组件的生命周期管理。

注意事项

  1. 依赖要求 :该工具依赖 Vue 和 Element UI 的 ImageViewer 组件,确保项目中已经安装了这些依赖。

  2. 图片地址 :确保 urlList 中的图片地址是可访问的,否则可能会导致预览失败。

  3. 性能考虑:对于大量图片的预览,可能会影响性能,建议限制预览图片的数量。

  4. 浏览器兼容性:该工具使用了 Vue 的 API 和 Element UI 的组件,确保在目标浏览器中能够正常工作。

可直接运行的 Demo

为了让大家更好地理解和使用 previewImages 工具,我准备了一个可直接运行的 HTML demo。你只需要将以下代码保存为 index.html 文件,然后在浏览器中打开即可看到效果。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Element UI ImageViewer 命令式调用示例</title>
  <!-- 引入 Element UI CSS -->
  <link rel="stylesheet" href="https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css">
  <!-- 引入 Vue -->
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <!-- 引入 Element UI JS -->
  <script src="https://unpkg.com/element-ui@2.15.14/lib/index.js"></script>
</head>
<body>
  <div id="app">
    <div style="margin: 20px;">
      <h2>Element UI ImageViewer 命令式调用示例</h2>
      <div style="margin: 20px 0;">
        <el-button type="primary" @click="previewSingleImage">预览单张图片</el-button>
        <el-button type="success" @click="previewMultipleImages">预览多张图片</el-button>
        <el-button type="warning" @click="previewWithInitialIndex">带初始索引的预览</el-button>
      </div>
      <div style="margin: 20px 0;">
        <el-input v-model="customImageUrl" placeholder="输入图片地址" style="width: 400px;"></el-input>
        <el-button type="info" @click="previewCustomImage" style="margin-left: 10px;">预览自定义图片</el-button>
      </div>
    </div>
  </div>

  <script>
    // 复制 previewImages 工具代码
    function previewImages(props) {
      const { urlList } = props
      if (!Array.isArray(urlList) || urlList.length === 0) {
        console.error(
          '[Vue Element Error][ImageViewer] urlList should be a non-empty array'
        )
        return
      }
      buildComponentInBody(ElementUI.ImageViewer, props)
    }

    function buildComponentInBody(component, props) {
      let disposer = null
      const onClose = () => disposer && disposer()
      const mountComponent = (component) => {
        const Component = Vue.extend(component)
        const instance = new Component({
          propsData: { ...props, onClose },
        }).$mount()
        document.body.appendChild(instance.$el)
        return () => {
          instance.$destroy()
        }
      }

      disposer = mountComponent(component)
    }

    // 示例图片地址
    const imageUrls = [
      'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg',
      'https://cube.elemecdn.com/9/28/3d939848980f98040e7488856320jpeg.jpeg',
      'https://cube.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
      'https://cube.elemecdn.com/5/93/396259950760c7ec4f6880e94446jpeg.jpeg'
    ]

    new Vue({
      el: '#app',
      data() {
        return {
          customImageUrl: 'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg'
        }
      },
      methods: {
        // 预览单张图片
        previewSingleImage() {
          previewImages({ urlList: [imageUrls[0]] })
        },
        // 预览多张图片
        previewMultipleImages() {
          previewImages({ urlList: imageUrls })
        },
        // 带初始索引的预览
        previewWithInitialIndex() {
          previewImages({ 
            urlList: imageUrls,
            initialIndex: 2 // 从第三张图片开始预览
          })
        },
        // 预览自定义图片
        previewCustomImage() {
          if (this.customImageUrl) {
            previewImages({ urlList: [this.customImageUrl] })
          } else {
            this.$message.warning('请输入图片地址')
          }
        }
      }
    })
  </script>
</body>
</html>

这个 demo 包含了以下功能:

  1. 预览单张图片:点击按钮预览第一张示例图片。
  2. 预览多张图片:点击按钮预览所有示例图片。
  3. 带初始索引的预览:点击按钮从第三张图片开始预览。
  4. 预览自定义图片:输入图片地址,然后点击按钮预览。

你可以直接在浏览器中打开这个文件,点击各个按钮查看效果。

总结

previewImages 工具是一个非常实用的图片预览解决方案,它通过命令式的方式调用 Element UI 的 ImageViewer 组件,实现了代码触发的图片预览功能。这个工具不仅使用简单,而且灵活强大,可以满足各种场景下的图片预览需求。

通过封装 previewImages 工具,我们解决了 Element UI 图片预览的局限性,实现了更加灵活的图片预览功能。同时,我们也学习了如何通过 Vue.extend$mount 实现组件的命令式调用,这是一个非常实用的前端开发技巧。

希望这篇文章对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言。

相关推荐
尘世中一位迷途小书童1 小时前
前端工程化基石:package.json 40+ 字段逐一拆解
前端·javascript·架构
OpenTiny社区1 小时前
WebMCP + WebSkills:企业级智能化页面操控方案,兼顾隐私安全与高效落地!
前端·ai编程·mcp
酉鬼女又兒2 小时前
零基础快速入门前端JavaScript四大核心内置对象:Math、Date、String、Array全解析(可用于备赛蓝桥杯Web应用开发)
前端·javascript·css·蓝桥杯·前端框架·js
__sgf__2 小时前
ES11(ES2020)新特性
前端·javascript
__sgf__2 小时前
ES8(ES2017)新特性
前端·javascript
__sgf__2 小时前
ES9(ES2018)新特性
前端·javascript
送鱼的老默2 小时前
学习笔记--vue3 watchEffect监听的各种姿势用法和总结
前端·vue.js
你挚爱的强哥2 小时前
解决:动态文本和背景色一致导致文字看不清楚,用js获取背景图片主色调,并获取对比度最大的hex色值给文字
前端·javascript·github
用户69371750013842 小时前
Android 手机终于能当电脑用了
android·前端