[VUE | UI组件] 图片预览

本文介绍了一个基于Element-UI的图片预览组件,支持拖拽、缩放、上下翻页及下载等功能,利用file-saver.js实现图片下载,适用于多图片展示场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果图

 

依赖

1. 使用 element-ui 中的 icon (可自行修改)

2. file-saver.js

 

功能

支持拖拽、放大缩小、上一张、下一张、点击百分比复位。

 

代码

file.js:

import FileSaver from 'file-saver'

export function download(url, name) {
  const xhr = new XMLHttpRequest()
  url = url.replace('http:', '')
  xhr.open('GET', url, true)
  xhr.responseType = 'blob'
  xhr.onload = () => {
    if (xhr.status === 200) {
      // 将图片文件用浏览器中下载
      FileSaver.saveAs(xhr.response, name)
      // 将图片信息放到Img中
      console.log(window.URL.createObjectURL(xhr.response))
    }
  }
  xhr.send()
}

页面代码:

<template>
  <div v-if="value"
    class="image-preview"
    @mousewheel="onMousewheel">
    <!-- 标题栏 -->
    <div v-show="currentName"
      class="image-preview__title-bar">
      <span class="image-preview__name">{{currentName}}</span>
    </div>
    <!-- 关闭按钮 -->
    <div class="image-preview__close"
      @click="onActionClick('close')">
      <span class="el-icon-close icon-close"></span>
    </div>
    <!-- 图片显示区 -->
    <div class="image-preview__image"
      :class="{'image-preview__image--move':isMoving}"
      :style="{'background-image':`url(${currentUrl})`,
          top:`${y}px`,
          left:`${x}px`,
          transform:`scale(${scale / 100})`}"
      @mousedown="onImageMousedown"></div>
    <!-- 操作栏 -->
    <div class="image-preview__action">
      <div class="el-icon-zoom-in action"
        @click="onActionClick('zoomIn')"></div>
      <div class="action"
        style="width:76px;padding:15px 0;"
        @click="onActionClick('reset')">{{scaleStr}}</div>
      <div class="el-icon-zoom-out action"
        @click="onActionClick('zoomOut')"></div>
      <div class="el-icon-download action"
        @click="onActionClick('download')"></div>
    </div>
    <!-- 上、下一页 -->
    <div v-if="hasPrevious"
      class="image-preview__previous"
      @click="onPreviousClick"></div>
    <div v-if="hasNext"
      class="image-preview__next"
      @click="onNextClick"></div>
  </div>
</template>

<script>
import { download } from '../../utils/file.js'
export default {
  props: {
    value: Boolean,
    // Array(String) 图片地址列表
    list: { type: Array, default: () => [] },
    // 文件名列表
    nameList: { type: Array, default: () => [] },
    // 当前图片地址
    index: Number
  },

  computed: {
    isMultiple() {
      return this.list.length > 1
    },

    currentUrl() {
      return this.list.length && this.list.length > this.mIndex
        ? this.list[this.mIndex]
        : ''
    },

    currentName() {
      return this.nameList.length && this.nameList.length > this.mIndex
        ? this.nameList[this.mIndex]
        : ''
    },

    scaleStr() {
      return this.scale + '%'
    },

    hasPrevious() {
      return this.mIndex > 0
    },

    hasNext() {
      return this.mIndex < this.list.length - 1
    }
  },

  data() {
    return {
      mIndex: 0,
      isMoving: false,
      scale: 100,
      x: 0,
      y: 0,
      moveX: 0,
      moveY: 0
    }
  },

  methods: {
    onPreviousClick() {
      if (this.hasPrevious) {
        this.reset()
        this.mIndex--
      }
    },

    onNextClick() {
      if (this.hasNext) {
        this.reset()
        this.mIndex++
      }
    },

    reset() {
      this.scale = 100
      this.x = 0
      this.y = 0
    },

    onImageMousedown(e) {
      if (e.button !== 0) return
      this.isMoving = true
      this.moveX = e.clientX
      this.moveY = e.clientY
      window.addEventListener('mousemove', this.onImageMousemove, false)
      window.addEventListener('mouseup', this.onImageMouseup, false)
    },

    onImageMousemove(e) {
      this.x -= this.moveX - e.clientX
      this.y -= this.moveY - e.clientY
      this.moveX = e.clientX
      this.moveY = e.clientY
    },

    onImageMouseup(e) {
      this.isMoving = false
      window.removeEventListener('mousemove', this.onImageMousemove, false)
      window.removeEventListener('mouseup', this.onImageMouseup, false)
    },

    onActionClick(action) {
      switch (action) {
        case 'close':
          this.reset()
          this.$emit('input', false)
          break
        case 'reset':
          this.reset()
          break
        case 'zoomIn':
          if (this.scale < 200) {
            this.scale += 10
          }
          break
        case 'zoomOut':
          if (this.scale > 50) {
            this.scale -= 10
          }
          break
        case 'download':
          download(this.currentUrl, this.currentName || '图片.jpg')
          break
        default:
          break
      }
    },

    onMousewheel(e) {
      this.onActionClick(e.deltaY < 0 ? 'zoomIn' : 'zoomOut')
    }
  },

  watch: {
    index: {
      immediate: true,
      handler(val) {
        this.mIndex = val
      }
    },
    value(val) {
      document.body.style.overflow = val ? 'hidden' : ''
    }
  },

  beforeDestroy() {
    window.removeEventListener('mousemove', this.onImageMousemove, false)
    window.removeEventListener('mouseup', this.onImageMouseup, false)
    document.body.style.overflow = ''
  }
}
</script>

