更新记录
1.0.0(2026-01-09) 下载此版本
注意此文件直接就是源文件,可直接WEB_HT-ImagePreview将这个文件夹放入项目的components文件夹或任意位置开箱即可,项目index.vue文件中有使用按钮。开箱即用,支持vue3/js或vue3/ts。具体使用案例方式见.md。 ps:该插件主要解决需要在大图预览中展示描述信息需求。
平台兼容性
WEB_HT-ImagePreview 大图预览组件
一个功能强大、高度可定制的大图预览组件,专为 uni-app + Vue3 + TypeScript 项目设计,完美适配微信小程序。
✨ 亮点特色
- 🎨 高度可定制 - 支持自定义样式、颜色、插槽等
- 🔧 TypeScript 支持 - 完整的类型定义,开发体验更佳
- 📱 响应式设计 - 完美适配各种屏幕尺寸
- 🚀 性能优化 - 懒加载、防抖、内存优化
- 🎯 零依赖 - 基于 uni-app 原生组件,无第三方依赖
- 💡 易于集成 - 简单的 API 设计,几行代码即可使用
🚀 快速开始
安装
-
将组件文件复制到你的项目中:
src/components/WEB_HT-ImagePreview/ ├── WEB_HT-ImagePreview.vue └── README.md -
在页面中引入组件:
<script setup lang="ts"> import ImagePreview from '@/components/WEB_HT-ImagePreview/WEB_HT-ImagePreview.vue' </script>
30秒快速上手
<template>
<view>
<!-- 点击触发预览 -->
<image
v-for="(item, index) in images"
:key="index"
:src="item.url"
@click="showPreview(index)"
/>
<!-- 大图预览 -->
<ImagePreview
:visible="visible"
:image-list="images"
:initial-index="currentIndex"
close-button-position="left"
@close="visible = false"
/>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const images = ref([
{ url: 'https://example.com/image1.jpg' },
{ url: 'https://example.com/image2.jpg' }
])
const visible = ref(false)
const currentIndex = ref(0)
const showPreview = (index: number) => {
currentIndex.value = index
visible.value = true
}
</script>
🎆 功能特性
核心功能
- ✅ 图片列表预览 - 支持多张图片轮播预览
- ✅ 手势操作 - 滑动切换、点击关闭、背景关闭
- ✅ 灵活定位 - 支持指定初始显示的图片索引
- ✅ 智能适配 - 图片自适应屏幕尺寸,保持比例
UI 组件
- ✅ 关闭按钮 - 可控制显示/隐藏,支持自定义图标,支持左右位置设置,智能适配安全区域
- ✅ 计数器 - 显示当前图片位置(如:1/5)
- ✅ 指示点 - 支持显示/隐藏,自定义颜色
- ✅ 加载状态 - 图片加载中、错误状态处理
高级特性
- ✅ 插槽系统 - 默认插槽、关闭按钮插槽
- ✅ 事件系统 - 切换、关闭、加载等事件回调
- ✅ 类型安全 - 完整的 TypeScript 类型定义
- ✅ 数据灵活 - 支持多种图片字段名格式
📚 API 文档
Props 属性
| 属性名 | 类型 | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
visible |
Boolean |
false |
✅ | 控制预览组件的显示/隐藏 |
imageList |
ImageItem[] |
[] |
✅ | 图片数据数组 |
initialIndex |
Number |
0 |
❌ | 初始显示的图片索引 |
showCloseButton |
Boolean |
true |
❌ | 是否显示关闭按钮 |
showCounter |
Boolean |
true |
❌ | 是否显示图片计数器 |
showIndicatorDots |
Boolean |
false |
❌ | 是否显示轮播指示点 |
indicatorColor |
String |
'rgba(255,255,255,0.5)' |
❌ | 指示点颜色 |
indicatorActiveColor |
String |
'#ffffff' |
❌ | 激活状态指示点颜色 |
imageField |
String |
'imgUrl' |
❌ | 图片字段名,支持 imgUrl/url/src |
ImageItem 类型定义
interface ImageItem {
imgUrl?: string // 首选图片字段
url?: string // 备选图片字段
src?: string // 备选图片字段
[key: string]: any // 其他自定义字段
}
Events 事件
| 事件名 | 触发时机 | 参数类型 | 说明 |
|---|---|---|---|
@close |
点击关闭按钮或背景时 | () |
关闭预览时触发 |
@change |
切换图片时 | (index: number, image: ImageItem) |
图片切换事件 |
@imageLoad |
图片加载完成 | (index: number) |
图片加载成功回调 |
@imageError |
图片加载失败 | (index: number) |
图片加载错误回调 |
Slots 插槽
默认插槽 (#default)
用于在预览界面上显示自定义内容,支持绝对定位。
插槽参数:
currentImage: ImageItem- 当前显示的图片对象currentIndex: number- 当前图片索引(从0开始)total: number- 图片总数
<template #default="{ currentImage, currentIndex, total }">
<view class="custom-overlay">
<text>{{ currentImage.title }}</text>
<text>{{ currentIndex + 1 }}/{{ total }}</text>
</view>
</template>
关闭按钮插槽 (#close-icon)
用于自定义关闭按钮的样式和内容。
<template #close-icon>
<image src="/static/close-icon.png" class="custom-close" />
</template>
📝 使用示例
🎨 基础用法
最简单的图片预览实现:
<template>
<view class="gallery">
<!-- 图片网格 -->
<view class="image-grid">
<image
v-for="(item, index) in imageList"
:key="index"
:src="item.imgUrl"
mode="aspectFill"
class="grid-item"
@click="showPreview(index)"
/>
</view>
<!-- 大图预览 -->
<ImagePreview
:visible="previewVisible"
:image-list="imageList"
:initial-index="currentIndex"
@close="previewVisible = false"
@change="onImageChange"
/>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ImagePreview from '@/components/WEB_HT-ImagePreview/WEB_HT-ImagePreview.vue'
interface ImageItem {
imgUrl: string
title?: string
desc?: string
}
const imageList = ref<ImageItem[]>([
{
imgUrl: 'https://example.com/image1.jpg',
title: '风景照片1',
desc: '美丽的山水风光'
},
{
imgUrl: 'https://example.com/image2.jpg',
title: '风景照片2',
desc: '壮丽的日出景色'
}
])
const previewVisible = ref(false)
const currentIndex = ref(0)
const showPreview = (index: number) => {
currentIndex.value = index
previewVisible.value = true
}
const onImageChange = (index: number, image: ImageItem) => {
console.log(`当前显示第${index + 1}张图片:`, image.title)
}
</script>
<style lang="scss">
.gallery {
padding: 20rpx;
}
.image-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
.grid-item {
width: 100%;
height: 300rpx;
border-radius: 10rpx;
&:active {
opacity: 0.8;
}
}
}
</style>
🔥 高级用法
带自定义插槽、操作按钮和样式定制:
<template>
<view>
<!-- 触发按钮 -->
<button @click="showGallery">查看图片库 ({{ imageList.length }} 张)</button>
<!-- 高级预览组件 -->
<ImagePreview
:visible="visible"
:image-list="imageList"
:initial-index="currentIndex"
:show-indicator-dots="true"
:show-counter="true"
indicator-color="rgba(255, 255, 255, 0.6)"
indicator-active-color="#007aff"
@close="visible = false"
@change="onImageChange"
@imageLoad="onImageLoad"
@imageError="onImageError"
>
<!-- 自定义内容区域 -->
<template #default="{ currentImage, currentIndex, total }">
<!-- 图片信息显示 -->
<view class="image-info" v-if="currentImage.title">
<view class="title">{{ currentImage.title }}</view>
<view class="desc" v-if="currentImage.desc">{{ currentImage.desc }}</view>
<view class="meta">
<text>第 {{ currentIndex + 1 }} / {{ total }} 张</text>
<text v-if="currentImage.size">· {{ formatFileSize(currentImage.size) }}</text>
</view>
</view>
<!-- 操作按钮组 -->
<view class="action-buttons">
<button
class="action-btn"
@click="downloadImage(currentImage)"
>
📥 下载
</button>
<button
class="action-btn"
@click="shareImage(currentImage)"
>
📤 分享
</button>
<button
class="action-btn"
@click="collectImage(currentImage)"
:class="{ collected: currentImage.collected }"
>
{{ currentImage.collected ? '♥' : '♡' }} 收藏
</button>
</view>
</template>
<!-- 自定义关闭按钮 -->
<template #close-icon>
<image src="/static/icons/close.png" class="custom-close-icon" />
</template>
</ImagePreview>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ImagePreview from '@/components/WEB_HT-ImagePreview/WEB_HT-ImagePreview.vue'
interface ExtendedImageItem {
imgUrl: string
title: string
desc?: string
size?: number
collected?: boolean
}
const imageList = ref<ExtendedImageItem[]>([
{
imgUrl: 'https://example.com/image1.jpg',
title: '壮丽的山脉',
desc: '拍摄于某某国家公园,日出时分的金光洒在山峰上',
size: 2048000,
collected: false
},
{
imgUrl: 'https://example.com/image2.jpg',
title: '宁静的湖泊',
desc: '清晨的雾气轻轻浮在湖面上,如梦如幻',
size: 1756000,
collected: true
}
])
const visible = ref(false)
const currentIndex = ref(0)
const showGallery = () => {
currentIndex.value = 0
visible.value = true
}
const onImageChange = (index: number, image: ExtendedImageItem) => {
console.log('切换到:', image.title)
// 可以在这里上报浏览统计
}
const onImageLoad = (index: number) => {
console.log(`第 ${index + 1} 张图片加载完成`)
}
const onImageError = (index: number) => {
console.error(`第 ${index + 1} 张图片加载失败`)
uni.showToast({ title: '图片加载失败', icon: 'none' })
}
const downloadImage = async (image: ExtendedImageItem) => {
try {
const res = await uni.downloadFile({ url: image.imgUrl })
await uni.saveImageToPhotosAlbum({ filePath: res.tempFilePath })
uni.showToast({ title: '保存成功' })
} catch (error) {
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
const shareImage = (image: ExtendedImageItem) => {
uni.share({
provider: 'weixin',
scene: 'WXSceneSession',
type: 2,
imageUrl: image.imgUrl,
title: image.title
})
}
const collectImage = (image: ExtendedImageItem) => {
image.collected = !image.collected
uni.showToast({
title: image.collected ? '已收藏' : '取消收藏',
icon: 'none'
})
}
const formatFileSize = (bytes: number): string => {
if (bytes < 1024) return bytes + 'B'
if (bytes < 1048576) return Math.round(bytes / 1024) + 'KB'
return Math.round(bytes / 1048576 * 100) / 100 + 'MB'
}
</script>
<style lang="scss">
.image-info {
position: absolute;
bottom: 80rpx;
left: 30rpx;
right: 30rpx;
background: linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0.4));
backdrop-filter: blur(10rpx);
color: white;
padding: 30rpx;
border-radius: 20rpx;
.title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.desc {
font-size: 26rpx;
opacity: 0.9;
line-height: 1.4;
margin-bottom: 15rpx;
}
.meta {
font-size: 22rpx;
opacity: 0.7;
}
}
.action-buttons {
position: absolute;
top: 100rpx;
left: 30rpx;
display: flex;
gap: 20rpx;
.action-btn {
background: rgba(0, 0, 0, 0.6);
color: white;
border: none;
padding: 15rpx 25rpx;
border-radius: 25rpx;
font-size: 24rpx;
backdrop-filter: blur(10rpx);
&:active {
background: rgba(0, 0, 0, 0.8);
}
&.collected {
color: #ff6b6b;
}
}
}
.custom-close-icon {
width: 40rpx;
height: 40rpx;
}
</style>
支持不同图片字段名
<template>
<ImagePreview
:visible="previewVisible"
:image-list="imageList"
image-field="src"
@close="hidePreview"
/>
</template>
<script setup>
// 图片数据使用不同的字段名
const imageList = ref([
{ src: 'https://example.com/image1.jpg' },
{ src: 'https://example.com/image2.jpg' }
])
</script>
💡 最佳实践
性能优化
// 1. 图片懒加载策略
const loadImageOnDemand = (index: number) => {
const preloadRange = 2 // 预加载前后2张
const start = Math.max(0, index - preloadRange)
const end = Math.min(imageList.value.length - 1, index + preloadRange)
// 只加载可视范围内的图片
return imageList.value.slice(start, end + 1)
}
// 2. 图片压缩和缩略图
interface OptimizedImageItem {
imgUrl: string // 原图
thumbnailUrl: string // 缩略图
title: string
}
// 3. 内存释放
const clearImageCache = () => {
// 在适当时机清理图片缓存
plus?.cache?.clear()
}
响应式设计
// 适配不同屏幕尺寸
.custom-overlay {
// 小屏幕设备
@media (max-width: 750rpx) {
font-size: 24rpx;
padding: 20rpx;
}
// 大屏幕设备
@media (min-width: 751rpx) {
font-size: 28rpx;
padding: 30rpx;
}
}
// 安全区域适配
.action-buttons {
top: calc(100rpx + env(safe-area-inset-top));
padding-top: env(safe-area-inset-top);
}
错误处理
// 完整的错误处理机制
const handleImageError = (index: number) => {
console.error(`图片加载失败: ${imageList.value[index]?.imgUrl}`)
// 显示默认图片
if (imageList.value[index]) {
imageList.value[index].imgUrl = '/static/images/placeholder.jpg'
}
// 用户友好提示
uni.showToast({
title: '图片加载失败,显示默认图片',
icon: 'none',
duration: 2000
})
}
// 网络检测
const checkNetworkStatus = () => {
uni.getNetworkType({
success: (res) => {
if (res.networkType === 'none') {
uni.showToast({
title: '网络连接失败,请检查网络',
icon: 'none'
})
}
}
})
}
❓ 常见问题
Q: 图片显示不出来怎么办?
A: 检查以下几点:
-
图片路径是否正确:
// 检查图片 URL 是否可访问 console.log('Image URL:', imageItem.imgUrl) -
网络权限配置:
// manifest.json 中配置 "mp-weixin": { "setting": { "urlCheck": false } } -
域名白名单:微信小程序需要在后台配置图片域名白名单。
Q: 滑动切换不流畅怎么办?
A: 优化建议:
// 增加 GPU 加速
.image-swiper {
transform: translateZ(0);
-webkit-transform: translateZ(0);
}
// 减少复杂动画
.preview-image {
will-change: transform;
}
Q: 如何自定义加载动画?
A: 使用插槽添加加载指示器:
<template #default="{ currentImage }">
<view class="loading" v-if="!imageLoaded">
<view class="loading-spinner"></view>
<text>加载中...</text>
</view>
</template>
Q: 如何实现图片缩放功能?
A: 组件主要专注于预览,缩放功能建议使用专门的图片缩放组件或自行实现手势交互。
🛠️ 兼容性
| 平台 | 支持状态 | 注意事项 |
|---|---|---|
| 微信小程序 | ✅ 完全支持 | 需配置图片域名白名单 |
| H5 | ✅ 完全支持 | 注意跨域问题 |
| App | ✅ 完全支持 | 性能较优 |
| 支付宝小程序 | ⚠️ 需测试 | 部分样式可能需调整 |
| 百度小程序 | ⚠️ 需测试 | 部分样式可能需调整 |
版本要求
- uni-app: >= 3.0.0
- Vue: >= 3.0.0
- TypeScript: >= 4.0.0 (可选)
⭐ 感谢
如果这个组件对你有帮助,请考虑给个 Star ⭐,这对我们来说意义重大!
同时也欢迎:
- 👍 点赞和评论
- 📢 分享给其他开发者
- 💬 提供使用反馈
- 🐛 报告 Bug 和建议
📄 许可证
本项目采用 MIT License 许可证 - 查看 LICENSE 文件获取详细信息。
这意味着你可以:
- ✅ 商业使用
- ✅ 修改代码
- ✅ 分发
- ✅ 私人使用
- ✅ 专利使用
但是需要:
- 保留原作者版权声明
- 保留许可证声明
✨ 由 [webHt] 精心制作 ✨
如果这个组件对你有帮助,请考虑给个 Star ⭐

收藏人数:
下载插件并导入HBuilderX
赞赏(0)
下载 10
赞赏 0
下载 13174912
赞赏 1843
赞赏
京公网安备:11010802035340号