不要犹豫了,来动手打造一款自己的大图预览组件~

473 阅读1分钟

这是我参与更文挑战的第2天,活动详情查看:更文挑战

大图预览这个功能应该是比较常见的了,比如说在新闻阅读,博客阅读等,对文字中的图片,基本上都要有一个放大查看的功能,所以基于此,想要做一个大图预览模式的组件,做的不完美的地方希望大家可以指出~

其他先不说,上图,有兴趣的话再继续向下看:

1. 初始化Cli项目

使用vue create创建项目,项目目录如下:

image-20210602220526931

在components下新建img-large-mode文件夹,分别建立styles,utils,BgMask.vue,index.vue,然后也可以在components下建立一个index.js文件,供组件注册使用

2. img-large-mode

主要文件分析:

遮罩页面

 <!-- BgMask.vue -->
<template>
<!-- 包裹动画 -->
  <transition name="fade" v-if="visible">
    <div>
      <!-- 遮罩大背景,且通过监听window.resize事件进行自适应 -->
      <div class="bg-mask" :style="{width: bodyWidth + 'px', height: bodyHeight + 'px'}" @click="closeAll">
      </div>
      <!-- 图片显示主题 自适应宽高且监听滚轮以进行缩放-->
      <div class="content-box-body" :style="{width: bodyWidth + 'px', height: (bodyHeight - 100) + 'px'}" @click="closeAll" @mousewheel="handleMousewheel">
        <img
          :src="imgURL"
          alt
          class="img-custom"
          ref="customImg"
          :style="{
          transform: `scale(${activeImg.scale}) rotate(${activeImg.rotate}deg)`,
          transition: `${aniOpenFlag && aniName} 0.12s linear`
        }"
        />
      </div>
      <!-- 工具按钮 -->
      <div class="content-box-footer">
        <abbr title="放大" @click="enLarge" v-if="_buttonOptions.enLargeButton">
          <span class="iconfont icon-fangda icon"></span>
        </abbr>
        <abbr title="缩小" @click="narrowImg" v-if="_buttonOptions.narrowImgButton">
          <span class="iconfont icon-suoxiao icon"></span>
        </abbr>
        <abbr title="还原" @click="initImg" v-if="_buttonOptions.initImgButton">
          <span class="iconfont icon-huanyuan icon"></span>
        </abbr>

        <abbr title="左转" @click="rotate('left')" v-if="_buttonOptions.leftButton">
          <span class="iconfont icon-xiangyouxuanzhuan icon"></span>
        </abbr>
        <abbr title="右转" @click="rotate('right')" v-if="_buttonOptions.rightButton">
          <span class="iconfont icon-xiangzuoxuanzhuan icon"></span>
        </abbr>
        <abbr title="下载" v-if="_buttonOptions.downloadButton">
          <span class="iconfont icon-xiazai icon"></span>
        </abbr>
      </div>
    </div>
  </transition>
</template>

<script>
//import x from ''
export default {
  name: 'bg-mask',
  components: {},
  data() {
    return {
      // 动画开关
      aniOpenFlag: true,
      // 当前图片相关属性
      activeImg: {
        scale: 1,
        rotate: 0
      },
      // 浏览器宽高
      bodyWidth: window.innerWidth,
      bodyHeight: window.innerHeight
    }
  },
  props: {
    // 按钮配置
    buttonOptions: {
      type: Object,
      default: function () {
        return {}
      }
    },
    // 图片URL地址
    imgData: {
      default: ''
    },
    // 遮罩开关
    visible: {
      type: Boolean,
      required: true
    }
  },
  computed: {
    // 按钮配置,合并传递的数据
    _buttonOptions() {
      let initOptions = {
        enLargeButton: true,
        narrowImgButton: true,
        initImgButton: true,
        leftButton: true,
        rightButton: true,
        downloadButton: true
      }
      return Object.assign(initOptions, this.buttonOptions)
    },
    // 图片地址
    imgURL() {
      if (Object.prototype.toString.call(this.imgData) == '[object String]') {
        return this.imgData
      } else {
        return ''
      }
      // if (Object.prototype.toString.call(this.imgData) == '[object Array]') {
      //   return ''
      // }
    },
    // 动画显隐,一般全开,主要在恢复原形时暂时为none
    aniName() {
      return this.aniOpenFlag ? 'all' : 'none'
    }
  },
  created() {
    // 监听窗口变化,随时改变遮罩与图片大小
    window.addEventListener('resize', this.initMask)
  },
  methods: {
    // 左转右转方法
    rotate(direction) {
      this.aniOpenFlag = true
      let rotate = this.activeImg.rotate
      rotate = direction == 'right' ? rotate + 90 : rotate - 90
      this.activeImg.rotate = rotate
    },
    // 缩小方法
    narrowImg() {
      let scale = this.activeImg.scale
      scale -= 0.1
      if (scale <= 0.1) {
        scale = 0.1
      }
      this.activeImg.scale = scale
    },
    // 放大方法
    enLarge() {
      let scale = this.activeImg.scale
      scale += 0.1
      if (scale >= 5) {
        scale = 5
      }
      this.activeImg.scale = scale
    },
    // 监听滚轮,放大缩小
    handleMousewheel(e) {
      if (e.deltaY < 0) {
        this.enLarge()
      }
      if (e.deltaY > 0) {
        this.narrowImg()
      }
    },
    // 初始化宽高
    initMask() {
      this.bodyWidth = window.innerWidth
      this.bodyHeight = window.innerHeight
    },
    // 图片恢复方法
    initImg() {
      this.aniOpenFlag = false
      this.activeImg = {
        scale: 1,
        rotate: 0
      }
    },
    // 关闭方法
    closeAll() {
      this.initImg()
      this.$emit('close')
    }
  }
}
</script>