<style lang="scss" scoped>
@import '../../scss/index.scss';
* {
  user-select: none;
}
.image-preview {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 999999;
  background-color: rgba(0, 0, 0, 0.75);
  pointer-events: all;
  &__title-bar {
    position: absolute;
    top: 0;
    left: 0;
    height: 50px;
    width: 100%;
    color: #fff;
    z-index: 1;
    background-color: rgba(0, 0, 0, 0.25);
    span {
      line-height: 50px;
      margin-left: 20px;
    }
  }
  &__close {
    position: absolute;
    top: 0;
    right: 0;
    width: 50px;
    height: 50px;
    font-size: 24px;
    z-index: 9;
    cursor: pointer;
    color: #fff;
    background-color: rgba(0, 0, 0, 0.4);
    transition-duration: 0.3s;
    &:hover {
      background-color: rgba(255, 255, 255, 0.4);
    }
    .icon-close {
      position: absolute;
      font-weight: 600;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
  }
  &__image {
    position: absolute;
    width: 100%;
    height: 100%;
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
    cursor: grab;
    transition: transform 0.5s;
    &--move {
      cursor: grabbing;
    }
  }
  &__action {
    position: absolute;
    bottom: 0;
    left: 50%;
    display: flex;
    user-select: none;
    padding: 4px;
    transform: translateX(-50%);
    color: #fff;
    background-color: rgba(0, 0, 0, 0.4);
  }

  &__previous {
    position: absolute;
    top: 50%;
    left: 20px;
    cursor: pointer;
    transform: translateY(-50%);
    width: 0;
    height: 0;
    border-width: 30px 30px 30px 0;
    border-style: solid;
    border-color: transparent #fff transparent transparent;
    transition-duration: 0.3s;
    opacity: 0.5;
    &:hover {
      opacity: 1;
      border-right-color: $primary;
    }
  }
  &__next {
    position: absolute;
    top: 50%;
    right: 20px;
    cursor: pointer;
    transform: translateY(-50%);
    width: 0;
    height: 0;
    border-width: 30px 0 30px 30px;
    border-style: solid;
    border-color: transparent transparent transparent #fff;
    transition-duration: 0.3s;
    opacity: 0.5;
    &:hover {
      opacity: 1;
      border-left-color: $primary;
    }
  }
}

.action {
  cursor: pointer;
  font-size: 24px;
  line-height: 24px;
  padding: 15px;
  text-align: center;
  &:hover {
    background-color: rgba(255, 255, 255, 0.4);
  }
}
</style>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值