首页 > 编程语言 >Javascript文件上传

Javascript文件上传

时间:2023-12-06 11:33:41浏览次数:34  
标签:文件 const File Javascript xhr file 上传

什么是文件上传
文件上传包含两部分,

一部分是选择文件,包含所有相关的界面交互。
一部分是网络传输,通过一个网络请求,将文件的数据携带过去,传递到服务器中,剩下的,在服务器中如何存储,那就与前端无关了。
制作文件上传相关的功能时,一定要先确保文件上传的接口可用,否则之后会遇到无数的麻烦,无论怎么写都是写不通的。

可以使用apifox 或者其他的工具先把接口调通。

简单上传的实现
上传界面的制作
抛开文件上传的接口和功能,我们先在本地模拟一个文件上传功能,把静态界面写好,等接口完成后,将模拟的上传替换为真实的接口即可。

但是,很有可能说这些相关的页面无法实现,那就不是文件上传的问题,而是之前的基础不牢固导致的。跟文件上传没有任何关系。

制作下面的一个页面,分为三个区域:文件选择,上传中,上传完成。

 

 

 

 


这里的文件上传,我们使用一个定时器来模拟。

首先选择文件并且读取出文件中的数据
// 绑定点击事件
doms.choose.addEventListener('click', (e) => {
doms.input.click();
})

// 选择文件后执行
doms.input.addEventListener('change', (e) => {
// 获取文件
const file = e.target.files[0];

// 使用文件读取器读取
const reader = new FileReader();

// 获取文件的DataUrl
reader.readAsDataURL(file);

// 监听文件加载完成
render.onload((e) => {
// 获取临时的DataUrl, 是文件在内存中的临时地址,直接从内存中读取文件
const url = e.target.result;
// 开始上传文件,上传的是File, 而不是DataUrl。
const cancel = load(file, (e) => {
console.log("上传进度:" + e);
}, (e) => {
console.log("上传完成:" + e);
})
})
})


封装上传函数
上传函数暂时先使用一个定时器模拟,之后只需要将这里替换为真实的上传即可。

function load(file, process, finished) {
// 模拟上传进度
let pro = 0;
const timer = setInterval(() => {
pro++;
// 监听文件上传进度
process(pro);
if(pro >= 100) {
const result = "文件上传结果"
clearInterval(timer);
// 文件上传完成的回调函数
finished(result);
}
}, 50);

// 返回取消上传的函数
return function () {
clearInterval(timer);
}
}


其余的都是简单的界面交互和事件绑定,如果写不出来就该反思一下之前学的基础怎么样。

发送请求
发送请求只需要改写一下upload函数,将模拟上传改为真实的上传即可。

function upload(file, process, finished) {
// 这里使用原生的xhr来发送请求
const xhr = new XMLHttpRequent();
xhr.onload = function() {
const resp = JSON.parse(xhr.responseText);
finished(resp);
}
xhr.upload.onprogress = e => {
const percent = Math.floor((e.loaded / e.total) * 100);
process(percent);
}
// 这一段看不懂就需要回去学习ajax相关的只是,不是说用axios等第三方库会写就行的
xhr.open("POST", config.url);
const form = new FormData();
form.append('avatar', file);
xhr.send(form);
return function() {
xhr.abort();
}
}


拖拽上传
拖拽上传和之前的点击上传只有界面交互上的区别,其余完全相同。
所以这里主要写的就是拖拽的交互逻辑。

H5的input文件输入框本身是支持拖拽的,所以我们只需要将默认的文件选择器放置在选择界面并且设置宽高相同,通过调整透明度使其不显示,就可以实现拖拽上传,并且连原本的点击事件都不需要了。

input {
display: block;
width: 100%;
height: 100%;
opacity: 0;
position: absolute;
left: 0;
top: 0;
}

// doms.choose.addEventListener('click', (e) => {
// doms.input.click();
// })

但是有些时候,为了兼容一些特殊的浏览器,这些浏览器的input不支持拖拽,那我们就必须为我们自己写的上传框注册拖动事件,让他也可以接收托入的文件。

要完成这个目标,首先就需要让输入框编程一个可拖动目标,也就是可以接收拖拽的物体。
只需要将两个事件的默认行为阻止即可。

// 这个事件会在进入拖拽元素后触发,类似mouseenter
doms.choose.ondragenter = (e) => {
e.preventDefault();
}

