Javascript文件上传

news2025/1/21 22:03:58

什么是文件上传

文件上传包含两部分,

  • 一部分是选择文件,包含所有相关的界面交互。
  • 一部分是网络传输,通过一个网络请求,将文件的数据携带过去,传递到服务器中,剩下的,在服务器中如何存储,那就与前端无关了。

制作文件上传相关的功能时,一定要先确保文件上传的接口可用,否则之后会遇到无数的麻烦,无论怎么写都是写不通的。

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

简单上传的实现

上传界面的制作

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

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

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

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


  1. 首先选择文件并且读取出文件中的数据
// 绑定点击事件
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);
		})
	})
})

  1. 封装上传函数

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

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只能选择文件夹

因为文件夹选择还在实验阶段,所以需要适配不同内核的浏览器


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

裁剪上传

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

这里有两个重点:

  1. 如何实现本地裁剪预览。
  2. 如何实现文件的部分上传。

这里用一个简单的头像上传为例:


实现本地预览很简单,只需要用前面学到的知识,读取出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就可以实现裁剪和裁剪的预览。

裁剪完成后,通过CanvastoBlob方法拿到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://www.coloradmin.cn/o/1063893.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TL-ER3220G端口映射设置

1、打开IE浏览器或其它浏览器&#xff0c;在地址栏输入192.168.1.1登录路由器的Web管理界面&#xff1b; 2、打开后弹出密码输入框&#xff0c;输入路由器的用户名和密码&#xff0c;出厂默认值为admin/admin&#xff0c;成功登录后将看到路由器的系统状态信息&#xff1b; 3、…

2023 年 Web 安全最详细学习路线指南,从入门到入职(含书籍、工具包)【建议收藏】

第一个方向&#xff1a;安全研发 你可以把网络安全理解成电商行业、教育行业等其他行业一样&#xff0c;每个行业都有自己的软件研发&#xff0c;网络安全作为一个行业也不例外&#xff0c;不同的是这个行业的研发就是开发与网络安全业务相关的软件。 既然如此&#xff0c;那其…

MyBatisPlus(十三)逻辑查询:and / or

说明 逻辑查询&#xff0c;对应SQL语句中的多个查询条件进行逻辑组合&#xff0c;包括 and 和 or。 逻辑 “与” &#xff1a;and 默认情况下&#xff0c;多个查询条件&#xff0c;就是使用的逻辑与&#xff08;and&#xff09;连接&#xff0c;并不需要再使用 and() 函数。…

modelize.ai - 小记

文章目录 关于 关于 官网&#xff1a;https://www.beta.modelize.ai Modelize.ai是一个AI agents和teams的创作平台&#xff0c;通过结合多元的agents以及工作流的衔接&#xff0c;让AI agents之间高效协同&#xff0c;达到一键式完成复杂工作的效果。 公司的愿景是让AI打工人…

从零开始学习线性回归:理论、实践与PyTorch实现

文章目录 &#x1f966;介绍&#x1f966;基本知识&#x1f966;代码实现&#x1f966;完整代码&#x1f966;总结 &#x1f966;介绍 线性回归是统计学和机器学习中最简单而强大的算法之一&#xff0c;用于建模和预测连续性数值输出与输入特征之间的关系。本博客将深入探讨线性…

Texifier 专业打造,让你的LaTeX编辑更高效!

作为LaTeX用户&#xff0c;你一定知道寻找一款优秀的编辑工具是多么重要。而Texifier&#xff08;原Texpad&#xff09;就是你在Mac上寻找的完美解决方案&#xff01;它是一款专业的LaTeX编辑工具&#xff0c;为你带来高效、便捷的编辑体验。 Texifier拥有丰富的功能&#xff…

Linux网络编程系列之UDP协议编程

一、什么是UDP协议 UPD协议&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是Internet协议族中的一个无连接协议&#xff0c;属于传输层&#xff0c;它不保证数据传输的可靠性或完整性&#xff0c;只是把应用程序发给网络层的数据封装成数据包进行传…

VL53L5CX驱动开发(1)----驱动TOF进行区域检测

VL53L5CX驱动开发----1.驱动TOF进行区域检测 闪烁定义视频教学样品申请源码下载主要特点硬件准备技术规格系统框图应用示意图区域映射生成STM32CUBEMX选择MCU 串口配置IIC配置X-CUBE-TOF1串口重定向代码配置Tera Term配置演示结果 闪烁定义 VL53L5CX是一款先进的飞行感应&…

