手把手教你写一个图片预览组件

outside_default.png

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

一、前言

本篇主要介绍,vue项目手写一个图片预览组件,组件主要包括图片方法、图片缩小、显示原图、下载、复制等功能。

二、实现方式

首先我们需要做一个图片预览组件都有的功能表头,如下图

outside_default.png

主要功能包括,放大、缩放比例显示、缩小、原图比例、旋转、复制、下载,如果是多图预览的话,还有上一张、下一张。接下来我们逐个实现各个功能。

1.初始化窗口显示完整图片

我们在一打开图片预览的时候,就是要看到图片的全貌,有时候图片会比我们预览的窗口要大许多,因此我们需要初始化的时候计算图片的大小和窗口大小的对应比例。代码如下

<template>
  <div class="preview-image">
    <ul class="header">
      ..
    </ul>
    <section ref="previewImage" class="preview-content dragscroll" :class="{'hideScroll' : hideScroll}" @wheel="handleMousewheel">
      <img ref="mediaElement"  :style="getStyles" :src="currentImgUrl" />
    </section>
  </div>
</template>
<script>
export default{
    data () {
        return {
          // 样式控制
          styles: {
            ratio: 0,
            rotate: 0,
            width: 0,
            height: 0,
            y: 0,
            x: 0,
            defaultWidth: 0,
            defaultHeight: 0
          },
          container: '',
          viewer: ''
        }
    },
    computed: {
        // 获取当前图片的url
        currentImgUrl () {
          let result = this.url
          if (this.list.length) {
            result = this.list[this.index]
          }
          return result
        },
        // 动态获取样式变动
        getStyles () {
          const styles = this.styles
          let _styles = {}
          _styles = {
            transform: `rotate(${styles.rotate}deg)`,
            width: styles.width + 'px',
            height: styles.height + 'px',
            marginTop: styles.y + 'px',
            marginLeft: styles.x + 'px',
            zoom: styles.scale
          }
          return _styles
        },
    },
    methods: {
        // 初始化图片比例
        async initStyle (url) {
          // 初始设置旋转为0
          this.styles.rotate = 0
          const image = await this.loadImage(url)
          if (!image) return
          // 保存图片初始宽高
          this.styles.defaultWidth = image.width
          this.styles.defaultHeight = image.height
          // 图片大小
          const width = image.width
          const height = image.height
          // 等待页面加载完成
          await this.$nextTick()
          // 获取窗口和图片
          this.container = this.$refs.previewImage
          this.viewer = this.$refs.mediaElement
          // 可视区大小
          const containerWidth = this.container.clientWidth
          const containerHeight = this.container.clientHeight
          // 可视区域宽度与图片实际宽度比例
          const initWidthScale = containerWidth / width
          // 可视区域高度与图片实际高度比例
          const initHeightScale = containerHeight / height

          // 优先缩放至比例最小的那个值
          if (initWidthScale < 1 || initHeightScale < 1) {
            let scale = initHeightScale > initWidthScale ? initWidthScale : initHeightScale
            this.setZoomSize(scale)
          } else {
            this.setZoomSize(1)
          }
          this.$nextTick(() => {
            this.setMargin()
          })
        },
        // 加载一张图片,获取图片实例
        loadImage (url) {
          return new Promise((resolve, reject) => {
            const image = new Image()
            image.src = url
            image.onload = () => {
              resolve(image)
            }
            image.onerror = () => {
              const err = this.$t('im.loadErr')
              reject(err)
            }
          })
        },
        // 等比缩放
        setZoomSize (ratio) {
          this.styles.width = this.styles.defaultWidth * ratio
          this.styles.height = this.styles.defaultHeight * ratio
          this.styles.ratio = ratio
        },
        // 通过设置边距来计算图片位置
        setMargin () {
          if (this.container && this.viewer) {
            const contaienrSize = this.container.getBoundingClientRect()
            const imageSize = this.viewer.getBoundingClientRect()
            let y = (contaienrSize.height - imageSize.height) / 2
            let x = (contaienrSize.width - imageSize.width) / 2
            y = y < 0 ? 0 : y
            x = x < 0 ? 0 : x
            this.styles.y = y
            this.styles.x = x
          }
        },
    }
}
</script>
复制代码

以上我们实现了图片的初始化显示

2.实现放大缩小逻辑

咱们这里主要css通过设置图片的缩放比例,利用zoom样式来实现图片的放大缩小。

// 缩放,ratio为放大或缩小比例
handleZoom (ratio) {
    const styles = this.styles
    const abs = Math.abs(ratio)
    const changeScale = styles.ratio * abs
    const calScale = ratio > 0 ? changeScale : -changeScale
    let _scale = styles.ratio + calScale
    const max = Math.min(this.SCALE_LIMIT.MAX, _scale) // 最大比例
    const min = Math.max(this.SCALE_LIMIT.MIN, _scale) // 最小比例
    console.log(_scale, max, this.SCALE_LIMIT.MAX)
    if (_scale > max) {
        _scale = max
    } else if (_scale < min) {
        _scale = min
    }
    _scale = parseFloat(_scale)
    this.setZoomSize(_scale)
    this.$nextTick(() => {
        this.setMargin()
    })
},