// 只要拖拽物一直在拖拽元素之上,就会一直触发,类似mouseover
doms.choose.ondragover = (e) => {
e.preventDefault();
}

注册上面的两个事件之后,元素就会变为可拖拽目标,就可以监听ondrop事件,这个事件的触发时机是拖拽物在触发元素上松手时,正好符合拖拽上传的操作逻辑。

doms.choose.ondrop = (e) => {
e.preventDefault();
const file = e.dataTransfer.files;
// 开启拖拽后一些奇奇怪怪的东西也可以被拖拽进来,需要做校验
if (!e.dataTransfer.types.includes("Files")) {
alert("仅支持拖拽文件");
return;
}
// 现在学习的是单文件上传
if(file.length !== 1) {
return;
}
doms.input.files = file; // 这样修改并不会触发input标签的change事件,所以需要提取change事件中的函数手动触发。
changeFile.call(doms.input);
}

doms.input.addEventListener('change', changeFile)


特殊格式上传
base64格式上传
要上传base64格式就不能再使用FormData来构建表单数据了,而是要使用json格式来上传。
依然只需要改写一下uplaod函数即可,为了区分我们另外写一个uploadBase64函数

function uploadBase64(file, process, finished) {
const ext = file.name.split('.').pop();
// 使用之前读取DataUrl的方法,逗号后面的字符就是对用的Base64
const render = new FileReader();
render.onload = (e) => {
const base64 = e.target.result.split(',').pop();
// 拿到base64后开始发送请求
xhr.open("POST", config.url);
// 传输json文本需要的请求头
xhr.setRequestHeader('content-type', 'application/json');
// 发送json文本
shr.send(
JSON.stringify({
ext,
avatar: base64
})
)
}
// 这里使用原生的xhr来发送请求
const xhr = new XMLHttpRequent();
xhr.onload = function() {
const resp = JSON.parse(xhr.responseText);
finished(resp);
}
xhr.upload.onprogress = e => {
const percent = Math.floor((e.loaded / e.total) * 100);
process(percent);
}
return function() {
xhr.abort();
}
}


二进制文件上传
二进制格式文件的上传是最常见的,也是最简单的,只需要带一个请求头,并且把数据带过去即可。不需要什么FormData呀,base64呀。

function uploadBinary(file, process, finished) {
// 这里使用原生的xhr来发送请求
const xhr = new XMLHttpRequent();
xhr.onload = function() {
const resp = JSON.parse(xhr.responseText);
finished(resp);
}
xhr.upload.onprogress = e => {
const percent = Math.floor((e.loaded / e.total) * 100);
process(percent);
}
xhr.open("POST", config.url);
// 传输二进制文件需要的请求头
xhr.setRequestHeader('content-type', 'application/octet-stream');
// 自定义请求头,根据文档的需求更改
xhr.setRequestHeader('x-ext', file.name.split('.').pop());
// 最后直接把文件发过去就可以
// 这种方式不只是图片,音频、视频等在二进制格式上都是打平的,一视同仁,任何格式的文件都可以上传。
xhr.send(file);
return function() {
xhr.abort();
}
}


多文件上传
如果想要一次选中多个文件上传,或者是上传一个文件夹中的所有文件,应该怎么做?

如果是点击上传,那么很简单,只需要在input标签上加上几个属性即可
input最后拿到的是一个数组,数组中的每一项都是一个File对象,通过File就可以拿到任何想要的文件信息。

multiple: 允许多选
webkitdirectory mozdirectory odirectory: 这个属性是让input只能选择文件夹
因为文件夹选择还在实验阶段,所以需要适配不同内核的浏览器

如果是拖拽上传,就相对比较麻烦,需要区分拖拽进来的是文件还是文件夹。
这个相对比较麻烦,暂时先留个坑

裁剪上传
什么是裁剪上传?就是选择一张图片或者其他文件后,可以截取其中的一部分发送到后端,这就是裁剪上传。

这里有两个重点:

如何实现本地裁剪预览。
如何实现文件的部分上传。
这里用一个简单的头像上传为例:

实现本地预览很简单,只需要用前面学到的知识,读取出DataUrl即可

const img = $('img')
const input = $('input');

input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.readAsDataUrl(file);
reader.onload = (e) => {
// 这里拿到的就是DataUrl;
const url = e.target.result;
img.src = url;
}
}

