一个动图需求引发的思考

需求

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

相关推荐
范文杰5 分钟前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪13 分钟前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪22 分钟前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy1 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom2 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom2 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom2 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom2 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom2 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI3 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端