一块儿用Typescript来写一个图片全屏预览组件吧

简介

2020年是一个元年,有武汉疫情告急,企业停工。所幸的是,开发人员能够在家办公,不幸的是,工资全凭情怀,网速还得把电脑伸到窗外来执行一下git pull ...。不扯皮了,近期不管是工做项目仍是我的项目都须要用到一些简单的全屏预览的需求,因此仍是动手写一个吧,之后可能还会用到的。功能还不是很完善,后期慢慢更新。css

Tip

开发框架: Vue.js + Typescripthtml

脚手架: Vue-clivue

icon: iconfont + iview iconios

传参Props

参数名称 参数类型 是否必传 示例
path 字符数组,字符串 ['url1', 'url2'], 'url'
isVisible 布尔值(真假) true / false

xmind脑图

图片预览

我没有去作图片的一些高度和宽度更改,由于大图的话后台给的数据图片都是60%的比例。 git

ee198888.png

实现思路

显示&&隐藏

接收传递过来的isVisible来控制组件容器的加载,同时关闭按钮传递false上去,表明我须要关闭,上层的isVisible绑定的值做出修改,这样就实现了一个显示隐藏的效果,代码以下:web

父组件

<ZoomImage :path="defaultImagePath" :isVisible.sync="visible" />
复制代码

子组件

<Icon type="md-close" class="default-btn close-zoom" @click="$emit('update:isVisible', false)"/>
复制代码

图片加载展现

由于传递的path参数存在字符串和数组两种可能。因此展现的时候,须要去进行判断,若是是数组参数,那么就须要去进行判断何时显示下一张箭头,何时显示上一张箭头,逻辑代码以下:typescript

<!-- HTMLElement DOM -->
<Icon v-show="isNextShow" type="ios-arrow-forward" class="default-btn leave-next" @click.stop="toNext"/>
<div class="img-wrapper" v-loading="isSuccess">
  <div class="error-view" v-show="isError">
    <Icon type="md-bug" :size="30"/>
    <p>加载失败</p>
  </div>
  <img v-show="!isError" :src="setImagePath" alt="图片" id="zoomImage" @load="imgLoad('success')" @error="imgLoad('error')">
</div>
复制代码
/* path展现 */
private isSuccess: boolean = true // 加载是否成功
private isError: boolean = false // 加载是否失败
private currentPathIndex: number = 0 // 若是path为数组,默认的index

/* 设置当前显示图片属性 */
get setImagePath() {
  // 若是path的类型是字符串, 就直接使用path自己, 若是不是就使用数组path[0]数组第一条
  return typeof this.path === 'string' ? this.path : this.path[this.currentPathIndex]
}

/* * 侦听图片是否加载完成 ? 这里还须要就是去作v-loading的显示和关闭,这个指令的简单方式实现。百度不少。各位能够自定义或者不用 * 我在img图片上绑定了load 和 error事件,经过传递的字符串来共用一个方法,这样就不须要写两个不一样的methods了。只是作的操做不一样了 * isError是用来作加载失败的展现的。防止没有图片的时候黑屏尴尬 */
private imgLoad(state: string): void {
  if (state === 'success') {
    // success
    this.isSuccess = false
  } else {
    this.isSuccess = false
    this.isError = true
  }
}
复制代码

工具栏

工具栏方法,如今我只作了这一些经常使用的操做,缩放,旋转,保存(google内核问题,在想新的解决方案,如何去处理),恢复。旋转默认是90度,缩放默认10%。这是一个比较合理的参数了。工具栏我是经过for循环出来的。而后共同绑定了一个methods方法,不须要去写5个方法分开实现相应功能,只须要根据你for的index来去作switch的选择处理。数组

<ul class="tool-wrapper">
  <li v-for="(item, index) in toolIcon" :key="index" class="tool-item">
    <i class="iconfont tool-item__icon" :class="item" @click.stop="bindBlowClick(index)"/>
  </li>
