element ui的图片预览组件并没有单独提供出来,默认是在image组件里面一起使用,但是有的时候,我们需要点击某一个自定义按钮,然后弹出图片预览,但是,又不需要使用el-image组件,于是乎,有了这篇笔记。

首先思路并不是我的,我也是看了下同事,他网上找的教程,然后我看了下,在掘金找到一篇文章:《Vue中使用element-ui的内置组件实现图片预览全局调用功能》;深受启发,于是也记下自己的笔记。

首先思路是两个:

  1. element ui的图片预览本身是一个vue组件
  2. vue.extend构造器将vue组件通过js的方式构造出来,new出来的这个构造器,他的属性为组件的属性以及其他(其他就是我也不明白)

那么就很简单了,我们将图片预览组件引入,通过构造器构造出来,拿到构造后的实例,实例的属性就是组件的配置,我们再把组件的show方法挂载的全局vue的原型上,不就可以在任何地方通过this.xxx方法触发了吗?

当然,也没有那么简单,还是有点曲折,我们一一解决。

引入图片预览组件

组件的路径大概是:element-ui/packages/image/src/image-viewer

我们查看这个组件,可以看到,他其实是没有v-if指令的,这就导致这个组件一使用,就在html上显示了。

查看一下element的image组件,可以看到,是在外面做了v-if

所以,我们也需要这么做,这样的话就不能直接引入再通过构造器构造了,我们需要一个中间层,用于v-if管理。

所以我们需要创建一个vue组件:Preview.vue

<template>
  <transition name="viewer-fade">
    <ElImageViewer v-if="show" :urlList="urlList"
      :on-close="closeViewer"  />
  </transition>
</template>
<script>
import ElImageViewer from "element-ui/packages/image/src/image-viewer";
export default {
  props: {
    urlList: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      show: false,
    };
  },
  methods: {
    closeViewer() {
      this.show = false;
    },
  },
  components: {
    ElImageViewer,
  },
};
</script>

组件里面目前用了两个属性一个方法,show用于控制是否显示关闭,closeViewer方法是一个回调,当用户点击了关闭后会触发这个回调方法,我们就需要将show设置为false。

urlList就是图片的数组了,由于图片可以是多个,所以用的数组。

此时中间管理层我们已经做好了,下面开始构造他

vue.enxted构造组件

构造器我们需要使用到Vue实例,而且我们希望的是,这个组件可以像普通组件一样,import引入,然后vue.use()激活,所以我们要手动先写一个组件的js文件,配合use方法使用。

use方法接收一个参数,这个参数可以是对象,也可以是函数,如果是对象的话,该对象就必须提供一个install的属性,这个属性是一个方法,接收一个参数,就是Vue实例。

因为我们需要import引入刚刚创建的中间管理层组件,所以只能使用对象的形式。

代码如下:

preview.js:

import PreviewItem from "./Preview.vue";
const Preview = {};

Preview.install = function (Vue) {
  const PreviewConstructor = Vue.extend(PreviewItem);
  const instance = new PreviewConstructor();
  instance.$mount(document.createElement("div"));
  document.body.appendChild(instance.$el);

  Vue.prototype.$showImg = function (urlList=[]) {
    instance.urlList = urlList
    instance.show = true;
  }

}

export  default Preview;

大概原理:

引入管理中间件组件,创建一个对象Preview,给对象添加install方法,接收到vue实例对象。

通过vue实例对象的extend构造器,将管理中间件组件作为参数传入,此时会return出一个vue组件的构造函数,我们new出这个构造函数。

此时dom并没有生成,我们需要通过这个new出的构造函数的$mount方法,将它挂载到元素上,但是此时我们并不需要他真实挂载到网页的某个地方,所以我们创建一个div元素,将组件挂载到div中,这样他就不会出现在网页中去。

然后我们再通过instance组件的构造函数实例的$el属性,拿到dom,在将它传入网页的body中去。

此时我们的元素v-if为false,所以并不会显示。

但是,我们还没有办法触发让他显示,但是我们已知了两个条件:

  1. 有全局的Vue对象了
  2. instance实例已经可以通过他控制组件的属性

所以我们可以创建一个方法,然后挂载到Vue的原型上,方法接收一个数组,也就是图片数组,并且在方法触发时,控制instance的show属性为true。

然后我们导出这个Preview对象;

main.js 激活

在main.js文件中

import Preview from  "./preview.js"; 
Vue.use(Preview);

这样就行了, 我们可以在项目的vue组件里面,使用:

this.$showImg(["xxx"])

来触发图片预览组件。

谈谈动画

element 原装组件触发的图片预览弹窗,第一次显示的时候会有动画的,关闭的时候就没有动画了,但是我按照图片组件的那种引入方式写,不知道为什么,就是不会有动画出现。

