图片加载中
Picture of the article

给Next.js自带的next/image添加Fallback能力

最终更新于2021年9月4日
#技术
#前端

Next.js自带的next/image既没有自带应对图片错误/超时的Fallback能力,也没有对应loading="lazy"行为的onLoadingStarted接口。
我们来给它添加上述两个功能吧!

背景

最近重做的blog使用了免费方便又好用的statically.io来对图片资源进行加速。然而测试中我发现statically.io在中国大陆地区会经常抽风,以至于经常出现图片加载错误或时间过长的现象。

解决这个问题的方法很多,比如可以改成用github+npm国内镜像,或者直接github+jsdelivr的方法来serve静态图片资源。但是感觉这样有点涉嫌滥用公共资源了,无法满足自己的道德洁癖。其他CDN又多多少少有我不喜欢的点(海外太慢,不方便管理文件等),所以便决定采用直接fallback回原始链接的方法解决CDN加载失败/太慢的这个问题。

需求

其实只有两个:
1、加载失败的图片自动fallback,重新加载;
2、加载过慢的图片放弃加载,直接fallback

方案

利用img元素的onload和onerror再搭配setTimeout就可以简单写出来一个具有上述两个功能的组件。由于我的网站用的是Next.js,用自带的next/image来做也是一个道理。

首先迅速实现一个能在加载错误时自动fallback的图片组件,代码如下:

imageLoader的实现方式可以参考这篇文档,这里不赘述。publicImageLoader代表正常CDN的imageLoaer,fallbackImageLoader是错误/超时后要用的imageLoader。

接下来我们给这个组件添加setTimeout。如果3000毫秒后仍在加载,则自动fallback。

那这个setTimeout应该放在哪里?我们自然会想到可以直接加到空dependecy list的useEffect上古时代的componentDidMount里即可:

把CDN的host改成localhost模拟慢加载的情况,试用一下,会发现在3秒后对应图片的src果然指向了我们要fallback去的host🎉可以收工了!


...


如果上面的方法真的就解决了问题,那也不值得我写篇文章讲这个事情。如果你用的是原本的img元素而没有使用next/image,那上面的操作确实能达到理想的效果。然而如果你像我一样用了next/image,你会发现所有在3秒之内没有滚动进入过viewport的图片全部fallback了。

为什么会出现这个情况呢?这是因为next/image默认的loading属性为"lazy",它会导致只有图片在即将进入viewport时才会开始加载。而我们的setTimeout会在组件mount后立刻开始计时,导致3秒后isLoading仍然是true,就fallback了。

解决这个问题也不难,只要让计时器从next/image触发加载动作时开始计时就可以了。然而不知为何next/image没有提供onLoadingStarted这种接口,所以我们无法判断图片是否已经开始加载。

其中一种曲线救国的方案是利用IntersectionObserver观察我们的图片组件是否已经进入viewport,一旦进入就开始计时。

我们可以写一个钩子函数,方便之后复用:

这个钩子函数接受一个被观察元素的Ref作为参数,返回一个isIntersecting表示被观察对象是否在viewport中, 返回一个observer方便外部的其他操作。

观察next/image的行为。我们发现,一旦某一张lazy loading的图片进入viewport,它就会开始加载,之后不会再有其他变化了。所以如果我们要给自制的图片组件用上useIntersectingViewport,第一次进入viewport后可以立刻observer.disconnect()并且记录下这个状态,用来表示图片是否已经开始加载。代码如下:

这样就能确保setTimeout只有在图片进入viewport后才开始计时,而且整个页面不会同时有很多observer。

测试运行,上面的问题都得到了解决🎉🎉🎉

优化和扩展

上面给出的简易代码示例还有不少可扩展的空间,如:
- 只要一个图片fallback了,就通知所有未加载图片直接使用fallback;
- 和next/image使用同样的lazyBoundary;
- 在localStorage设置过期时间,期间所有图片加载默认fallback;
- 多重CDN fallback;
...等等

IntersectionObserver本身也有很多的用法,MDN上讲的肯定比我好。

兼容性

尽管IntersectionObserver能带来比不停测量getBoundingClientRect()判断是否在viewport内的老办法好得多的性能,它仍然是一个较新的东西。如有必要可以使用这个ployfill

有意思的文章

IntersectionObserver API 使用教程
Trust is good, observation is better: Intersection Observer v2

最终更新于2021年9月4日
#技术
#前端

评论

?
提交评论
上一篇:从百済到扶桑:平野神社四神考
下一篇:Nimaga svetafor uchun aynan qizil, yashil va sariq ranglari tanlangan?