应用推广商品时,商品海报已成为标配。无论是拼多多、京东等主流电商平台,还是掘金等技术社区,都会提供生成海报功能。
关于绘制海报,我之前的文章曾介绍过用 canvas 绘制,而在实际开发中,我们更多会用 html2canvas 等成熟的库来简化开发。
今天和大家聊聊一个更细节、更实际的问题:在弱网环境和大图加载等极端情况下,如何确保完整绘制海报,并保障用户体验? 这个问题看似简单(等待所有图片加载完成再绘制),但实际需要考虑的细节还挺多。
你可以点击查看最终的下载 Demo:
我是印刻君,一位前端程序员,关注我,了解更多有温度的轻知识,有深度的硬内容。
问题分析
在用户保存海报的实际场景中,主要存在以下三种网络状态:
- 网络良好。用户点击保存后,海报立刻就能完成绘制;
- 网络普通。用户点击保存后,需要等待一段时间才能完成绘制;
- 网络较差。比如地铁、电梯等情况。用户点击保存后,海报长时间无法完成绘制。
核心解决方案
针对上述三种场景,我们需要设计一套完整的处理策略:
- 网络良好,立即下载
这种情况通常是图片已经加载过有缓存了,我们可以直接触发下载流程:
const handleClick = () => {
// ...
if (isAllImgLoaded()) {
// 图片已经加载完成,直接下载
downloadPoster();
} else {
// ...
}
};
- 网络一般,异步等待
网络一般时,我们无法预知图片加载顺序,解决这个问题需要两步:
第一步,我们需要在用户点击下载按钮后,先展示一个 loading,告诉用户图片正在下载中:
const handleClick = () => {
if (isAllImgLoaded()) {
downloadPoster()
} else {
setImgLoading(true)
// 进入等待状态
}
}
第二步,我们需要在每一张图片加载完成后,都检查一下所有图片是否均加载完成,如果加载完成且界面还显示 loading,就直接下载。
const onProductImgLoad = () => {
setProductImgLoad(true)
}
const onQrImgLoad = () => {
setQrImgLoad(true)
}
useEffect(() => {
checkAllImgLoad()
}, [bgImgLoad, qrImgLoad])
const checkAllImgLoad = () => {
if (isAllImgLoad() && imgLoading) {
downloadPoster()
}
}
- 网络较差,超时处理
网络较差时,我们不能让用户无限等待,所有需要设置合理的超时机制。可以设置 5s 作为超时的时间:
const handleClick = () => {
if (isAllImgLoaded()) {
// 图片已加载完成,直接下载
downloadPoster()
} else {
// 图片未加载完成,显示加载状态
setImgLoading(true)
// 设置 5 秒超时
timeoutId.current = setTimeout(() => {
setImgLoading(false)
alert('海报下载超时,请重新尝试')
}, 5000)
}
}
继续优化
考虑 3 种网络状态后,似乎我们的 Demo 已经很完善了。其实我们可以还可以考虑重复下载的场景:
- 成功场景:用户下载成功后,再次下载时无需重新加载图片资源
- 失败场景:用户下载失败后,再次下载时必须重新加载图片资源
这个优化,我们可以利用 React 的 key 属性来巧妙实现:
- 成功场景:用户下载成功后,海报的 key 保持不变
- 失败场景:用户下载失败后,重新生成一个海报的 key
这是因为,组件的 key 发生变化后,React 会将其视为全新的组件实例,从而销毁旧实例并创建新实例,确保图片资源重新加载。
const close = () => {
if (posterDownloadStatus !== 'success') {
// 失败时改变 key,强制重新创建组件
setPosterKey(`poster-${Date.now()}`)
}
}
return (
// ...
<Poster key={posterKey} />
)
总结
海报生成功能看似简单,却也需要多个维度进行深度思考。完善的处理,才能在保证功能稳定性的同时,为用户提供更好的使用体验。
我是印刻君,一位前端程序员,关注我,了解更多有温度的轻知识,有深度的硬内容。