Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 和 jQuery 的图片容错处理方案 #9

Open
JaredCen opened this issue Jun 5, 2019 · 0 comments
Open

Vue 和 jQuery 的图片容错处理方案 #9

JaredCen opened this issue Jun 5, 2019 · 0 comments

Comments

@JaredCen
Copy link
Owner

JaredCen commented Jun 5, 2019

网页在获取图片资源的时候,经常会由于 资源路径无效网络环境不理想服务器过载 等原因导致资源加载超时或失败。为了保证良好的用户体验,我们需要对图片加载做容错处理。

最简单的方式就是通过绑定 img 元素的 error 事件,在图片加载失败时显示备用图片 error.jpg

若采用此方式,需要在 error 事件触发时取消事件的绑定,避免当 error.jpg 也加载失败时死循环拉取资源。

<img src="image-path.jpg" onerror="this.onerror=null;this.src='error.jpg'">

如果需要对 背景图片background-image) 实施容错处理呢?

如果需要在图片加载过程中加 loading 效果呢?

这个时候就需要 “替身” 发挥作用了 ——

我们可以利用一个对用户不可见的 img 元素,来验证图片资源的合法性。

jQuery 环境

额外创建一个不插入 DOM 节点树的 img 元素 “替身”,src 属性值为目标资源的 url。若 “替身” 能成功获取目标资源(load 事件触发),即让目标节点加载该资源,否则(error 事件触发)加载备用资源。

注意,重复加载同一个资源时并不会产生额外的网络消耗,浏览器会从本地缓存获取该资源。

在 jQuery 环境下,建议以插件的形式扩展,维持链式调用的特性:

$.fn.img = function(opts) {
  const $img = document.createElement('img');
  // 未加载完成时,显示 loading
  this.css('background-image', `url('${opts.loading}')`);
  $img.onload = () => {
    this.css('background-image', `url('${opts.src}')`);
  };
  $img.onerror = () => {
    this.css('background-image', `url('${opts.error}')`);
  };
  $img.src = opts.src;
  return this;
}

$('.image').img({
  src: 'success.jpg',
  error: 'error.jpg',
  loading: 'loading.gif',
});

预览地址:https://codepen.io/JunreyCen/pen/WBapBw

jQuery + 模板引擎

如果使用了模板引擎(譬如 lodash_.template 函数)的渲染方式,且希望维持 数据驱动视图 的模式时,可以参考下面的处理:

<div id="app"></div>
<template id="tpl">
  <div class="image" style="background-image: url('<%= loading %>')"></div>
  <img 
    style="display: none"
    src="<%= src %>"
    onload="$(this).siblings('.image').css('background-image', 'url(<%= src %>)')"
    onerror="$(this).siblings('.image').css('background-image', 'url(<%= error %>)')">
</template>

<script>
$(function() {
  $('#app').append(_.template($('#tpl').html())({
    src: 'success.jpg',
    error: 'error.jpg',
    loading: 'loading.gif',
  }));
});
</script>

题外话,推荐把 demo 中的处理封装成模板组件,配合 CSS 命名空间 就可以实现组件化了。鉴于 jQuery + 模板引擎 这类技术栈流行度已经没那么高了,这里不再单独提供例程。

Vue 环境

我们通过绑定 vue 指令 来实现图片的容错处理。其好处在于,每当图片资源的 src 被初始化或更新时,vue 指令都可以捕捉到变化,并容错处理后再响应式地作用于目标节点。

vue 官方文档 中对于 自定义指令 有详细的教程,这里就不多说了,直接贴代码。

<div id="app"></div>
<template id="tpl">
  <div class="image" v-img="{
    src: 'success.jpg',
    error: 'error.jpg',
    loading: 'loading.gif',
  }"></div>
</template>

<script>
  function imgHandler($el, binding) {
    const opts = binding.value;
    const $img = document.createElement('img');
    $el.style.backgroundImage = `url('${opts.loading}')`;
    $img.onload = () => {
      $el.style.backgroundImage = `url('${opts.src}')`;
    };
    $img.onerror = () => {
      $el.style.backgroundImage = `url('${opts.error}')`;
    };
    $img.src = opts.src;
  }

  Vue.directive('img', {
    inserted: imgHandler,
    update: imgHandler,
  });

  new Vue({
    el: '#app',
    template: '#tpl',
  });
</script>

踩坑点

基于上面提供的 vue demo,我们来模拟一个场景:

  • 目标节点的 src 被更新;
    譬如,src 先被赋值为 path-to-image-A.jpg,再更新为 path-to-image-B.jpg
  • 图片资源的加载速度不理想;

此时,万一资源 A 的加载速度比资源 B 还要慢,就会出现历史资源(A)把最新资源(B)覆盖掉的问题。我们稍微修改下 demo 来实现这个场景:

<div class="image" v-img="{
  // ...
  src,
  delay,
}"></div>

<script>
  function imgHandler($el, binding) {
    // ...
    if (opts.delay) {
      // 模拟图片加载延迟
      setTimeout(() => {
        $img.src = opts.src;
      }, opts.delay);
    } else {
      $img.src = opts.src;
    } 
  }

  new Vue({
    data() {
      return {
        src: '',
        delay: 0,
      };
    },
    mounted() {
      this.delay = 200;
      this.src = 'success.jpg';
      this.$nextTick(() => {
        this.delay = 0;
        this.src = 'success_2.jpg';
      });
    },
  });
</script>

或者直接看 demo 效果:https://codepen.io/JunreyCen/pen/JqmNQP

优化方案

解决方案也很简单,我们只需要把所有 “替身” 都记录下来,在更新时把上一次创建的 “替身” 清理掉。即使出现历史资源的捕获时机比最新资源的还要靠后的情况,由于历史资源的 onloadonerror 方法已经被重置,不会产生影响。

最终方案例程:https://github.com/JunreyCen/blog-demo/blob/master/image-handler/vue.2.html

PS: 文章首发于 简书 ,欢迎大家关注。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant