Skip to content

处理图片加载失败并显示备用图片

这是一份关于如何在图片资源加载失败时,优雅地切换到一张备用(默认)图片的学习笔记。笔记涵盖了从基础到更稳健的 Vue 解决方案。

核心需求

当页面上的 <img> 标签引用的图片 URL 无效或资源不存在时,我们不希望显示一个破碎的图片图标,而是自动加载并显示一张预设的备用图片。


方案一:使用 <img> 标签的原生 onerror 事件

最直接的方法是利用 <img> 标签自带的 onerror 事件处理器。当图片加载出错时,该事件会被触发。

实现方法

直接在 <img> 标签上编写 onerror 属性,当错误发生时,通过 JavaScript 修改当前图片的 src 属性为备用图片的地址。

html
<img src="path/to/image1.jpg" onerror="this.src='path/to/image2.jpg'">

存在的问题

这种方法虽然简单,但在现代前端框架(如 Vue)的开发实践中存在一些弊端:

  1. this 指向问题:在 onerror 的内联代码中,this 指向的是 <img> DOM 元素本身,而不是 Vue 组件的实例。这可能会让习惯于 Vue 上下文的开发者感到困惑,也难以访问组件中的数据(dataprops 等)。
  2. 不适用于动态数据:在实际开发中,图片地址通常是动态的(例如,从后端 API 获取的列表数据)。将逻辑直接写在模板中会使代码变得混乱且难以维护。

方案二:封装异步加载函数(Vue 推荐方案)

为了解决原生方案的弊端,我们可以采用一种更灵活、更符合 Vue 设计思想的方法:在设置 src 之前,先通过程序验证图片是否可以成功加载。

这种方法的核心是利用 JavaScript 的 Image 对象在内存中预加载图片,并通过 Promise 处理其异步的加载结果。

实现思路

  1. 创建一个名为 loadImage 的函数,该函数接收一个图片 URL 作为参数。
  2. 在函数内部,创建一个新的 Image 对象:const img = new Image()
  3. 为这个 Image 对象设置 onload (加载成功) 和 onerror (加载失败) 的事件监听器。
  4. 将函数的返回值包装成一个 Promise
    • 图片加载成功时 (onload),resolve 这个 Promise,并可以返回原始的图片 URL。
    • 图片加载失败时 (onerror),reject 这个 Promise。
  5. 在 Vue 组件中调用这个 loadImage 函数,并根据 Promise 的结果(使用 .then().catch())来动态设置真正要绑定到 <img> 标签上的响应式数据。

详细步骤与代码示例

假设我们正在使用 Vue 3 的 <script setup> 语法。

第一步:创建 loadImage 异步函数

这个函数是整个方案的核心,负责检查单个图片 URL 的可用性。

javascript
/**
 * 检查一个图片地址是否可以成功加载
 * @param {string} url 图片的 URL
 * @returns {Promise<string>} 如果加载成功,resolve 并返回原始 URL;否则 reject。
 */
function loadImage(url) {
  return new Promise((resolve, reject) => {
    // 创建一个 Image 对象,用于在内存中加载图片
    const img = new Image();

    // 当图片成功加载时触发
    img.onload = () => {
      console.log(`图片加载成功: ${url}`);
      resolve(url);
    };

    // 当图片加载失败时触发
    img.onerror = (err) => {
      console.error(`图片加载失败: ${url}`);
      reject(err);
    };

    // 设置图片的 src,这将启动加载过程
    img.src = url;
  });
}

第二步:在 Vue 组件中调用函数并处理结果

在组件中,我们定义一个响应式变量(如 imageSrc)来存储最终要显示的图片地址。然后调用 loadImage 函数,并根据结果更新 imageSrc 的值。

vue
<script setup>
import { ref } from 'vue';

// 1. 定义备用图片和目标图片地址
const primaryImageUrl = 'path/to/non-existent-image.jpg'; // 一个可能加载失败的地址
const fallbackImageUrl = 'path/to/fallback-image.jpg';    // 备用图片地址

// 2. 定义一个 ref 用于绑定到 <img> 标签的 src
const imageSrc = ref('');

// 3. 实现 loadImage 函数 (同上)
function loadImage(url) {
  // ... (代码同上)
}

// 4. 调用函数并处理结果
loadImage(primaryImageUrl)
  .then(res => {
    // 加载成功,使用原始图片地址
    imageSrc.value = res;
  })
  .catch(() => {
    // 加载失败,使用备用图片地址
    imageSrc.value = fallbackImageUrl;
  });
</script>

<template>
  <img :src="imageSrc" alt="Displayed Image">
</template>

第三步:模板绑定

如上所示,在模板中,我们使用 Vue 的 v-bind: 指令(简写为 :)将 <img>src 属性与我们的响应式变量 imageSrc 绑定。

html
<img :src="imageSrc" alt="Displayed Image">

imageSrc 的值因异步加载结果而改变时,页面上的图片也会自动更新,从而实现了无缝切换。

总结

特性方案一 (原生 onerror)方案二 (封装异步函数)
实现复杂度简单,一行代码相对复杂,需要封装函数
this 上下文指向 DOM 元素,易混淆在 Vue 组件作用域内,逻辑清晰
可维护性差,逻辑耦合在模板中,逻辑与视图分离,易于复用
动态数据处理不便非常适合,为动态数据而设计
推荐度仅适用于静态或极简单的场景强烈推荐,在 Vue 项目中的最佳实践

通过封装一个基于 PromiseloadImage 函数,我们可以构建一个健壮、可重用且逻辑清晰的图片加载失败处理方案,完美契合 Vue 的响应式和组件化开发模式。