为什么不直接用 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>

关键属性

属性类型说明
srcstring图片地址
widthnumber/string宽度
heightnumber/string高度
aspect-rationumber/string宽高比
roundboolean圆角
lazy-loadboolean懒加载
fitstring填充方式:contain/cover/fill/none
altstringalt 文字