uniapp H5预览图片组件

发布于:2025-09-03 ⋅ 阅读:(20) ⋅ 点赞:(0)

前言

uniapp的预览API, uni.previewImage预览长图在H5环境下长图会模糊

如下图:
在这里插入图片描述
手指可以撑开放大, 但会模糊
解决方法:
使用自定义全局预览接口
global-preview-image/index.vue

<template>
  <view v-if="show" class="preview-mask" @click.stop="hide">
    <!-- 图片预览遮罩层 -->
    <swiper
      class="preview-swiper"
      :current="current"
      duration="300"
      @change="swiperChange"
    >
      <!-- 遍历图片地址列表,生成轮播项 -->
      <swiper-item v-for="(item, index) in urls" :key="index">
        <movable-area class="movable-area">
          <movable-view class="movable-view" scale scale-min="1">
            <scroll-view scroll-y class="preview-item">
              <view
                class="preview-image-box"
                :style="{
                  alignItems:
                    imagesHeight[index] >= windowHeight
                      ? 'flex-start'
                      : 'center',
                }"
              >
                <image
                  class="preview-image"
                  :class="[`preview-image-${index}`]"
                  :src="item"
                  :lazy-load="true"
                  mode="widthFix"
                  @click.stop="hide"
                  @load="load(index)"
                />
              </view>
            </scroll-view>
          </movable-view>
        </movable-area>
      </swiper-item>
    </swiper>
    <!-- 显示当前图片索引和总数 -->
    <text class="preview-index">{{ current + 1 }}/{{ urls.length }}</text>
  </view>
</template>

<script>
export default {
  data() {
    return {
      show: false, // 控制预览组件是否显示
      urls: [], // 图片地址列表
      loadSuccess: [], // 已加载成功的图片索引列表
      current: 0, // 当前显示的图片索引
      windowHeight: 0, // 设备高度
      imagesHeight: [], // 图片高度列表
    }
  },
  mounted() {
    /**
     * 组件挂载时获取系统窗口高度信息
     */
    uni.getSystemInfo({
      success: (res) => {
        this.windowHeight = res.windowHeight
      },
    })
  },
  methods: {
    /**
     * 打开图片预览组件
     * @param {Object} options - 配置参数
     * @param {Array} options.urls - 图片地址数组
     * @param {Number} options.current - 当前显示的图片索引
     */
    open({ urls = [], current = 0 }) {
      uni.showLoading({
        title: "加载中...",
      })
      // 如果urls不为空且current为字符串,则查找current在urls中的索引位置
      if (urls.length > 0 && typeof current === "string") {
        current = urls.findIndex((item) => item === current)
        current = current >= 0 ? current : 0
      }
      // 设置组件数据
      this.urls = urls || []
      this.loadSuccess = []
      this.imagesHeight = Array(urls.length).fill(0)
      this.current = current || 0
      this.show = true
    },

    /**
     * 关闭图片预览组件
     */
    hide() {
      this.show = false
      uni.hideLoading()
      setTimeout(() => {
        this.urls = []
      }, 300)
    },

    /**
     * 图片加载完成回调
     * @param {Number} i - 图片索引
     */
    load(i) {
      if (!this.loadSuccess.includes(i)) this.loadSuccess.push(i)
      // 如果当前图片已加载完成且为当前显示图片,则隐藏加载提示
      if (this.loadSuccess.includes(i) && this.current == i) {
        uni.hideLoading()
      }
      // 获取元素
      const query = uni.createSelectorQuery().in(this)
      // 获取元素高度
      query
        .select(".preview-image-" + i)
        .boundingClientRect((res) => {
          this.$set(this.imagesHeight, i, res.height)
        })
        .exec()
    },

    /**
     * 轮播图切换事件处理
     * @param {Object} e - 事件对象,包含 detail.current 表示当前切换到的图片索引
     */
    swiperChange(e) {
      // 根据切换后的图片索引判断是否显示加载提示
      if (this.loadSuccess.includes(e.detail.current)) {
        uni.hideLoading()
      } else {
        uni.showLoading({
          title: "加载中...",
        })
      }
      this.current = e.detail.current
    },
  },
}
</script>

<style scoped>
/* 预览遮罩层样式 */
.preview-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.9);
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 轮播图容器样式 */
.preview-swiper {
  width: 100%;
  height: 100%;
}

/* 预览项容器样式 */
.preview-item {
  width: 100vw;
  height: 100vh;
}
.preview-image-box {
  display: flex;
}
/* 图片及可移动视图相关样式 */
.preview-image,
.preview-image-box,
.movable-view,
.movable-area {
  width: 100%;
  height: 100%;
}

/* 可移动视图样式 */
.movable-view {
}

/* 预览图片样式 */
.preview-image {
  display: block;
}

/* 图片索引文本样式 */
.preview-index {
  position: absolute;
  bottom: 60rpx;
  color: #fff;
  font-size: 32rpx;
}
</style>

挂载body上
global-preview-image/index.js

import Vue from 'vue'
import PreviewImage from './index.vue'

const PreviewImageConstructor = Vue.extend(PreviewImage)
let instance

/**
 * 初始化预览图片实例
 * 创建PreviewImage组件实例并将其挂载到DOM中
 */
function initInstance() {
  instance = new PreviewImageConstructor({
    el: document.createElement('div')
  })
  document.body.appendChild(instance.$el)
}

/**
 * 预览图片函数
 * @param {Object|String} options - 预览配置选项,如果是字符串则作为单张图片URL处理
 * @param {Array} options.urls - 图片URL数组
 * @param {Number} options.current - 当前显示图片的索引
 * @param {Boolean} options.loop - 是否可以循环预览
 * @param {Function} options.success - 接口调用成功的回调函数
 * @param {Function} options.fail - 接口调用失败的回调函数
 * @param {Function} options.complete - 接口调用结束的回调函数
 */
function previewImage(options) {
  // 如果实例不存在,则初始化实例
  if (!instance) {
    initInstance()
  }

  // 如果传入的是字符串,则转换为包含单个URL的对象格式
  if (typeof options === 'string') {
    options = {
      urls: [options]
    }
  }

  // 调用实例的open方法打开图片预览
  instance.open(options)
}

/**
 * 插件安装函数
 * @param {Object} Vue - Vue构造函数
 */
function install(Vue) {
  // 将previewImage方法挂载到Vue原型上,使所有Vue实例都可以访问
  Vue.prototype.$previewImage = previewImage

  // 挂载到uni对象
  if (typeof uni !== 'undefined') {
    uni.previewImage = previewImage
  }
}

export default {
  install,
  previewImage
}

main.js引入

...
// #ifdef H5
import PreviewImage from '@/components/global-preview-image'
// #endif
...
// #ifdef H5
Vue.use(PreviewImage)
// #endif
...

网站公告

今日签到

点亮在社区的每一天
去签到