绘制电商海报,如何处理弱网与大图等极端情况

529 阅读4分钟

应用推广商品时,商品海报已成为标配。无论是拼多多、京东等主流电商平台,还是掘金等技术社区,都会提供生成海报功能。

举例

关于绘制海报,我之前的文章曾介绍过用 canvas 绘制,而在实际开发中,我们更多会用 html2canvas 等成熟的库来简化开发。

今天和大家聊聊一个更细节、更实际的问题:在弱网环境和大图加载等极端情况下,如何确保完整绘制海报,并保障用户体验? 这个问题看似简单(等待所有图片加载完成再绘制),但实际需要考虑的细节还挺多。

你可以点击查看最终的下载 Demo:

我是印刻君,一位前端程序员,关注我,了解更多有温度的轻知识,有深度的硬内容。

问题分析

在用户保存海报的实际场景中,主要存在以下三种网络状态:

  1. 网络良好。用户点击保存后,海报立刻就能完成绘制;
  2. 网络普通。用户点击保存后,需要等待一段时间才能完成绘制;
  3. 网络较差。比如地铁、电梯等情况。用户点击保存后,海报长时间无法完成绘制。

核心解决方案

针对上述三种场景,我们需要设计一套完整的处理策略:

  • 网络良好,立即下载

这种情况通常是图片已经加载过有缓存了,我们可以直接触发下载流程:

  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} />
)

总结

海报生成功能看似简单,却也需要多个维度进行深度思考。完善的处理,才能在保证功能稳定性的同时,为用户提供更好的使用体验。

我是印刻君,一位前端程序员,关注我,了解更多有温度的轻知识,有深度的硬内容。