对于图片的裁剪上传,首先需要用之前学过的知识,用原生三剑客写出一个裁剪框来获取用户的裁剪信息,然后利用Canvas就可以实现裁剪和裁剪的预览。

裁剪完成后,通过Canvas的toBlob方法拿到Canvas的像素信息对应的Blob对象,进而通过Blob对象合成出File对象实现上传。

const postBtn = $('button');
const img = $('img');

// 这里模拟一个裁剪结果
const cutInfo = {
x: 100, // 图像开始裁剪的位置x
y: 100, // 图像开始裁剪的位置y
cutWidth: 300, // 裁剪图像的宽度
cutHeight: 300, // 裁剪图像的高度
width: 100, // 裁剪后真实显示的宽度
height: 100 // 裁剪后真实显示的高度
}

postBtn.onclick = () => {
const { x, y, cutWidth, cutHeight, width, height } = cutInfo;
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, x, y, cutWidth, cutHeight, 0, 0, width, height);
// 这个API可以将canvas中的像素信息转换为Blob,有了Blob就可以很容易得获取到File,因为File是Blob的子类,两者之间的转换很容易,有了File就可以上传文件。
canvas.toBlob((blob) => {
// new File时传递的时一个数组,因为一个File可以由多个Blob组成。
const file = new File([blob], 'avatar.jpg', {
type: 'image/jpeg'
})
// 最后通过网络请求将File上传即可。
ajax(file);
}, 'image/jpeg');
}


大文件分片上传
对于一些较大的文件,我们一般需要分成一小段一小段,分成多次ajax请求发送到后端。这是因为大文件一旦在传输中出现问题,因为对文件做出了分片,不再需要对整个文件进行上传。

分片上传最大的难点在于如何对文件进行分片?
如何标记已经上传过的文件?

我们知道通过input标签可以拿到File对象,但其实可以将File对象做为一个数组来处理,将File对象用数组的slice方法进行分割,就可以得到若干的Blob对象,Blob对象也是可以直接上传的,这样就实现的文件的分片上传,后端再去将这些分片合成为一个完成的文件。

input.change = (e) => {
const file = e.target.files[0];
const blob1 = file.slice(0, 99);
}

// 可以写一个函数来完成文件的分片,接收文件的分片大小,返回Blob数组。
function createChunks(file, size) {
const cnt = Math.ceil(file.size / size);
const result = new Array(cnt).fill(0);
for(let i = 0; i < cnt; i++) {
result[i] = file.slice(i * size, (i+1) * size);
}
return result;
}

注意这个分片是可以瞬间完成的,因为File对象和Blob对象里都只有文件的基本信息,并没有保存详细数据,所以这只是一个简单的数学运算。

之后就需要解决下一个问题,如何标记上传过的文件,这就需要介绍一个算法:文件哈希。
文件哈希是一个文件的唯一标识,它可以将一个文件的字节数据按照一定的算法压缩成一个固定长度的字符串,但是这个字符串是不可逆的。md5是一个常用的文件哈希算法,可以使用第三方库spark-md5来完成文件哈希的算法。

// 因为计算哈希需要读取内存数据,一次性读取大文件的全部数据吃不消,所以需要使用增量算法,spark-md5这个库有做相关的处理
function hash(chunks) {
const spark = new SparkMD5();
function _read(i) {
if(i >= chunks.length) {
return spark.end(); // 读取完成
}
const blob = chunks[i];
const reader = new FileReader();
reader.onload = e => {
const bytes = e.target.result; // 获取字节数组。
spark.append(bytes); // 添加字节到hash运算中。
read(i + 1);
}
// 读取blob对象的字节信息。
reader.readAsArrayBuffer(blob);
}
_read(0);
}


总结
抛开文件上传的外衣,其实就是界面交互和网络请求。

学了这么多的场景,应该足以应对绝大多数的场景,对于element-ui或者是ant等组件库内提供的文件上传组件也能做到知其然且知其所以然。

也学到了对于图片文件的很多处理方式。学到了File和Blob的转换。

对于File和Blob,其实不只是用于图片文件,任何格式的文件在浏览器中都会被打平为File和Blob,只不过不同的文件需要用到不同的辅助处理。

例如图片文件的处理需要接触Image和Canvas,音频文件的处理需要借助Auduo和AudioContent。