【C语言】利用数组处理批量数据(字符数组)

前言:前面已经介绍了&#xff0c;字符数据是以字符的ASCII代码存储在存储单元中的&#xff0c;一般占一个字节。由于ASCII代码也属于整数形式&#xff0c;因此在C99标准中&#xff0c;把字符类型归纳为整型类型中的一种。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x…

使用ebpf 监控linux内核中的nat转换

1.简介 Linux NAT&#xff08;Network Address Translation&#xff09;转换是一种网络技术&#xff0c;用于将一个或多个私有网络内的IP地址转换为一个公共的IP地址&#xff0c;以便与互联网通信。 在k8s业务场景中&#xff0c;业务组件之间的关系十分复杂. 由于 Kubernete…

浅谈在操控器类中,为何要通过osgGA::CameraManipulator的逆矩阵改变视点位置

在osg代码目录下的include\osgGA目录存放了很多osg自带的操控器类&#xff0c;这些操控器类都派生自osgGA::CameraManipulator&#xff0c;而这个CameraManipulator又派生自osgGA::GUIEventHandler&#xff0c;可见其本质上是个事件处理类。因此它首先会接收事件&#xff0c;比…

月薪20k的软件测试工程师都要具备什么能力?你跟大佬的差距在哪?

第一&#xff0c;强大的业务能力&#xff1a;很熟悉业务流程&#xff0c;熟悉业务模块、数据、架构&#xff0c;测试所需资源。了解测试所需时间。 第二&#xff0c;发现bug能力&#xff1a;一般问题发现的能力&#xff0c;隐性问题发现能力&#xff0c;连带问题发现能力&…

专为实现最高性能和效率而设计,SQN3242UCKGTA、SQN3220SC、SQN3220 LTE-A Cat 6 模块【SKY85735-11射频前端】

一、SQN3242UCKGTA、SQN3220SC、SQN3220 LTE-A Cat 6 模块 1、简介 Sequans 的 Cassiopeia 是 Cat4 和 Cat6 LTE-Advanced 平台系列&#xff0c;包括集成了高性能网络和应用 CPU 的 SQN3220 Cat6 基带 SoC 和 SQN3220SC Cat4 基带 SoC、Sequans 的 SQN3242 LTE 优化收发器、经…

Pyhon-每日一练(1)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

大华智慧园区前台任意文件上传(1day)

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 漏洞简介 大华智慧园区综合管理平台是一个集智能化、信息化、网络化、…

3D人脸生成的论文

一、TECA 1、论文信息 2、开源情况&#xff1a;comming soon TECA: Text-Guided Generation and Editing of Compositional 3D AvatarsGiven a text description, our method produces a compositional 3D avatar consisting of a mesh-based face and body and NeRF-based ha…

总结三:计算机网络面经

文章目录 1、简述静态路由和动态路由&#xff1f;2、说说有哪些路由协议&#xff0c;都是如何更新的&#xff1f;3、简述域名解析过程&#xff0c;本机如何干预域名解析&#xff1f;4、简述 DNS 查询服务器的基本流程是什么&#xff1f;DNS 劫持是什么&#xff1f;5、简述网关的…

CCS安装和运行TMS320F28004x第一个程序

1. CCS安装 TI 的MCU或者DSP&#xff0c;官方的集成开发环境是 Code Composer Studio™ &#xff0c;要开发TI的芯片&#xff0c;首先需要安装 CCS 环境。 CCS 软件可以到下面的 TI 官网下载&#xff1a; https://www.ti.com.cn/tool/cn/CCSTUDIO 下载完之后&#xff0c;点击…

Pandas vs SQL全面对比

前一段时间给大家详解过 Pandas 的用法&#xff0c;今天再来分享下 Pandas 与 SQL 的对比。 Pandas 和 SQL 有很多相似之处&#xff0c;都是对二维表的数据进行查询、处理&#xff0c;都是数据分析中常用的工具。 对于只会 Pandas 或只会 SQL 的朋友&#xff0c;可以通过今天…

【QT5-程序控制电源-RS232-SCPI协议-上位机-基础样例【1】】

【QT5-程序控制电源-RS232-SCPI协议-上位机-基础样例【1】】 1、前言2、实验环境3、自我总结1、基础了解仪器控制-熟悉仪器2、连接SCPI协议3、选择控制方式-程控方式-RS2324、代码编写 4、熟悉协议-SCPI协议5、测试实验-测试指令&#xff08;1&#xff09;硬件连接&#xff08;…