复制代码

3.实现原始图片展示及复原

这里其实主要还是图片的缩放控制,代码如下

// 重置缩放比例, historyRatio记录原始大小之前的一次操作比例
handleResetZoom () {
  this.historyRatio = this.styles.ratio
  this.setZoomSize(1)
  this.$nextTick(() => {
    this.setMargin()
  })
},
// 复原缩放比例, historyRatio
handleHistoryZoom () {
  this.setZoomSize(this.historyRatio)
  this.historyRatio = 0
  this.$nextTick(() => {
    this.setMargin()
  })
},
复制代码

4.实现图片的翻转

这里主要是通过css的transform来实现的图片翻转,代码如下

// 旋转,配合样式的改变而改变
handleRotate () {
  this.styles.rotate += 90
},
复制代码

5.实现图片的复制

这里主要用的clipboard剪切板和canvas来实现的图片复制,代码如下

// 复制图片
handleCopy() {
  var canvas = document.createElement('canvas') // 创建一个画板
  let image = new Image()
  image.setAttribute("crossOrigin", 'Anonymous') //可能会有跨越问题
  image.src = this.currentImgUrl
  image.onload = () => {  // img加载完成事件
    canvas.width = image.width  //设置画板宽度
    canvas.height = image.height //设置画板高度
    canvas.getContext('2d').drawImage(image, 0, 0); //加载img到画板
    let url = canvas.toDataURL("image/png") // 转换图片为dataURL,格式为png
    this.clipboardImg(url) // 调用复制方法
  }
},
// 将图片写入到剪切板上
async clipboardImg (url) {
  try {
    const data = await fetch(url);
    const blob = await data.blob();
    await navigator.clipboard.write([
      new window.ClipboardItem({
        [blob.type]: blob
      })
    ]);
    alert('复制成功')
  } catch (err) {
    alert('复制失败')
  }
},
复制代码

6.实现图片的下载

这里就不说了,直接上代码吧

// 生成uuid
uuid (len, radix) {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyz'.split('')
  const uuid = []
  let i
  radix = radix || 16
  len = len || 8
  radix = radix || chars.length
  if (len) {
    // Compact form
    for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
  } else {
    // rfc4122, version 4 form
    let r
    // rfc4122 requires these characters
    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
    uuid[14] = '4'
    // Fill in random data. At i==19 set the high bits of clock sequence as
    // per rfc4122, sec. 4.1.5
    for (i = 0; i < 36; i++) {
      if (!uuid[i]) {
        r = 0 | Math.random() * 16
        uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r]
      }
    }
  }
  return uuid.join('')
},
// 下载
handleDownload () {
  fetch(this.currentImgUrl).then(res => res.blob().then(blob => {
    const a = document.createElement('a')
    const url = window.URL.createObjectURL(blob)
    const filename = this.uuid(8,16)
    a.href = url;
    a.download = filename;
    a.click();
    window.URL.revokeObjectURL(url);
  }));
},
复制代码

7.实现ctrl + 滚轮事件缩放以及mac触摸板滚动放大缩小

这里主要通过监听鼠标的滚轮事件,来处理的缩放时间,触摸板同理

// 滚轮事件
handleMousewheel (evt) {
  this.wheelEvent(evt)
},
// ctrl + 滚轮事件缩放
wheelEvent (e) {
  if (Math.abs(e.deltaX) !== 0 && Math.abs(e.deltaY) !== 0) return
  if (e.ctrlKey) {
    e.preventDefault()
    if (e.deltaY > 0) {
      this.handleZoom(-0.05)
    } else if (e.deltaY < 0) {
      this.handleZoom(0.05)
    }
    this.$nextTick(() => {
      this.setMargin()
    })
  }
},
复制代码

以上就是逐个功能的实现代码一一说明。这些都是对于图片操作的主要实现方式。

三、后记

1. 全部代码及组件使用说明

图片预览组件的全部代码点击查看,下载代码下来复制直接使用,二次开发也可以。组件位置如图所示

outside_default.png

其中index.vue为图片操作的各个封装,PreviewImageDialog.vue文件是做了个弹窗,用于弹窗显示图片预览内容,大家开发项目中肯定也有用到IU框架组件,到时候可以不用这个,直接引入index.vue组件, 放到dialog/modal等弹出窗中也是可以的。

2.关于dragToScroll

这里简单说下这个插件,它是用来将默认的拖拽变换成了scroll事件,在图片预览的时候,拖拽会变成滚动事件,用来查看全图。

本篇完结! 撒花! 感谢观看! 希望能帮助到你!

猜你喜欢

转载自blog.csdn.net/weixin_42981560/article/details/127255721