一个动图需求引发的思考

需求

移动端页面,原本是一个大背景图,现在有一部分需要做成动画,而这个动画大概比较复杂,所以由设计来做成 GIF。于是重新切图,将那部分不再保留在背景图上,用 GIF 来展示。但问题是 GIF 太大,于是产品问能不能在弱网时先展示静态图。

vue img @load v-if

于是我就在 GIF 完成加载前展示 jpg 图片,因为是在 Vue 中,所有直接使用 img 的 load 事件就可以了,但是没起作用:

js 复制代码
<img
  v-if="isLoad"
  src="path-to-your-gif.gif"
  alt="动图"
  @load="isLoad = true"
/>
<img
  v-else
  src="path-to-your-jpg.jpg"
  alt="静态图片"
/>

new Image()

利用 new Image() 创建一个新的 image 对象,并开始加载 GIF,当加载完成(onload 事件触发)时,将展示的图片 src 更新为 GIF 的 url,重新渲染 img 元素。

js 复制代码
<img
  :src="imgSrc"
  alt=""
/>

const imgSrc = ref("");
const img = new Image();
img.src = "path-to-your-jpg.jpg";
img.onload = () => {
  imgSrc.value = "path-to-your-gif.gif";
};

Vue img @load v-show

后来才发现,第一种方式不生效的原因是使用了 v-if,当使用 v-if 时,页面渲染就没有 v-else,所以 GIF 加载完成时,也无法展示。因为我一开始背景图上还有图片,所以没有发现。

js 复制代码
<img
  v-show="isLoad"
  src="path-to-your-gif.gif"
  alt="动图"
  @load="isLoad = true"
/>
<img
  v-show="!isLoad"
  src="path-to-your-jpg.jpg"
  alt="静态图片"
/>

background

利用背景图,也可以做占位图。但是它的问题是,在加载的过程中,图片从上到下逐渐的覆盖背景图,有点奇怪。不过用背景图也有好处,那就是在图片加载失败时,仍然能展示静态图。

css 复制代码
.img {
  background-image: url("path-to-your-jpg.jpg");
  background-size: 100%;
  background-position: center;
  background-repeat: no-repeat;
}

GIF

后来发现,其实 GIF 图是逐帧加载的,所以默认一开始就会显示静态图,并不需要再用一张静态图占位。我搜了一下,可能大部分浏览器是支持这样的。

也就是说,完全不做任何处理。

js 复制代码
<img
  src="path-to-your-gif.gif"
  alt="动图"
/>

但是这种方式有它的缺点,和以上占位图的方式比较:

  • 占位图:在完全加载完成之前,显示 jpg 图片,但是用户可能不知道这是个动图。而且 jpg 图片和 GIF 替换时会有一闪的感觉。
  • 直接 GIF:逐帧加载,在完全加载完成之前,会显示一个比较卡顿的过程。

考虑到如果大多数情况下,用户网速 OK,那似乎直接 GIF 会更好一点。

第三方库

简单搜了下,vue-load-image 短小精悍、方便易用。它的功能很简单,就是在图片加载完成之前显示 loader,加载失败显示失败信息。也大概看了下源码,其本质也是使用 new Image 加载图片,onload 事件、onerror 事件时分别改变状态 status:

js 复制代码
<template>
  <div class="vue-load-image">
    <slot v-if="status === 'loaded'" name="image" />
    <slot v-else-if="status === 'failed'" name="error" />
    <slot v-else-if="status === 'loading'" name="preloader" />
  </div>
</template>

createLoader() {
  this.destroyLoader()
  this.img = new Image()
  this.img.onload = this.handleLoad
  this.img.onerror = this.handleError
  this.img.crossOrigin = this.crossOrigin
  this.img.src = this.src
},
handleLoad() {
  this.destroyLoader()
  this.status = Status.LOADED
  this.$emit('onLoad')
},
handleError(error) {
  this.destroyLoader()
  this.status = Status.FAILED
  this.$emit('onError', error)
},

还有更多第三方库,比如:vue-lazyload,功能更多一些。

总结

前端就是关注用户体验,前端就是关注各种细节,实际工作中,每一个小小的问题,如果深入探索,就能发现更多本质的东西。对于初学者来说,不要放过任何一个你不能理解的问题,对于职场人来说,被迫去做去思考你不感兴趣、你觉得不重要的事情,也许很快你就会有新的收获。

不过对于 GIF 图为什么那么大,我还是不理解,也没有压缩的空间了,这个也需要深入的学习制作一下。

还有是不是并不需要 GIF,用 CSS 可以实现呢?用 CSS 或 js 实现那样的动画需要多大的成本,性能又如何呢?

相关推荐
m0_7482361129 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo61741 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489443 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-8 小时前
验证码机制
前端·后端
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js