</ul>
复制代码
/* 变量,这是定义工具栏的图标,使用的是iconfont的styles图标 */
private toolIcon: Array<string> = ['icon-rotateright', 'icon-rotateleft', 'icon-download', 'icon-suoxiao', 'icon-fangda', 'icon-emoji']

  /* 绑定工具栏相应方法, 作一些处理 */
  private bindBlowClick(id: number): void{
    // 默认值
    let rotateSize: number = 0
    let scaleSize: number = 1
    // 获取dom
    let imgDom: any = document.getElementById('zoomImage')
    // 获取dom参数数组,切割
    let DomArr: Array<string> = imgDom.style.transform.split(' ')
    if (DomArr.length >= 2) {
      // 利用正则获取当前的transform属性
      scaleSize = Number(DomArr[0].replace(/[^\d|^.]/g, ''))
      rotateSize = Number(DomArr[1].replace(/[^\d|^-]/g, ''))
    }
    switch (id) {
      case 0:
        // 顺时针旋转
        imgDom.style['transform'] = `scale(${scaleSize}) rotate(${(rotateSize === 360 ? 0 : rotateSize) + 90}deg)`
        break
      case 1:
        // 逆时针旋转
        imgDom.style['transform'] = `scale(${scaleSize}) rotate(${(rotateSize === 360 ? 0 : rotateSize) - 90}deg)`
        break
      case 2:
        // 保存/下载图片
        const aDom: HTMLAnchorElement = document.createElement('a')
        const aDomEvent: MouseEvent = new MouseEvent('click')
        aDom.download = 'download'
        aDom.href = this.path[this.currentPathIndex]
        aDom.dispatchEvent(aDomEvent)
        break
      case 3:
        // 缩小
        console.log(scaleSize)
        imgDom.style['transform'] = `scale(${scaleSize - 0.1}) rotate(${rotateSize})`
        break
      case 4:
        // 放大
        imgDom.style['transform'] = `scale(${scaleSize + 0.1}) rotate(${rotateSize}deg)`
        break
      case 5:
        // 重置
        imgDom.style['transform'] = `scale(1) rotate(0deg)`
        break
    }
  }
复制代码

贴一下代码吧

<!-- Template -->
<template>
  <div class="zoom-image-container" v-if="isVisible">
    <Icon type="md-close" class="default-btn close-zoom" @click="$emit('update:isVisible', false)"/>
    <!-- <Icon type="ios-arrow-back" class="default-btn leave-last"/> -->
    <Icon v-show="isNextShow" type="ios-arrow-forward" class="default-btn leave-next" @click.stop="toNext"/>
    <div class="img-wrapper" v-loading="isSuccess">
      <div class="error-view" v-show="isError">
        <Icon type="md-bug" :size="30"/>
        <p>加载失败</p>
      </div>
      <img v-show="!isError" :src="setImagePath" alt="图片" id="zoomImage" @load="imgLoad('success')" @error="imgLoad('error')">
    </div>
    <ul class="tool-wrapper">
      <li v-for="(item, index) in toolIcon" :key="index" class="tool-item">
        <i class="iconfont tool-item__icon" :class="item" @click.stop="bindBlowClick(index)"/>
      </li>
    </ul>
  </div>
</template>
复制代码
/* JS/TS代码 */
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'

@Component
export default class ZoomImage extends Vue {
  // 传入图片数据
  @Prop({ required: true })
  private path!: string | Array<string>
  // 传入显示
  @Prop({ default: false, required: true })
  private isVisible!: boolean
  private toolIcon: Array<string> = ['icon-rotateright', 'icon-rotateleft', 'icon-download', 'icon-suoxiao', 'icon-fangda', 'icon-emoji']
  private isSuccess: boolean = true
  private isError: boolean = false
  private currentPathIndex: number = 0
  private imgPath: Array<string> = ['http://a4.att.hudong.com/21/09/01200000026352136359091694357.jpg']

  // 设置当前显示图片属性
  get setImagePath() {
    return typeof this.path === 'string' ? this.path : this.path[this.currentPathIndex]
  }

  // 设置next按钮是否显示
  get isNextShow() {
    return typeof this.path !== 'string' && this.path.length > 1
  }

