Javascript文件上传
什么是文件上传
文件上传包含两部分,
- 一部分是选择文件,包含所有相关的界面交互。
- 一部分是网络传输,通过一个网络请求,将文件的数据携带过去,传递到服务器中,剩下的,在服务器中如何存储,那就与前端无关了。
制作文件上传相关的功能时,一定要先确保文件上传的接口可用,否则之后会遇到无数的麻烦,无论怎么写都是写不通的。
可以使用apifox 或者其他的工具先把接口调通。
简单上传的实现
上传界面的制作
抛开文件上传的接口和功能,我们先在本地模拟一个文件上传功能,把静态界面写好,等接口完成后,将模拟的上传替换为真实的接口即可。
但是,很有可能说这些相关的页面无法实现,那就不是文件上传的问题,而是之前的基础不牢固导致的。跟文件上传没有任何关系。
制作下面的一个页面,分为三个区域:文件选择,上传中,上传完成。
这里的文件上传,我们使用一个定时器来模拟。
- 首先选择文件并且读取出文件中的数据
// 绑定点击事件
doms.choose.addEventListener('click', (e) => {doms.input.click();
})// 选择文件后执行
doms.input.addEventListener('change', (e) => {// 获取文件const file = e.target.files[0];// 使用文件读取器读取const reader = new FileReader();// 获取文件的DataUrlreader.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的方法,逗号后面的字符就是对用的Base64const 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, // 图像开始裁剪的位置xy: 100, // 图像开始裁剪的位置ycutWidth: 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。
这就文件上传部分所有的笔记了。
相关文章:
Javascript文件上传
什么是文件上传 文件上传包含两部分, 一部分是选择文件,包含所有相关的界面交互。一部分是网络传输,通过一个网络请求,将文件的数据携带过去,传递到服务器中,剩下的,在服务器中如何存储…...
golang gin——文件上传(单文件,多文件)
文件上传 单文件上传 从form-data获取文件 package uploadimport ("github.com/gin-gonic/gin""net/http" ) // 单文件上传,多文件上传 func Upload(c *gin.Context) {file, _ : c.FormFile("file") // file为字段名dst : "…...
面试题:Redis和MySQL的事务区别是什么?
大家好,我是小米!今天我要和大家聊聊一个在技术面试中经常被问到的问题:“Redis和MySQL的事务区别是什么?”这个问题看似简单,但实际上涉及到了数据库和缓存两个不同领域的知识,让我们一起来深入了解一下吧…...
Canvas绘图
Canvas绘图 Canvas的意义 随着前端的不断发展,页面特效越来越炫酷,W3C组织也不断退出新的CSS特性:例如各种渐变,瀑布流布局,各种阴影,但是随着需求越来越花哨,W3C表示:我去你妈的&…...
逻辑回归评分卡
文章目录 一、基础知识点(1)逻辑回归表达式(2)sigmoid函数的导数损失函数(Cross-entropy, 交叉熵损失函数)交叉熵求导准确率计算评估指标 二、导入库和数据集导入库读取数据 三、分析与训练四、模型评价ROC曲线KS值再做特征筛选生成报告 五、行为评分卡模型表现总结 一、基础知…...
DPDK系列之三十三DPDK并行机制的底层支持
一、背景介绍 在前面介绍了DPDK中的上层对并行的支持,特别是对多核的支持。但是,大家都知道,再怎么好的设计和架构,再优秀的编码,最终都要落到硬件和固件对整个上层应用的支持。单纯的硬件好处理,一个核不…...
LVGL_基础控件滚轮roller
LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组,如果设置了默认组,…...
王道考研操作系统——文件管理
磁盘的基础知识 .txt用记事本这个应用程序打开,文件最重要的属性就是文件名了 保护信息:操作系统对系统当中的各个用户进行了分组,不同分组的用户对文件的操作权限是不一样的 文件的逻辑结构就是文件内部的数据/记录应该被怎么组织起来&…...
商业智能系统的主要功能包括数据仓库、数据ETL、数据统计输出、分析功能
ETL服务内容包含: 数据迁移数据合并数据同步数据交换数据联邦数据仓库...
基于帝国主义竞争优化的BP神经网络(分类应用) - 附代码
基于帝国主义竞争优化的BP神经网络(分类应用) - 附代码 文章目录 基于帝国主义竞争优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.帝国主义竞争优化BP神经网络3.1 BP神经网络参数设置3.2 帝国主义竞争算…...
将python项目部署在一台服务器上
将python项目部署在一台服务器上 1.服务器2.部署方法2.1 手动部署2.2 容器化技术部署2.3 服务器less技术部署 1.服务器 服务器一般为:物理服务器和云服务器。 我的是物理服务器:这是将服务器硬件直接放置在您自己的数据中心或机房的传统方法。这种方法需…...
【C语言】善于利用指针(二)
💗个人主页💗 ⭐个人专栏——C语言初步学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 导读:1. 字符指针1.1 字符串的引用方式1.2 有趣的面试题 2. 数组指针2.1 一维数组指针的定义2.2 一维数组…...
Python调用C++
https://www.cnblogs.com/renfanzi/p/10276997.html Linux使用Python调用C/C接口(一) - 代码先锋网 linux系统上使用Python调用C生成的.so动态链接库opencv_linux 下python 编译为so ,给c使用_比赛学习者的博客-CSDN博客 https://www.cnblogs.com/shuimuqingyang/p/13618105…...
自己实现扫描全盘文件的函数。
1.自己实现扫描全盘的函数 def scan_disk(dir): global count,dir_count if os.path.isdir(dir): files os.listdir(dir) for file in files: print(file) dir_count 1 if os.path.isdir(dir os.sep file): …...
JSON文件读写
1、依赖文件 #include <QFile> #include <QJsonDocument> #include <QJsonObject> #include <QDebug> #include <QStringList>2、头文件 bool ReadJsonFile(const QString& filePath""); bool WriteJsonFile(const QString&…...
VisualStudio2022环境下Release模式编译dll无法使用TLS函数问题
Debug x86环境下正常使用TLS回调函数 切换到Release发现程序没有使用tls 到C/C > 优化中将全程序优化关闭即可...
ChatGPT基础使用总结
文章目录 一、ChatGPT基础概念大型语言模型LLMs---一种能够以类似人类语言的方式“说话”的软件ChatGPT定义---OpenAI 研发的一款聊天机器人程序(2022年GPT-3.5,属于大型语言模型)ChatGPT4.0---OpenAI推出了GPT系列的最新模型ChatGPT典型使用…...
解决报错: require is not defined in ES module scope
用node启动mjs文件报错:require is not defined in ES module scope 现象如下: 原因: 文件后缀是mjs, 被识别为es模块,但是node默认是commonjs格式,不支持也不能识别es模块。 解决办法:把文件后缀从.mjs改…...
STM32 10个工程篇:1.IAP远程升级(六)
在IAP远程升级的最后一篇博客里,笔者想概括性地梳理总结IAP程序设计中值得注意的问题,诚然市面上或者工作后存在不同版本的IAP下位机和上位机软件,也存在不同定义的报文格式,甚至对于相似的知识点不同教程又有着完全不同的解读&am…...
【智能家居项目】裸机版本——字体子系统 | 显示子系统
🐱作者:一只大喵咪1201 🐱专栏:《智能家居项目》 🔥格言:你只管努力,剩下的交给时间! 今天实现上图整个项目系统中的字体子系统和显示子系统。 目录 🀄设计思路…...
PDF中跳转到参考文献后,如何回到原文
在PDF中,点击了参考文献的超链接可以直接跳至参考文献的位置。 如果想从当前参考文献在回到正文中对应位置时,可以通过 Alt \red{\text{Alt}} Alt ← \red{\leftarrow} ← 实现。...
了解基于Elasticsearch 的站内搜索,及其替代方案
对于一家公司而言,数据量越来越多,如果快速去查找这些信息是一个很难的问题,在计算机领域有一个专门的领域IR(Information Retrival)研究如何获取信息,做信息检索。在国内的如百度这样的搜索引擎也属于这个…...
【多模态融合】TransFusion学习笔记(2)
接上篇【多模态融合】TransFusion学习笔记(1)。 从TransFusion-L到TransFusion ok,终于可以给出论文中那个完整的框架图了,我第一眼看到这个图有几个疑问: Q:Image Guidance这条虚线引出的Query Initialization是什么意思? Q:图像分支中的…...
Pyhon-每日一练(1)
🌈write in front🌈 🧸大家好,我是Aileen🧸.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由Aileen_0v0🧸 原创 CSDN首发🐒 如…...
MySQL:数据库的物理备份和恢复-冷备份(3)
介绍 物理备份: 直接复制数据文件进行的备份 优点:不需要其他的工具,直接复制就好,恢复直接复制备份文件即可 缺点:与存储引擎有关,跨平台能力较弱 逻辑备份: 从数据库中导出数据另存而进行的备…...
功能比较:Redisson vs Jedis
Redis最流行的两个Java客户端库是Redisson和Jedis。Redisson提供内存中的数据网格功能,支持Redis的各种分布式对象和服务。另一方面,Jedis是一个更轻量级的产品,它缺乏其他库的某些功能。 如果你正在为Redis寻找一个Java客户端库…...
Spring web security
儅使用spring的web security時,默認會轉向自帶的spring security example page。而不會轉向error page。 TODO: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> &l…...
SpringCloud(二)Docker、Spring AMQP、ElasticSearch
文章目录 DockerDocker与虚拟机Docker架构镜像、容器、镜像托管平台Docker架构Docker实践 Spring AMQP简单使用案例工作队列- WorkQueue发布订阅服务FanoutExchangeDirectExchangeTopicExchange 消息转换器 ElasticSearch倒排索引IK分词器IK分词拓展与停用字典 操作索引库mappi…...
7.Tensors For Beginneers - Convector Components
介绍协向量时,曾说过它们有点像 行向量, 行向量确实以某种方式代表了协向量, 这里说明一下: 协向量是不变的; 协向量组件是可变的。 协向量不依赖坐标系,协向量的组件取决于坐标系。 当我们说协向量具有组…...
直线导轨坏了可以维修吗?
直线导轨是工业自动化设备中常用的零部件,其性能和使用寿命对设备的稳定运行和产能有着直接的影响,在生产中,由于各种原因,直线导轨会出现各种问题,那么,直线导轨的维修方法究竟是怎样的呢?我们…...
做网站有前途吗/奇葩网站100个
⭐️这是C专栏的第一篇博客,本篇博客我要来和大家一起聊一聊C中的一些基础语法,希望对大家有所帮助。 ⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code/tree/master/test_11_25 目录🌏命名空间…...
广东东莞营销/福州seo优化
select * into 目标表名 from 源表名 insert into 目标表名(fld1, fld2) select fld1, 5 from 源表名 以上两句都是将 源表 的数据插入到 目标表,但两句又有区别的: 第一句(select into from)要求目标表不存在,因为在插…...
福步外贸批发网/网站如何优化流程
wdOS是一个基于CentOS版本精简优化过的Linux服务器系统,网站服务器系统并集成nginx,apache,php,mysql等web应用环境及wdcp管理系统,安装完系统,所有的都安装完成装好系统,就可以通后台管理服务器和创建网站,增加FTP用户,创建数据库等使用方便,操作简单,安全,稳定,易用lamp,lnmp…...
爱站网 关键词挖掘工具站长工具/网站优化排名技巧
既然知道自己是个不自觉的人,自制力很差,那么就在做事情的时候不要给自己任何借口来推脱和延后完成,这是自己唯一能提高效率的办法!!转载于:https://www.cnblogs.com/zhangxiangning/p/10214439.html...
网站建设的常用技术有哪些/免费站推广网站在线
#(1)遍历列表 print(列表的遍历:) a[1,2,3]; for i in a:print(i); #(2)遍历元组 print(元组的遍历:) a(4,5,6); for i in a:print(i); #(3)遍历集合 print(集合的遍历:) a{7,8,9} for i in a:print(i); #(4)遍历字典 print(字典的遍历&#…...
建立网站的方案/海外推广代理商
Sweetest 常见问题汇总(持续更新...)安装配置1. 是否支持 Python2.7?答:不支持。Sweetest 仅支持 Python3.6 或以上,原因如下:框架中使用了有序字典等特性;人生苦短,我用新版 :)2. 安装后,无法正…...