logo

公共Plugin封装之图片预览

Mar 15, 2023 · 8 min

写在前面#

对于项目内需要经常复用的组件,并基于实际项目环境,封装一些公共的Plugin, 本篇文章为previewImage.js

基于个人项目环境进行封装的Plugin,仅以本文介绍封装Plugin思想心得,故相关代码可能不适用他人

项目环境#

Vue3.x + Ant Design Vue3.x + Vite3.x

背景介绍#

研发的管理后台中,涉及到需要预览的图片,结合使用的UI框架AntDesignVue3.x,一般情况下的使用方式是这样:

<template>
  <div>
    <a-button type="primary" @click="() => setVisible(true)">show image preview</a-button>
    <a-image
    :width="200"
    :style="{ display: 'none' }"
    :preview="{
      visible,
      onVisibleChange: setVisible,
    }"
    src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
    />
  </div>
</template>
<script lang="ts">
  import { defineComponent, ref } from 'vue';
  export default defineComponent({
    setup() {
      const visible = ref<boolean>(false);
        const setVisible = (value): void => {
          visible.value = value;
        };
        return {
          visible,
          setVisible,
        };
      },
    });
  </script>

当然还有另外的情况:
1.后台中需要预览图片的地方多
2.需要动态去修改图片src
出现上述两种情况,为避免写重复的代码,减少写控制变量和事件代码,针对代码层面进行优化时,则可以考虑进行二次封装

封装分解:核心代码#

import { createVNode, render as vueRender } from 'vue';
import { Image } from 'ant-design-vue';

const container = document.createDocumentFragment();
function render(props) {
  const vm = createVNode(Image, { ...props });
  vueRender(vm, container);
  return vm;
}

基于上述代码,实现创建一个虚拟节点,用来实现单图片预览的基础,下面则顺便对于使用到的内容进行讲解,权当是又一次加固相关技术知识吧~

封装分解:render#

export declare const render: RootRenderFunction<Element | ShadowRoot>;

export declare type RootRenderFunction<HostElement = RendererElement> = (vnode: VNode | null, container: HostElement, isSVG?: boolean) => void;

render 函数是 Vue2.x 中新增的一个函数
在 Vue 中我们使用模板 HTML 语法组建页面的,使用 render 函数我们可以用 Js 语言来构建 DOM. 因为 Vue 是虚拟 DOM ,所以在拿到 Template 模板时也要转译成 VNode 的函数,而用 render 函数构建 DOM ,Vue 就免去了转译的过程,因此相对而言,提升了性能
核心代码内,vueRender函数接受两个参数,下面我们看一下第一参数vm

封装分解:createVNode#

export declare const createVNode: typeof _createVNode;

declare function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props?: (Data & VNodeProps) | null, children?: unknown, patchFlag?: number, dynamicProps?: string[] | null, isBlockNode?: boolean): VNode;

export declare interface VNode<HostNode = RendererNode, HostElement = RendererElement, ExtraProps = {
  [key: string]: any;
}> {
  /* Excluded from this release type: __v_isVNode */
  /* Excluded from this release type: __v_skip */
  type: VNodeTypes;
  props: (VNodeProps & ExtraProps) | null;
  key: string | number | symbol | null;
  ref: VNodeNormalizedRef | null;
  /**
  * SFC only. This is assigned on vnode creation using currentScopeId
  * which is set alongside currentRenderingInstance.
  */
  scopeId: string | null;
  /* Excluded from this release type: slotScopeIds */
  children: VNodeNormalizedChildren;
  component: ComponentInternalInstance | null;
  dirs: DirectiveBinding[] | null;
  transition: TransitionHooks<HostElement> | null;
  el: HostNode | null;
  anchor: HostNode | null;
  target: HostElement | null;
  targetAnchor: HostNode | null;
  /* Excluded from this release type: staticCount */
  suspense: SuspenseBoundary | null;
  /* Excluded from this release type: ssContent */
  /* Excluded from this release type: ssFallback */
  shapeFlag: number;
  patchFlag: number;
  /* Excluded from this release type: dynamicProps */
  /* Excluded from this release type: dynamicChildren */
  appContext: AppContext | null;
  /* Excluded from this release type: ctx */
  /* Excluded from this release type: memo */
  /* Excluded from this release type: isCompatRoot */
  /* Excluded from this release type: ce */
}

createVNode(Image, {...props}),接受的参数:“定义的元素”、“元素的扩展属性”
结合实际业务需求,第一个参数(定义的元素)为Image组件,为保证全局使用,则我们可以将一般情况下需要写的变量控制,如组件显示visible,组件图片源地址src,以及组件上的触发事件events都可以使用props传入。如此,便实现了虚拟节点的创建.
由于render: RootRenderFunction中所接受的参数,container: HostElement为必传,因此下面看一下核心代码中第二个参数container

封装分解:createDocumentFragment#

const container = document.createDocumentFragment()创建一个新的空白的文档片段即虚拟DOM. DocumentFragments (en-US) 是 DOM 节点。它们不是主 DOM 树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到 DOM 树。在 DOM 树中,文档片段被其所有的子元素所代替。
因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能

完整代码:previewImage.js#

import { createVNode, render as vueRender } from 'vue';
import { Image } from 'ant-design-vue';
import 'ant-design-vue/es/image/style/index.less';

let previewImageInstance;

const container = document.createDocumentFragment();
function render(props) {
  const vm = createVNode(Image, { ...props });
  vueRender(vm, container);
  return vm;
}

// 关闭
function close() {
  update('', false);
}

// 销毁
function destroy() {
  if (previewImageInstance) {
    vueRender(null, container);
    previewImageInstance.component.update();
    previewImageInstance = null;
  }
}

// 更新
function update(src, visible) {
  previewImageInstance.component.props.preview.visible = visible;
  previewImageInstance.component.props.src = src;
  previewImageInstance.component.update();
}

// 显示
function show(src) {
  if (previewImageInstance) {
    update(src, true);
  } else {
    previewImageInstance = render({
      preview: { visible: true, onVisibleChange: close },
      src,
    });
  }
}
const previewImage = {
  show,
  destroy,
};

export default previewImage;

实际使用#

在需要使用预览图片的页面文件

import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();

/* 实际使用,同样的,可以在任何需要使用的地方调用事件 */
proxy.$previewImage.show(src);

发散思考:如何实现多张图预览呢?#

其实,封装的Plugin目前仅支持单张图片预览,也就是当页面内同时出现多张图片时,并没有AntDesignVue中预览显示视图层可以前后切换的功能,那如何实现呢?
可以这样做,也就是传入一个数组,而render里,组件树的vnodes必须是唯一的,则可以使用循环,将多个Image循环出来,具体可以参考下官网的写法

function render() {
  return h(
  'div',
  Array.from({ length: 20 }).map(() => {
    return h('p', 'hi')
  })
  )
}

参考链接#

> cd ..