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