我想了下,可能是因为预览组件transition元素里面并没有使用v-if或者v-show导致的,但是原装里面没有使用,为啥会有动画????

所以很无奈,我只能在管理中间层组件那使用了一个transition元素包裹,这样效果就和官方的保持了一致,但是我也不知道为什么官方的可以有动画,所以,等待以后明白了再说吧,或者有大佬点醒下我。

关于预览组件点击遮罩层关闭的问题

其实官方组件已经考虑到这个问题了,但是并没有使用,可能是体验不好把!

打开图片预览组件,我们找到prop属性,里面有这个一个配置:

maskClosable: {
      type: Boolean,
      default: true,
    },

这个属性字面意思是mask元素的close开关,默认是true,其实表示的是默认点击mask遮罩层的时候,是可以关闭预览弹窗的。

因为我们在去methods里面找handleMaskClick方法,这个方法是绑定在mask元素上的:

<div class="el-image-viewer__mask" @click.self="handleMaskClick"></div>

方法内容为:

handleMaskClick() {
  if (this.maskClosable) {
    this.hide();
  }
},
hide() {
  this.deviceSupportUninstall();
  this.onClose();
},

可以看到,handleMaskClick会判断maskClosable是否为true,如果为true,那么就会触发hide方法,hide则会在关闭一些事件监听后,触发onClose,这个是由prop接收的回调。

那么为什么会有人认为element ui的图片预览组件不支持点击遮罩层关闭呢?

我看了下框架的更新日志,这个点击mask关闭功能是在 2.15.0版本更新的,也就是说在那之前,是没有这个功能的,所以,这是个历史遗留问题,好在,现在都有解决方式了。

issues地址:https://github.com/ElemeFE/element/pull/20652

self修饰符表示事件必须是绑定的这个元素本身触发的才会触发函数,不会被冒泡啥的触发。

所以,我们如果想开关这个功能,就需要在中间层绑定这个属性,并prop接收一个对应的设置。

具体后面看完整代码

一些其他配置

图片预览组件还有一些其他配置:

  • zIndex 层级
  • onSwitch 当图片切换时的回调,参数为图片的index下标
  • initialIndex 首次显示时显示的图片index下标
  • appendToBody 是否允许传入body

差不多就是这些,所以,如果想完美一点的配置,我们就应该在管理中间层都要对这些参数进行绑定,并且在prop中加上接收对应属性的配置

完整版管理中间件

<template>
  <transition name="viewer-fade">
    <ElImageViewer v-if="show" :z-index="zIndex" :initial-index="imageIndex" :urlList="urlList"
      :on-close="closeViewer" :on-switch="switchViewer" :appendToBody="appendToBody"
      :maskClosable="maskClosable" />
  </transition>
</template>
<script>
import ElImageViewer from "element-ui/packages/image/src/image-viewer";
export default {
  props: {
    urlList: {
      type: Array,
      default: () => [],
    },
    zIndex: {
      type: Number,
      default: 2000,
    },
    onSwitch: {
      type: Function,
      default: () => {},
    },
    onClose: {
      type: Function,
      default: () => {},
    },
    initialIndex: {
      type: Number,
      default: 0,
    },
    appendToBody: {
      type: Boolean,
      default: true,
    },
    maskClosable: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      show: false,
    };
  },
  methods: {
    closeViewer() {
      this.show = false;
      this.onClose();
    },
    switchViewer(index) {
      this.onSwitch(index);
    },
  },
  components: {
    ElImageViewer,
  },
};
</script>

构造器调整

preview.js:

import PreviewItem from "./Preview.vue";
const Preview = {};

Preview.install = function (Vue) {
  const PreviewConstructor = Vue.extend(PreviewItem);
  const instance = new PreviewConstructor();
  instance.$mount(document.createElement("div"));
  document.body.appendChild(instance.$el);

  Vue.prototype.$showImg = function (urlList=[],initialIndex=0,onSwitch=()=>{}) {
    //在这里加上一些配置
    instance.urlList = urlList;
    instance.initialIndex = initialIndex;
    instance.onSwitch = onSwitch;
    instance.show = true;
  }

}

export  default Preview;

具体的一些用法,可能会有一些自定义,所以这里是一个示例,没有经过测试,主要是同事写好了,我再去改的话有点吃饱了,就记个笔记在这。

如果你在使用中发现了问题,欢迎留言反馈,我会第一时间处理的,所以留言一定要留真实邮箱地址,才能接收到我的回复邮件。

分类: vue 项目实战 标签: vueelement图片预览extend构造器

评论

暂无评论数据

暂无评论数据

目录