对于vue项目而言,图片懒加载是一个常见的图片加载方案,可以优化用户体验,而vue-lazyload则是一个广泛使用的插件。
 对于某些业务场景,我们需要监听图片加载失败事件,而后进行相应的处理,然而无论是官方文档还是网络上的解决方案都不直观甚至有错误,开发者不经过一番探索,很难知道如何处理,甚至怀疑该插件是否能监听加载失败事件。笔者经过一番探索,所幸的是,发现该问题终究是能够解决的,现将相关解决方案分享如下。

业务场景

试想这样的场景:

在img标签上,使用v-lazy绑定的图片的地址。图片的地址可能有误,图片可能因此加载失败。这时,我们需要重新给图片赋予新的正确地址,使图片加载出来。应该如何处理?

你可能持有怀疑,为什么图片地址会不正确呢?事实上,对某些技术架构而言,的确有可能,例如:

图片的源文件非常大,可能达到几十M,这时我们不会加载源文件地址,而是加载缩略图,缩略图的体积大大减小,利于我们提升用户体验,缩小等待时间。然而,缩略图是由阿里云oss提供的服务,它会自动为我们进行图片裁剪,得到缩略图,不过源文件限制在20M以内。这样,当我们加载缩略图时,如果源文件的尺寸大于20M,缩略图就不会存在,使得图片加载失败,这时我们需要加载源文件地址。

当上述情况发生时,我们就需要监听vue-lazyload的图片加载失败事件,重新请求源文件地址了。

常见解决方案分析
1.使用filter选项,修改源文件地址

在官方文档中,做了示例,可以通过filter选项修改图片地址。问题在于,该方案无法检测图片源文件的尺寸,也无法判断指定图片地址是否存在。如果要通过请求探测指定地址的图片是否存在,则会使得加载时间大大加长,不利于用户体验。并且针对全局生效,很难针对各种情况做合理配置。

2.使用adapter选项,监听error事件

我们可以通过adapter的error选项,监听到图片加载失败。但是当这个时候,我们无论做什么,都无法使图片重新加载,即便我们修改了图片源地址,一切已成定局,因此行不通。同样该选项针对全局生效,难以做个性化配置。

3.使用vm.$Lazyload.$onvm.$Lazyload.$once监听error事件

对于这两个选项而言,我们可以在vue的组件实例中,通过this.$Lazyload.$oncethis.$Lazyload.$on来进行监听error事件,因为他们已经挂载到Vue的原型上。
然而,经笔者实测,这两个事件虽然会触发,然而触发的时机和次数却与预期并不相同。当笔者对40张图片使用了懒加载时,即便只有几张图片地址错误,这两个事件也会反复触发,达到几十次甚至于几百次。因此,大胆断言,这两个事件的设计并不是为了监听图片加载失败或成功来进行相关处理。

(当然,不排除笔者没有领会设计者意图的可能,如果你使用这两个事件成功了,请告知。)

推荐解决方案:dispatchEvent + @error

我们只需要做两个地方的处理,就能达到预期。首先是对vue-lazyload做全局配置,开启dispatchEvent选项:

Vue.use(VueLazyload, {
  loading: require("@/assets/images/lazy-loading.svg"),
  error: require("@/assets/images/lazy-loading.svg"),
  dispatchEvent: true,
  attempt: 1
});

在以上情况下,我们将dispatchEvent设置为true,表明开启img的原生dom事件,默认情况下,该选项是false,是不会开启原生dom事件的。这样,我们现在可以监听img的原生error事件:

<template>
  <div class="card">
    <div class="card-img">
      <img
        v-lazy="thumbImgUrl"
        alt=""
        @click="onPreview"
        v-if="showThumb"
        @error="handleLazyErr"
      />
      <img v-lazy="data.imgUrl" alt="" @click="onPreview" v-else />
      <el-image-viewer
        v-if="showViewer"
        :on-close="closeViewer"
        :url-list="[previewImgUrl]"
      />
    </div>
    <div class="card-name">{{ data.name }}</div>
  </div>
</template>

<script>
import ElImageViewer from "element-ui/packages/image/src/image-viewer";

export default {
  name: "",
  components: { ElImageViewer },
  props: {
    data: {
      type: Object,
      default() {
        return {};
      }
    }
  },
  data() {
    return {
      showThumb: true,
      showViewer: false
    };
  },
  methods: {
    onPreview() {
      this.showViewer = true;
    },
    // 关闭查看器
    closeViewer() {
      this.showViewer = false;
    },
    // 监听加载失败
    handleLazyErr() {
      setTimeout(() => {
        this.showThumb = false;
      }, 2000);
    }
  },
  computed: {
    // 原图太大,显示缩略图
    thumbImgUrl() {
      return `${this.data.imgUrl}?x-oss-process=image/resize,h_355,w_268`;
    },
    previewImgUrl() {
      let url;
      if (this.showThumb) {
        url = `${this.data.imgUrl}?x-oss-process=image/resize,w_1920`;
      } else {
        url = this.data.imgUrl;
      }
      return url;
    }
  }
};
</script>

现在,在以上案例中,我们通过监听到图片加载失败触发原生的error事件,来加载图片源文件,这样就比较巧妙的实现了失败时显示原图的需求。

本文完。

Logo

前往低代码交流专区

更多推荐