  // 绑定工具栏相应方法
  private bindBlowClick(id: number): void{
    let imgDom: any = document.getElementById('zoomImage')
    let DomArr: Array<string> = imgDom.style.transform.split(' ')
    let rotateSize: number = 0
    let scaleSize: number = 1
    if (DomArr.length >= 2) {
      scaleSize = Number(DomArr[0].replace(/[^\d|^.]/g, ''))
      rotateSize = Number(DomArr[1].replace(/[^\d|^-]/g, ''))
    }
    switch (id) {
      case 0:
        // 顺时针旋转
        imgDom.style['transform'] = `scale(${scaleSize}) rotate(${(rotateSize === 360 ? 0 : rotateSize) + 90}deg)`
        break
      case 1:
        // 逆时针旋转
        imgDom.style['transform'] = `scale(${scaleSize}) rotate(${(rotateSize === 360 ? 0 : rotateSize) - 90}deg)`
        break
      case 2:
        // 保存/下载图片
        const aDom: HTMLAnchorElement = document.createElement('a')
        const aDomEvent: MouseEvent = new MouseEvent('click')
        aDom.download = 'download'
        aDom.href = this.path[this.currentPathIndex]
        aDom.dispatchEvent(aDomEvent)
        break
      case 3:
        // 缩小
        console.log(scaleSize)
        imgDom.style['transform'] = `scale(${scaleSize - 0.1}) rotate(${rotateSize})`
        break
      case 4:
        // 放大
        imgDom.style['transform'] = `scale(${scaleSize + 0.1}) rotate(${rotateSize}deg)`
        break
      case 5:
        // 放大
        imgDom.style['transform'] = `scale(1) rotate(0deg)`
        break
    }
  }
  
  // 侦听图片是否加载完成
  private imgLoad(state: string) {
    if (state === 'success') {
      // success
      this.isSuccess = false
    } else {
      this.isSuccess = false
      this.isError = true
    }
  }

  // 下一张
  private toNext(): void {
    const pathLength: number = this.path.length
    this.currentPathIndex === pathLength - 1 ? this.currentPathIndex = 0 : this.currentPathIndex++
  }
}
</script>
复制代码
/* styles */
<style lang="scss">
@import '@/assets/scss/global.d.scss';
  .zoom-image-container{
    @include position($position: fixed, $top: 0, $left: 0);
    @include zIndex($zindex: 2000);
    @include flex($justify: center, $align: center);
    // background: rgba(0, 0, 0, 0.8);
    background: black;
    height: 100vh;
    width: 100vw;
    .leave-last{
      @include position($position: absolute, $left: rem(50px), $top: 50%);
    }
    .leave-next{
      @include position($position: absolute, $right: rem(50px), $top: 50%);
    }
    .default-btn{
      @include radius(50%);
      font-size: rem(25px);
      padding: rem(15px);
      font-weight: bold;
      color: $bg;
      background: #222222;
      cursor: pointer;
      &:hover{
        color: $main;
      }
    }
    .close-zoom{
      @include position($position: absolute, $right: rem(50px), $top: rem(50px));
    }
    .img-wrapper {
      #zoomImage{
        // defalut css
        transform: rotate(0deg) scale(1);
      }
      .error-view{
        width: rem(400px);
        height: rem(400px);
        background: $bg;
        cursor: pointer;
        @include flex($justify: center, $align: center, $direction: column);
        p{
          font-size: rem(20px);
        }
      }
    }
    .tool-wrapper{
      @include position($position: absolute, $bottom: rem(20px), $left: 50%);
      transform: translate(-50%);
      @include flex();
      .tool-item{
        background: #222222;
        &__icon{
          font-size: rem(30px);
          color: $bg;
          cursor: pointer;
          padding: rem(10px);
          &:hover{
            color: $main;
          }
        }
      }
    }
  }
</style>
复制代码

总结

但愿评论区的朋友可以提出一些宝贵的建议,若是合理的功能我会尽力去实现的。sass

2020年想给本身的博客文章多写一点,可是不知道写点什么,leetcode吧,只是会简简单单的题目,技术深度,又没有什么理解深刻的技术,不过之后会慢慢积累本身的技术,分享本身和代码的故事,2020咱们继续努力。文章我是写在语雀上的。没有去作多的站点。之前搭建两个blog都没有坚持下来。 语雀地址app

也欢迎一块儿作交流哦。

98e83a6b.png
相关文章
相关标签/搜索