这就文件上传部分所有的笔记了。

 

参考文章:http://blog.ncmem.com/wordpress/2023/12/06/javascript%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0/

欢迎入群一起讨论

 

 

标签:文件,const,File,Javascript,xhr,file,上传
From: https://www.cnblogs.com/songsu/p/17879135.html

相关文章

  • Ubuntu上文件系统根目录磁盘空间扩充
    今天使用Ubuntu的时候,出现了磁盘根目录空间不足的提示,需要我们对于根目录磁盘空间进行扩充。1.打开终端输入命令,安装gparted管理器sudoapt-getinstallgparted接着输入Y接受,安装完成后输入命令sudogparted打开管理器2.进入gparted管理器界面如下,选择/dev/sda3根目......
  • 对比传统跨网文件交换方式,文件摆渡系统拥有这4大优势!
    网络隔离已是较为常见的网络安全保护措施,越来越多公司进行隔离网建设来隔绝外部网络有害攻击,但隔离后不少数据和文件仍需进行流转,就产生了跨网数据交换需求,在过去,企业使用较多的传统跨网文件交换方式有移动介质、网盘、FTP应用等,这些传统跨网文件交换方式一定程度上解决了企业的数......
  • 网络隔离后文件如何交换,推荐一款专业全能的数据摆渡系统!
    随着网络技术的迅猛发展,信息安全系统的脆弱性已经展现在人们眼前,为了保证涉密信息和重要数据不被泄露,一些重要部门比如政府部门、军队单位、大型企业及科研单位以及银行业金融机构等都建立了自己独有的内部网络。采用内外网隔离的策略,各个网络之间不互通,每个网络形成一个网络安全......
  • windows批量修改文件名-ren命令(重命名)
    windows批量修改文件名-ren命令(重命名)重点:文件名一对一对应1.进入要批量修改文件名的目录,在地址栏输入cmd,回车进入命令行模式2.执行dir/b>G:\Desktop\1.txt,将文件名拷贝到1.txt文件中。  3.新建一个excle表格,将文件名一对一对应,可借助分列排序,将文件名一对一对应。......
  • Linux文件管理
    Linux系统是一个基于Unix的操作系统,它有着独特的文件管理方式。在Linux系统中,一切都是文件,包括普通的文本文件,图片文件,音频文件,视频文件,以及设备文件,目录文件,链接文件,管道文件,套接字文件等等。Linux系统使用一个分层的目录结构来组织这些文件,这个结构被称为文件系统。文件系统的最......
  • idea文件导入问题
    ieda文件导入问题1导入没有.iml的idea项目文件  解决方法:从有.iml的文件中copy一份无法解决!目前还没找到生成方法,当作遗留问题保留!  2Maven项目中.iml文件缺失简单说明IDEA中的.iml文件是项目标识文件,缺少了这个文件,IDEA就无法识别项目。跟Eclipse的.proje......
  • coredump文件生成,以及GDB工具使用
    一、coredump文件生成Core文件其实就是内存的映像,当程序崩溃时,存储内存的相应信息,主用用于对程序进行调试。当程序崩溃时便会产生core文件,其实准确的应该说是coredump文件,默认生成位置与可执行程序位于同一目录下。1.查看core文件生成是否开启ulimit-a第一行corefile......
  • 控制文件读写内容的模式
    控制文本读写格式t(默认的):文本模式读写都是以字符串的为单位的只能针对文本文件必须加入encoding参数b:二进制模式读写文件都是以bytes/二进制为单位可以针对所有的文件不可以加入encoding参数前提:b/t模式都不能单独使用,必须与r/w/a之一结合使用。(1)t模......
  • Macbook磁盘系统结构/文件/目录介绍分析
    1.系统磁盘根目录详解:1.1磁盘根目录结构/(根目录)|--Applications#存放应用程序|--Users#存放用户文件和设置|--cores#存放核心转储文件,通常用于调试|--home#用户家目录的根目录|--sbin......
  • 文件的操作方法
    文件的操作方法(1)读操作f.read()读取文件所有内容,执行玩该操作后,文件内的指针会移动到文件内容的末尾。f.readline()读取一行内容,光标会移动到第二行首部f.readlines()读取每一行的内容,存入列表中。#读操作#withopen('02.txt','rt',encoding='utf-8')asf:......