React 实现图片上传和展示功能

13,089 阅读5分钟

基本介绍 📝

  • 最近完成了产品提出的一个类似微信发图文朋友圈的需求(以下实现均基于 React + mobx 项目):
    • 1⃣️ 通过➕号按钮选择系统图片,最多可选择9张图,选择的图片以九宫格形式展示
    • 2⃣️ 选择图片后开始上传,图片大小限制为 20 MB,超过限制不上传且不展示
    • 3⃣️ 展示图片上传过程中的 loading 状态
    • 4⃣️ 图片上传失败可点重试按钮重新上传
    • 5⃣️ 可以删除选中的图片(若上传中删除前还需中断 xhr 请求)
    • 6⃣️ 发布后的图片内容按照一定规则展示(单图片、四宫格和九宫格)
  • 历尽千辛万苦终于实现了以上的功能,具体效果如下:
    • 上传图片页面: show.gif
    • 展示页面(单图,按照单图规则展示): image.png
    • 展示页面(四图,四宫格):
      image.png
    • 展示页面(多图,九宫格): image.png
  • 该需求涉及到许多零散的知识点,遂以此文做总结记录,下面将介绍思路和具体的实现代码。

思路分析 🤔

选择图片

  • 选择图片可以用设置 type 属性为 file 值的 input 标签:
<input type="file" accept="image/gif,image/jpeg,image/jpg,image/png" multiple />
  • 通过设置其 multiple 属性和 accept 属性可以使得 input 标签支持多选和仅支持选择 gifjpg/jpegpng 类型的文件。 image.png
  • 当点击标签时,则可以从文件系统中选择照片。我们的需求是提供一个➕号按钮作为选择图片的入口,这里可以使用 ref 获取该 input 元素,当点击➕号按钮时触发 input 的点击事件,即可以调用系统的相册进行图片选取。

限制图片大小和数量、预览本地图片

  • input 元素的 change 事件被触发时,可以通过 input.files 获取到当前选中文件的 FileList 类数组对象,当选择两张图片后,打印 FileList 对象: image.png
  • 可以看到每个 File 对象都有 size 字段,可以用来判断图片大小(字节)是否超过限制(20MB20 * 1024 * 1024)。
  • 区别于原生 APP,web 网页无法阻止用户选择超过 9 张图片,只能在代码中做限制,当总选择的图片超过 9 张则做超出数量提示和截断处理(取前 9 张图)。
  • 由于可以获取到每张图片的 File 对象,则可以使用 URL.createObjectURL() 创建一个对象 URL,可以作为 img 标签的 src 值进行传入,则能实现本地图片的预览功能。
  • 需要注意的是,当不再需要这些使用 URL.createObjectURL() 创建的 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放,让浏览器知道不用在内存中继续保留对这个文件的引用了(可设置图片的load事件处理器来释放对象URL,当图片加载完成之后对象URL就不再需要了)。
    const localUrl = URL.createObjectURL(flieList[0])
    // localUrl 可作为图片的源
    <img src={localUrl} alt='' />
    
    // 无需使用时释放内存
    window.URL.revokeObjectURL(localUrl);
    

上传图片和失败重试

  • 通过前面的分析,我们可以获取到每张图片的 File 对象,我们就可以通过新建一个 FromData 对象,并将 File 对象添加到 FromData 对象中,使用 XMLHttpRequest 来处理无刷新上传图片。
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', fileData);
    xhr.open('POST', UPLOAD_IMAGE_URL); // 需要后端提供上传 URL
    xhr.send(formData);
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          // handle response
        
        } else {
          // handle error
        }
      }
    }
    
  • 上传失败时则重新执行以上上传逻辑。

图片展示

  • 发布后的图片需要按照一定的规则展示:
    • 单张图使用微信朋友圈图片的显示规则,需要获取图片原本的宽高,因此需要后端在上传图片完成后返回图片的原始宽高。
    • 四张图时使用四宫格
    • 其他数量的图片以九宫格形式展示
  • 为了使得图片在保持其宽高比的同时填充 img 元素的整个内容框,我们通过可以设置 img 元素的 object-fit CSS 属性值为 cover 来实现。
  • 下面将介绍整个图片上传和展示功能的具体实现代码。

具体实现 🧐

上传图片页面相关组件

  • 这部分将实现创建页的上传图片组件,当添加图片张数少于9张时展示➕号按钮,图片添加后开始上传图片,图片上传过程中展示 loading 状态,添加的图片可以被删除,即效果如下: show.gif

  • 首先是 UploadImage 组件,代码如下: image.png image.png

  • UploadImage 组件中使用了 ImageItem 子组件,封装了上传、删除、重试逻辑,相关代码如下: image.png

  • 样式文件代码如下: image.png

  • 可以看到,在使用 UploadImage 组件时需要传入 imageList 和及其更新方法 updateImageList。接下来我们看使用 updateImageList 组件的最外层组件 PostEditorView 的相关代码:

    image.png

  • 上面的代码可以看到,可以写一些自定义 hooks 来减小 PostEditorView 组件的大小:

    • useConfirmModal 封装提交内容时的弹框逻辑
    • useAlertModal 封装图片上传失败时的弹框逻辑
    • useUploadInput 封装选择图片相关逻辑
  • 至此,上传图片页面相关组件的代码均已展示完成,建议结合思路阅读相关代码,仅提供思路,部分无关代码已删除。

展示图片页面组件

  • 这部分将实现内容页的展示图片组件。BlogImageList 组件相关代码如下: image.png

  • 样式文件代码如下: image.png

  • 在展示页面,只需要将带图片id、原始宽高的图片列表传给组件即可。

  • PS:单张图使用微信朋友圈图片的显示规则,如下图所示,假设图片宽高比 X,当图片 1:1 时显示的尺寸为 Y * Y(在项目中 Y = 180,L = 4):

    image.png

总结 👀

  • 本文主要整理了在 React 项目实现图片上传和展示功能的相关思路和代码,涉及到使用 input[type=file] 标签来唤起系统选择图片功能,使用 URL.createObjectURL() 对 File 对象进行处理实现本地图片预览,以及使用 xhr 实现无刷新图片上传等知识点。

以上内容如有遗漏错误,欢迎留言 ✍️指出,一起进步💪💪💪

如果觉得本文对你有帮助,🏀🏀留下你宝贵的 👍

参考资料

  1. 前端图片上传那些事儿
  2. 在web应用程序中使用文件
  3. MDN - URL.createObjectURL()
  4. 微信朋友圈图片的显示规则