<style lang='less' scoped>
...
</style>
<!-- components/img-large-mode/index.vue -->
<template>
  <div class="img-large-mode">
    <!-- 引入遮罩组件,配置相关属性与方法 -->
    <bg-mask :visible.sync="visible" v-if="visible" @close="closeMask" :imgData="imgData" :buttonOptions="buttonOptions"></bg-mask>
  </div>
</template>

<script>
//import x from ''
import BgMask from './BgMask'
export default {
  components: {
    BgMask
  },
  props: {},
  data() {
    return {
      // 按钮配置
      buttonOptions: {},
      imgData: '',
      visible: false
    }
  },
  computed: {},
  methods: {
    // 打开遮罩方法
    show(opt) {
      // 对用户传入的的opt进行相关设置
      if(opt) {
        this.imgData = opt.imgData
        this.buttonOptions = opt.buttonOptions
      }
      // 打开遮罩
      this.visible = true
    },
    // 关闭遮罩方法
    closeMask() {
      this.visible = false
      let imgLargeDomArr = document.querySelectorAll('.img-large-mode')
      // 删除产生的dom
      if (imgLargeDomArr.length > 0) {
        for (let i = 0; i < imgLargeDomArr.length; i++) {
          imgLargeDomArr[i].parentNode.removeChild(imgLargeDomArr[i])
        }
      }
    }
  }
}
</script>

styles是iconfot的css,utils里是工具函数,没几个方法,还在补充,目前不影响使用,就先不放了...

组件注册

/* components/index.js */
// 引入组件
import imgLargeMode from "@/components/img-large-mode";
// install方法,以供Vue.use()注册使用
const install = (Vue, option) => {
  // 创建构造器,生成一个Vue实例
  const componentInstance = Vue.extend(imgLargeMode);
  // 定义实例化对象
  let currentComponent = null;
  // 对象实例化,将组件挂载到body下
  const initInstance = () => {
    currentComponent = new componentInstance();
    let componentEL = currentComponent.$mount().$el;
    document.body.appendChild(componentEL);
  };
  // 定义用户使用的方法,挂在到Vue
  Vue.prototype.$_openLargeMode = {
    show(opt) {
      initInstance();
      return currentComponent.show(opt);
    }
  };
};
if (typeof window !== "undefined" && window.Vue) {
  install(window.Vue);
}
export default {
  install,
  imgLargeMode
};

然后在main.js中引入使用

...
import imgLargeMode from "@/components/index.js";
Vue.use(imgLargeMode);
...

简单使用

<template>
  <div class="use">
    <button @click="openMask">点击</button>
  </div>
</template>

<script>
//import x from ''
export default {
  components: {},
  data() {
    return {}
  },
  computed: {},
  methods: {
    openMask() {
      this.$_openLargeMode.show({
        imgData: `https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/7/16ee08923006f96b~tplv-t2oaga2asx-image.image`,
        buttonOptions: {
          downloadButton: false
        }
      })
    }
  }
}
</script>

<style lang='less' scoped>
//@import url()
</style>

OK,这个大图预览组件基本就完成了,有兴趣的可以试一下

这个算是第一版,还有很多不足的地方,回头慢慢升级,欢迎指正~

Demo源码地址: Github

文末感谢&参考文章: juejin.cn/post/696206…