图片上传详解
前端
知识点
- input 属性:input file
- FormData 对象:异步上传二进制文件
- FileReader 对象:实现图片预览功能
选择文件
需要选择上传文件,需要用到input标签:
<input type="file" multiple id="input" accept="image/*" />
input 属性:
- accept
- 指定选择文件类型的范围。默认为所有文件类型
- 图片为 accept="image/*"
- 文件类型取值见MDN
- capture
当文件类型为图片或视频且在移动端时,此属性才有意义。
- capture = 'user' 调用前置摄像头
- capture = 'environment' 调用后置摄像头
- 不设置则为用户自己选择
- multiple 一个 Boolean 值,如果存在,则表示用户可以选择多个文件
- files 返回一个 FileList,列出每个所选文件对象。除非 multiple 指定了属性,否则此列表只有一个成员。主要用于 JS 操作。
const input = document.getElementById("input");
input.onchange = function () {
let fileList = document.getElementById("input").files;
console.log(fileList); // 文件列表
};
当文件只存在一个File,如下上传了3个文件,因此文件FileList 长度为3。
校验图片
文件选中后,可以得到 文件名称
、文件大小
、文件类型
、上传修改时间
。业务上通常用来校验文件的文件大小、文件类型。
图片格式
一般业务上写 accept 都基本全部写出具体支持的格式,防止不支持的格式或者图片导致意外情况。
图片格式 accept=".JPG,.JPEG,.PNG,.APNG,.GIF,.BMP,.WEBP,.ICO,.SVG"
图片是否存在
const input = document.getElementById("input");
input.onchange = function () {
let fileList = document.getElementById("input").files;
console.log(fileList);
// 取出文件
const file = fileList[0];
console.log(file);
// 检查 fileList 长度是否大于0,验证是否有文件选中
if (fileList.length > 0) {}
// 验证具体文件是否上传,可以通过name 来确定,但一般用不上,文件名在本地可更改
};
图片大小
一般电商图片我们支持3M图片足够,当然更大的图片也是可以,建议最大不超过10M。前台展示的图片GIF图小于3MB,普通图片100KB左右。动图(apng\gif)大小控制在1M内。
文件size是字节数,需要换算成KB/MB好进行运算
(n KB)表示方法,方便代码阅读: n * 1024
(n MB)表示方法,方便代码阅读: n * 1024 * 1024
if (fileList[0].size > 1024 * n ){}
预览图片
一般电商预览的图片都是经过上传后返回的线上CDN地址,是一种先上传后预览的规则。
FileReader
// 继续使用上文的file
const fileReader = new FileReader();
fileReader.readAsDataURL(file); //读取图片
fileReader.addEventListener("load", function () {
// 读取完成
let res = fileReader.result;
// res是base64格式的图片
const image = document.createElement("img");
image.src = res;
image.width = 100;
document.querySelector("body").appendChild(image);
});
URL.createObjectURL
const image = document.createElement("img");
image.src = URL.createObjectURL(file);
image.width = 100;
document.querySelector("body").appendChild(image);
上传图片
FormData 对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。
formData.append(name, value, filename)
- name:属性名
- value:属性值,在我们这里则指 file 数据
- filename:当第二个参数为 file 或 blob 时,告诉服务器的文件名。Blob 对象的默认文件名是“blob”。File 对象的默认文件名 是文件的文件名。
import md5 from 'blueimp-md5';
// 继续使用上文的file
const formDate = new FormData()
formDate.append('imageItem', file)
// 一般浏览器端安全需要带上 csrf_token。csrf_token是需要服务器种到cookie中的。
formData.append('_csrf_token', getCsrfToken());
function getCsrfToken() {
return md5(getCookie('_csrf_token'));
}
function getCookie(key) {
return (
decodeURIComponent(
document.cookie.replace(
new RegExp(
'(?:(?:^|.*;)\\s*' + encodeURIComponent(key).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'
),
'$1'
)
) || ''
);
};
直传
在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器。
// fetch 包/ajax 等同理底层都是对XMLHttpRequest的封装,见此文档https://github.com/github/fetch/blob/master/fetch.js
// window.fetch 本身是浏览器支持的,见文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
import 'whatwg-fetch';
// 继续使用上文的file
const formData = new FormData()
formData.append('imageItem', file)
// 继续使用上文的 getCsrfToken() 方法
formData.append('_csrf_token', getCsrfToken());
window
.fetch(url, {
method: 'POST', // 文件上传要用post
credentials: 'include', // 带上cookie信息
body: formData
})
.then((res) => res.json())
.then((data) => {},(error) => {});
fetch请求头和参数信息:
// XMLHttpRequest
// 继续使用上文的file
const formData = new FormData()
formData.append('imageItem', file)
// 继续使用上文的 getCsrfToken() 方法
formData.append('_csrf_token', getCsrfToken());
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', '');
xhr.setRequestHeader("Content-type", "multipart/form-data");
xhr.send(formData);
XMLHttpRequest请求头和参数信息:
XMLHttpRequest设置了 multipart/form-data 和 fetch 不设置 multipart/form-data 有差别,如果XMLHttpRequest不设置了 multipart/form-data 跟 fetch 不设置 multipart/form-data 是一致的结果。 思考:文件上传不需要设置 headers了,这是为什么?什么时候需要设置 headers 呢?
踩坑:fetch上传formData文件。 无需额外增加headers、浏览器会自动根据文件加上headers 生成boundary
分片上传
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。
一般的使用场景:
- 大文件上传
- 网络环境环境不好,存在需要重传风险的场景
分片上传的整个流程大致如下:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件。
当然以上请求是串行发送,当然可以并行发送。并行发送需要注意浏览器http请求并发限制,较大文件导致并发过多,tcp 链接被占光 ,需要做下并发控制,比如只有3~5个在请求在同一时间进行发送。
断点续传
断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传或者下载未完成的部分,而没有必要从头开始上传或者下载。本文的断点续传主要是针对断点上传场景。
一般应用场景:
- 断点续传可以看成是分片上传的一个衍生,因此可以使用分片上传的场景,都可以使用断点续传。
拖拽上传
源码分析
rc-upload
rc-upload 是 antd upload 在使用的图片上传组件,也是react-componet upload 官方组件。依赖关系如下图:
rc-upload 使用的默认上传文件方式是 xmlHttpRequest。可见文档 request
plupload
plupload 采用html5,flash,silverlight,html4降级模式,兼容所有主流浏览器,可跨平台。目前淘系上传应该是基于此组建封装的,不过那都已经是2014年的事情了。淘系封了超过3mb文件可支持暂停续传,支持上传过程中追加文件,支持自动开始上传,支持拖放文件上传(Chrome或webket核心支持拖放文件夹上传)。除了上传还做了token验证,防 XSS。
转载自:https://juejin.cn/post/7195746542068793404