前言
图片/文件上传组是企业项目开发中必不可少的环节之一, 但凡涉及到用户模块的都会有图片/文件上传需求, 在很多第三方组件库(ant desigin, element ui)中它也是基础组件之一. 接下来笔者就来带大家从零实现一款图片/文件上传组件以及扩展出更强大的上传组件.
你将收获
- 常用的图片上传功能实现方案
- 手写一个图片/文件上传组件
- 如何将裁剪功能集成到上传组件中
- 内容平台/可视化平台下的图片自治方案
- 如何扩展出更强大的图片上传方案
正文
作为一名前端工程师, 解决项目问题是我们的基本职责之一, 我们可以利用已掌握的知识去解决项目开发中的问题和需求, 这也是我们职业生涯必将经历的第一个阶段,即——适应期. 如果我们想继续晋升, 我们就需要不断的打怪升级,掌握各种技能, 这样我们才能在未来遇到问题时采用最佳的方案高效的解决问题, 也就是第二个阶段——发展期.
为了更快的进入发展期, 我们需要不断的提升自己的技术深度和广度, 能纵向考虑到问题的本质也能横向的对问题提出多种解决方案, 最终选择一种最优方案来实现. 要实现这一点,我们需要对问题做深度思考和复盘, 接下来笔者将介绍几种常用的图片上传方案,来扩展大家的广度.
1. 常用的图片上传方案
从web1.0时代开始, 我们用的最多的上传方案就是form表单, 我们只需要在form内写好各种input(输入型元素), 并定义好上传的服务器地址(action)即可.形式类似如下:
- <form action="/xuxiaoxi/form/post">
- <div class="form-item"><input type="text" /></div>
- <div class="form-item"><input type="passward" /></div>
- <div class="form-item"><input type="file" /></div>
- <div class="form-item"><input type="submit" /></div>
- </form>
在XHR技术还没普及时, 我们大多会选择上述方案, 唯一的缺点就是提交之后会刷新页面, 用户体验不太好, 还可能造成局部数据丢失, 但仍然有解决方案, 就是form + iframe技术.
1.1 form + iframe方案
form + iframe方案的基本思路就是我们提交动作是在父页面触发, 但是form表单指向为iframe, 这样可以实现局部刷新, 现在有些场景仍然在使用该方案, 具体原理如下:
以上两种方案都可以实现传统form提交下的局部刷新功能, 不过方案一需要单独维护iframe表单, 所以我呢一般采用方案二, 而且兼容性都可以达到IE9(虽然现在来说兼容IE浏览器意义不大, 但是还是要了解一下)
1.2 ajax + formData方案
在XHR盛行之后,我们可以轻松使用ajax来实现异步请求了, 对于文件上传, 我们也可以更灵活的使用ajax和formData来实现, 逐渐脱离了对原生form表单的依赖.
- FormData 对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据(keyed data),而独立于表单使用。如果表单enctype属性设为multipart/form-data ,则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。
我们先来看一个简单的使用formData上传文件的例子:
- let formData = new FormData();
- // HTML 文件类型input,由用户选择
- formData.append("userfile", fileInputElement.files[0]);
- let request = new XMLHttpRequest();
- request.open("POST", "http://http://io.nainor.com/h5/form");
- request.send(formData);
以上短短5行代码就实现了将文件通过formData的方式上传给了服务器, 是不是很简单呢? 笔者之前的文章 基于react/vue开发一个专属于程序员的朋友圈应用就采用了该方案, 感兴趣的可以学习研究一下.
如果要实现多文件上传也非常简单, 这里我们以axios为例, 具体实现如下:
- const formData = new FormData()
- for(let i=0; i< files.length; i++) {
- formData.append(`file_${i+1}`, files[i].file)
- }
- axios({
- method: 'post',
- url: '/files/upload/tx',
- data: formData,
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- });
这里要注意多文件上传要在请求的http header中设置 Content-Type 为 multipart/form-data . 当然大家还可以基于以上原理实现更符合自身业务需求的文件上传组件, 比如预览, 限流等.
1.3 第三方组件实现
为了更高效快速的开发业务, 我们有时候也可以选择第三方比较成熟的方案, 比如antd的upload组件, 比如element ui的上传组件, 这里笔者总结了几个比较好用且强大的方案, 大家可以感受一下:
- antd/element 的 upload 组件
- FilePond 可以上传任何内容,并能够优化图像以加快上传速度,同时提供顺畅的用户体验
- Web Uploader 百度WebFE(FEX)团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件
- vue-simple-uploader 基于vue的强大美观的文件上传组件
我们可以通过上述提供的第三方组件库, 结合自己服务端的配置,就可以轻松实现强大的上传组件了.
2. 将裁剪功能集成到图片上传组件
对于图片上传组件来说, 我们往往不能确定用户上传的到底是什么, 所以我们要提前约束, 比如说对图片大小, 图片格式, 图片比例等进行限制以符合我们的业务标准. 图片大小和图片格式的限制非常好实现, 但是对于图片比例, 这个我们不能期望用户自己来处理, 因为这样会极大的增加用户使用网站的负担, 所以我们可以提供一种功能, 让用户在线切图. 如下图所示:
以上截图来自于H5-Dooring在线编辑器的图片上传组件, 在用户上传之后我们会出现图片裁切界面, 我们会指定图片的比例, 让用户自由裁切. 笔者将基于antd的upload组件配合antd-img-crop来带大家实现在线切图功能. 具体代码实现如下:
- import React, { useState } from 'react';
- import { Upload } from 'antd';
- import ImgCrop from 'antd-img-crop';
- const Demo = () => {
- const [fileList, setFileList] = useState([
- {
- uid: '-1',
- name: 'image.png',
- status: 'done',
- url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
- },
- ]);
- const onChange = ({ fileList: newFileList }) => {
- setFileList(newFileList);
- };
- const onPreview = async file => {
- let src = file.url;
- if (!src) {
- src = await new Promise(resolve => {
- const reader = new FileReader();
- reader.readAsDataURL(file.originFileObj);
- reader.onload = () => resolve(reader.result);
- });
- }
- const image = new Image();
- image.src = src;
- const imgWindow = window.open(src);
- imgWindow.document.write(image.outerHTML);
- };
- return (
- <ImgCrop rotate>
- <Upload
- action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
- listType="picture-card"
- fileList={fileList}
- onChange={onChange}
- onPreview={onPreview}
- >
- {fileList.length < 5 && '+ Upload'}
- </Upload>
- </ImgCrop>
- );
- };
- ReactDOM.render(<Demo />, mountNode);
以上只是一个基本的裁切并上传图片的例子, 当然antd-img-crop还提供了更多灵活的配置来方便我们设计更灵活强大的裁切效果. 当然我们还可以使用react-cropper来实现, 它提供了更灵活的裁切控制以及裁切实时预览功能, 如下图所示:
3. 内容平台/可视化平台下的图片自治
对于内容平台或者可视化平台而且, 单纯的上传图片还不能满足用户的需求, 因为内容/可视化平台更加注重图片的选择和使用, 对图片要求也很高, 用户自己上传毕竟资源有限, 往往不能达到用户对内容发布的需求或者可视化设计的需求, 所以往往在这类平台中会提供图片素材库这一功能, 用户可以在素材库中搜索海量图片以满足自己的需求, 而往往这样, 才更能留住用户, 增加用户粘性.
基于以上场景产品经理往往会提出这样的需求: 能不能提供可选方案, 用户既能自己上传图片, 也能使用我们提供的图片库资源呢? 这个时候有经验的前端往往会说一句: 安排!
在设计该功能之前我们往往要先参考其他已有实现, 这里我们举几个例子, 如下图所示:
以上案例中我们可以发现在用户上传图片的时候都会提供两个可选选项, 一个是本地上传, 一个是直接在图片库中选择, 所以我们的方案也类似, 可以统一将图片库封装到文件上传组件中作为通用功能, 也可以组合式封装, 各自可以独立使用也可以组合使用.
对于H5-Dooring对图片库的封装, 使用了将其作为通用服务来实现, 也就是但凡使用了上传组件,一定会出现可选的从图片库选择按钮. 实现方案也很简单, 就是在upload组件中扩展一层, 使用Modal+Tab来做图片选择的界面, 当选择完成后将图片的地址手动设置到upload组件中即可. 代码如下:
- handleImgSelected= () => {
- const fileList = [
- {
- uid: uuid(8, 16),
- name: 'h5-dooring图片库',
- status: 'done',
- url: this.state.curSelectedImg,
- },
- ];
- this.props.onChange && this.props.onChange(fileList);
- this.setState({ fileList, wallModalVisible: false });
- };
这里用了antd的form组件的受控模式.
4. 图片上传组件扩展
上面介绍的方案对于基本使用场景完全够用了, 但是如果是内容网站或者可视化搭建平台, 由于我们的配置可能会随时分发到公网, 这就会涉及到内容安全的问题, 如果一旦用户配置了违法的图片信息, 那么对于平台提供上可能会受到牵连, 所以我们还需要提供一套完善的审核机制, 比如用户配置好或者发布好内容后, 需要进过审核才能正式发布到线上, 但是完全依赖人工审核效率又比较低, 所以这个时候我们就需求找到机器自动化审核方案了. 比如阿里云和腾讯云等都提供了图片鉴别等服务, 我们可以将这些服务集成到我们的组件中, 来实现真正的业务自治能力, 这样才能更安全的进行企业化经营和开发.
还有一个需求就是用户对于上传的图片有编辑需求, 我们还可以提供对图片的在线编辑功能, 类似于如下方案:
我们能让用户有能力对自己选择的图片进行自行设计, 加水印等能力, 这样是不是更有意思呢?
5. 总结
以上教程笔者已经集成到H5-Dooring中,对于一些更复杂的交互功能,通过合理的设计也是可以实现的,大家可以自行探索研究。
本文转载自微信公众号「趣谈前端」