一个动图需求引发的思考

需求

移动端页面,原本是一个大背景图,现在有一部分需要做成动画,而这个动画大概比较复杂,所以由设计来做成 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 实现那样的动画需要多大的成本,性能又如何呢?

相关推荐
Myli_ing17 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维35 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
雯0609~1 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ1 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five2 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序2 小时前
vue3 封装request请求
java·前端·typescript·vue