为什么不直接用 img?
移动端图片展示有几个常见痛点:
- 图片很大,需要 懒加载(进入视口才加载)
- 加载中要显示 占位图/Loading 动画
- 加载失败要显示 错误占位
- 点击要能 大图预览(支持缩放滑动)
van-image + showImagePreview 一站式解决。
基础用法
<van-image
width="100"
height="100"
src="https://example.com/photo.jpg"
fit="cover"
/>
// 圆角圆形头像
<van-image round width="48" height="48" :src="avatar" />
// 懒加载
<van-image lazy-load width="200" height="200" :src="imgUrl" />
三种状态自定义
<van-image :src="imgUrl" width="200" height="200">
<template #loading>
<div class="loading">
<van-loading type="spinner" size="24" />
</div>
</template>
<template #error>
<div class="error">
<van-icon name="photo-o" size="40" />
<p>加载失败</p>
</div>
</template>
</van-image>
<style>
.loading, .error {
display: flex;
align-items: center;
justify-content: center;
width: 100%; height: 100%;
background: #f7f8fa;
color: #969799;
}
.error p { margin-top: 4px; }
</style>
点击大图预览
<template>
<div class="gallery">
<van-image
v-for="(img, index) in images"
:key="index"
:src="img"
width="100"
height="100"
fit="cover"
@click="preview(index)"
/>
</div>
</template>
<script setup>
import { showImagePreview } from "vant";
const images = [
"https://example.com/1.jpg",
"https://example.com/2.jpg",
"https://example.com/3.jpg",
];
const preview = (index) => {
showImagePreview({
images,
startPosition: index,
closeable: true,
});
};
</script>
<style scoped>
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.gallery .van-image {
width: 100%;
aspect-ratio: 1;
}
</style>
大图预览支持:双指缩放、左右滑动切换、关闭按钮、长按保存。
实战:商品图片网格 + 懒加载 + 预览
<template>
<van-list v-model:loading="loading" :finished="finished" @load="onLoad">
<div class="product-grid">
<div
v-for="item in list"
:key="item.id"
class="product-item"
@click="previewImage(item.images, 0)"
>
<van-image
width="100%"
aspect-ratio="1"
:src="item.cover"
lazy-load
fit="cover"
/>
<div class="info">
<h3>{{ item.name }}</h3>
<p class="price">¥{{ item.price }}</p>
</div>
</div>
</div>
</van-list>
</template>
<script setup>
import { ref } from "vue";
import { showImagePreview } from "vant";
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const previewImage = (images, index) => {
showImagePreview({ images, startPosition: index });
};
</script>
<style scoped>
.product-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
padding: 8px;
}
.product-item { background: #fff; border-radius: 8px; overflow: hidden; }
.product-item .info { padding: 8px; }
.product-item h3 { font-size: 14px; margin: 0; }
.product-item .price { color: #ee0a24; font-weight: bold; }
</style>
关键属性
| 属性 | 类型 | 说明 |
|---|---|---|
| src | string | 图片地址 |
| width | number/string | 宽度 |
| height | number/string | 高度 |
| aspect-ratio | number/string | 宽高比 |
| round | boolean | 圆角 |
| lazy-load | boolean | 懒加载 |
| fit | string | 填充方式:contain/cover/fill/none |
| alt | string | alt 文字 |