整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript实战002:前端各种文件下载功能

JavaScript实战002:前端各种文件下载功能的实现

前端项目开发的时候,系统支持文件下载是前端开发中常用到的功能之一,当用户访问我们的网站时发现有自己需要的资源时可以将资源下载下来。文件下载主要有两种形式,一种是通过文件地址下载,该文件可以存放在前端或者后端。另一种则是通过文件流下载,前端通过发送请求给后端并获取后端文件流进行下载。

a标签下载

download属性:是HTML5中的a标签的新特性,用来规定被下载的超链接目标。在a标签中如果没有申明download属性的时a标签的默会链接跳转进行预览(如txt , jpg , pdf ),当前浏览器不支持预览的文件时则出现下载。当申明了download属性之后浏览器会对href属性链接的文件进行下载。download属性与不支持H5的低版本浏览器不兼容且仅限于同源文件,如果是非同源download属性会失效。比如引用第三方的网站内容、引用前后端分离的服务器内容、甚至本地测试引用的本地文件,download都会不起作用。如果你想测试该功能可以在本地开一个服务,将文件放同一服务中测试就可以了。

直接使用a标签时只要在a标签中添加download属性,如果是非a标签的话可以在出发事件的时候通过JavaScript来创建一个隐藏a标签下载,当我们点击时触发隐藏的a标签下载事件。这里使用appendChild和removeChild的处理是为了兼容Firefox浏览器。

XMLHttpRequest下载

需要了解XMLHttpRequest可以参考文章:JavaScript实战001:XMLHttpRequest使用入门,这里我利用XMLHttpRequest对象来发送请求,用blob类型来接受后台发过来的二进制类型文件。然后模拟a标签创建隐藏的下载链接,通过URL.createObjectURL()方法创建一个指向blob对象的URL地址。

iframe下载

iframe是HTML标签元素,可以用来创建内联框架。iframe提供了src属性用来规定在 iframe 中显示的文档的 URL,我们可以直接将文件地址抛给iframe,也可以赋值文件流地址给iframe。功能实现跟a标签有点相似,创建一个隐藏的iframe标签来实现文件的下载功能。使用文件地址下载需要注意的是浏览器支持预览的文件类型无法下载(比如图片、PDF文档、text文本等会直接打开文件预览),文件流下载只需将请求接口赋给src属性,iframe会自动去请求该文件实现下载。

window.open下载

window.open()方法用于打开一个新的浏览器窗口或查找一个已命名的窗口,也可以用它来实现文件下载功能。而且这个比iframe更简单,直接将文件地址或者请求接口赋给window.open(url)方法即可实现文件下载功能。但是有个缺点就是每次下载都会打开一个新的窗口来实现下载(想不跳转可以尝试window.location.assign()方法,用于加载一个新的文档),而且如果使用文件地址下载的是浏览器支持预览的文件类型无法下载(比如图片、PDF文档、text文本等会直接打开文件预览)。

form表单提交下载

form表单是个比较常用的html表签,用户提交用户信息,比如常见的登录、注册界面大部分都是通过form表单进行数据提交的。form表单所有要提交的数据都必须放在form标签中,method属性定义提交的方法(有get和post两种提交方法),action属性定义请求的地址。form标签中支持input、menus、textarea、fieldset、legend 和 label 等元素,通过submit向服务器提交数据。这里我创建了form表单和input框,input用于输入请求的参数,form用于提交数据请求。

Django后台接口

这里提供下Django的后台文件请求接口,以上文件请求都是基于该接口实现的。接收请求方法为GET,请求参数为id(数据库存储的文件id),采用FileResponse方式返回的文件流信息(具体实现功能可以参考文章:Django实战013:各种文件下载功能实现详解)。

总结:

下载的方式方法有很多,以上只是JavaScript中常见的几种下载方式。其实用ajax或者axios也可以实现下载,但是万变不离其中,会JavaScript下载害怕不会别的么。以上下载方式个人觉得还是iframe比较简单方便,请求最好还是通过文件流来实现,相对文件地址下载会更安全些。

更多前端技巧可以参考专栏:Vue实战技巧

ileReader 对象FileReader 对象主要用来把文件读入内存,并且读取文件中的数据。通过构造函数创建一个 FileReader 对象。

这个文件读取对象有以下几种方法:

1.readAsText():读取文本文件(可以使用Txt打开的文件),返回文本字符串,默认编码是UTF-8。


2.readAsBinaryString():读取任意类型的文件。返回二进制字符串。这个方法不是用来读取文件展示给用户看,而是存储文件。例如:读取文件的内容,获取二进制数据,传递给后台,后台接收了数据之后,再将数据存储。


3.readAsDataURL():读取文件获取一段以data开头的字符串,这段字符串的本质就是DataURL.DataURL是一种将文件(这个文件一般就是指图像或者能够嵌入到文档的文件格式)嵌入到文档的方案。DataURL是将资源转换为base64编码的字符串形式,并且将这些内容直接存储在url中>>优化网站的加载速度和执行效率。


4.abort():中断读取

该对象常见应用在即时预览:

即时:当用户选择完图片之后就立刻进行预览的处理 >>onchange。

预览:通过文件读取对象的readAsDataURL()完成。

以下是应用时的具体实现(推荐了解黑马程序员web前端培训课程)

HTML部分:

JS部分:

其中获取数据时,FileReader还提供一个完整的事件模型,用来捕获读取文件时的状态。

onabort:读取文件中断片时触发

onerror:读取错误时触发

onload:文件读取成功完成时触发

onloadend:读取完成时触发,无论成功还是失败

onloadstart:开始读取时触发

onprogress:读取文件过程中持续触发

.应用程序标签

  • DataList(数据列表)

  • Progress(进度条)

  • Meter(数值显示器) 示例:

<html lang="en"><head> <meta charset="UTF-8"> <title>应用程序标签</title> <style> .my-progress{ -webkit-appearance: none; } </style></head><body><!--数据列表,呈现需要载体--><input type="text" list="data-list"><datalist id="data-list"> <option value="张三"></option> <option value="李四"></option></datalist><!--进度条--><progress></progress><!--progress默认最大值是1,可以设定自己想要的值--><progress class="my-progress" value="30" max="100"></progress><meter min="0" max="100" low="40" high="90" optimum="100" value="91">A+</meter></body>

可以利用数据列表(data-list)做一个那种带下拉框的输入框,可以根据用户输入的内容匹配下拉框的内容(select2也可以实现这种需求,而且最低可以兼容到IE8,推荐用select2),后面的progress和meter样式不好控制,用的不太多。

2.自定义属性(data-*)

在html5中可以自定义数据,以data开头,利用这个自定义标签我们可以把属性暂时存储到页面中,在js中可以使用,例子如下:

<html lang="en"><head> <meta charset="UTF-8"> <title>data属性</title></head><body> <ul id="list"> </ul></body><script> // 键是ID 值是信息 var data={ 01: { name: "张三1", age: 18 }, 02: { name: "张三2", age: 19 }, 03: { name: "张三3", age: 20 } }; var list=document.getElementById("list"); for(var id in data){ var item=data[id]; var liem=document.createElement("li"); liem.innerHTML=item.name; //自定义data-*属性 liem.setAttribute("data-name",item.name); liem.setAttribute("data-age",item.age); list.appendChild(liem); //点击获取dataset属性 liem.addEventListener("click",function(){ //下面这两种方式均可以,都不带data- alert(this.dataset['name']); alert(this.dataset.name); }) }</script>

下面这篇文章总结的很好,可以参考:http://blog.csdn.net/qq_31851435/article/details/53100691

3.新的表单提交方式

传统的一共有9中表单提交方式,html5新增了几种,目前只用在移动端,pc端兼容性有问题,而且在输入的时候并没有进行验证,比如类型是email的,并没有校验邮箱格式,还是需要我们自己手动写正则表达式验证的,只是在手机端当唤起输入法的时候会自动切换到英文输入法。

<head> <meta charset="UTF-8"> <title>新的表单提交方式</title></head><body><h3>传统的表单提交方式(9种)</h3>用户名:<input type="text"> <br>密码:<input type="password"><br>性别:<input type="radio"><br>课程:<input type="checkbox"><br>隐藏:<input type="hidden">文件:<input type="file"><br>按钮:<input type="button" value="按钮"><br>表单:<input type="submit"><br>重置:<input type="reset"><br><hr><h3>新的表单提交方式(目前只用在移动端)</h3>颜色:<input type="color"><br>网址:<input type="url"><br>搜索:<input type="search" results="10" placeholder="Search..."><br>邮箱:<input type="email" pattern="^\*.com"><br>日期:<input type="date" min="2015-09-01" max="2018-09-01"><br>图片:<input type="image"><br>范围:<input type="range" min="0" max="50" value="10"><br>数字:<input type="number"></body>

4.新的选择器

html5提供新的选择器,querySelector选择单个的元素,返回满足条件的第一个元素,是一个dom元素,querySelectorAll选择全部的元素,返回满足条件的全部元素,是一个dom数组,里面可以是id,标签或class类。

<html lang="en"><head> <meta charset="UTF-8"> <title>新选择器</title></head><body><h3>新选择器</h3><ul> <li class="item">item1</li> <li class="item">item2</li> <li class="item">item3</li> <li class="item">item4</li> <li class="item">item5</li> <li class="item">item6</li> <li class="item">item7</li> <li class="item">item8</li> <li class="item">item9</li> <li class="item">item10</li></ul></body><script> //匿名函数,可以将作用域分隔开 (function(){ var liem=document.querySelector(".item"); //只选择第一个 console.log(liem.innerHTML); var lis=document.querySelectorAll("ul>li"); //选择全部 for(var i=0;i<lis.length;i++){ console.log(lis[i]); lis[i].addEventListener("click",function(){ debugger; //可以调试代码。类似于浏览器打断点 console.log(this.innerHTML); }) } })()</script>

5.元素类列表(Element.classList)

新H5中DOM对象多了一个classList属性,是一个数组:

  • add 添加一个新的类名

  • remove 删除一个类名

  • contains 判断是否包含一个指定的类名

  • toggle 切换一个类名。 element.toggle('class-name',[addorremove]),第一个参数是类名,第二个参数是布尔值,如果是true,则添加类名,如果是false,则去掉类名。示例:

<head> <meta charset="UTF-8"> <title>元素类列表</title> <link rel="stylesheet" href="css/bootstrap.css"></head><body><div class="container"> <div class="collapse navbar-collapse"> <nav class="navbar navbar-default"> <a class="navbar-brand" href="#">Brand</a> <ul class="nav navbar-nav"> <li><a href="#">Home</a></li> <li><a href="#">Link</a></li> <li><a href="#">Link</a></li> </ul> </nav> </div></div><script> //匿名函数 (function(){ /** 点击时添加和删除类名**/ var lis=document.querySelectorAll(".nav li"); for(var i=0;i<lis.length;i++){ lis[i].addEventListener("click",function(e){ //去掉原先所有的样式 for(var j=0;j<lis.length;j++){ lis[j].classList.remove("active"); } //添加点击样式 this.classList.add("active"); e.preventDefault(); //取消事件的默认操作 }) } /** 点击时存在类名则删除,不存在类名则添加**/ var elem=document.querySelector(".navbar-brand"); //点击时如果存在active这个属性则去掉,如果不存在这个属性则加上 elem.addEventListener("click",function(e){ //判断是否包含active这个类名 var isExist=elem.classList.contains("active"); this.classList.toggle("active",!isExist); e.preventDefault(); }) })()</script></body>

6.访问历史Api

在HTML5中可以通过window.history操作访问历史状态,让一个页面可以有多个历史状态

  • window.history.forward(); // 前进

  • window.history.back(); // 后退

  • window.history.go(); // 刷新

  • history.pushState(放入历史中的状态数据, 设置title(现在浏览器不支持), 改变历史状态),通过JS可以加入一个访问状态

<head> <meta charset="UTF-8"> <title>历史记录</title></head><body><input type="button" value="测试历史记录" onclick="addHistory()"></body><script> //添加历史记录 function addHistory(){ //判断浏览器是否支持历史记录,毕竟是h5的新特性,低版本浏览器不支持 if(window.history && history.pushState){ //支持 history.pushState(new Date().toLocaleDateString(),"设置历史记录的标签,但是目前浏览器还不支持","?demo="+new Date().toLocaleTimeString()); }else{ console.log("抱歉,浏览器不支持历史记录") } } //下面这个事件是点击浏览器中的前进或后退时触发 window.addEventListener("popstate",function(e){ console.log("点击了历史按钮"+e.state); })</script>

7.全屏Api

H5中可以用requestFullScreen()方法实现指定元素的全屏显示(类似于浏览器中按F11)。首先需要获取需要全屏显示的元素,然后判断浏览器是属于谷歌内核还是火狐内核或者其它浏览器,然后调用响应方法即可。(注意:目前这个特性浏览器兼容性还比较差,谷歌和火狐可以)

var elem=需要全屏的元素;if (elem.webkitRequestFullScreen) { elem.webkitRequestFullScreen();} else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen();} else if (elem.requestFullScreen){ elem.requestFullScreen();}
<head> <meta charset="UTF-8"> <title>全屏显示</title></head><body><h3 style="cursor: pointer">点我全屏显示</h3><p>测试全屏显示测试全屏显示测试全屏显示测试全屏显示测试全屏显示测试全屏显示 测试全屏显示测试全屏显示测试<br> 全屏显示测试全屏显示测试全屏显示测试全屏显示测试全屏显示</p><script> //匿名函数 (function () { //获取事件源和要全屏显示的元素 var elem=document.querySelector("p"); document.querySelector("h3").addEventListener("click",function(e){ //判断浏览器是否支持全屏显示 if (elem.webkitRequestFullScreen) { elem.webkitRequestFullScreen(); } else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen(); } else if (elem.requestFullScreen){ elem.requestFullScreen(); }else{ console.log("浏览器不支持全屏显示") } }); })()</script></body>

8.Application Cache离线访问技术

在H5中可以利用Application Cache实现离线访问技术,测试时可以通过谷歌浏览器——NetWork——No throttling(Disable cache后面的那个下拉框)——下拉选择Offline(模拟未联网的情形)。示例如下:

html代码:<html lang="en" manifest="cache.manifest"><head> <meta charset="UTF-8"> <title>离线访问</title> <link rel="stylesheet" href="style.css"></head><body><p>这是html页面中的内容</p><script src="script.js"></script></body>style.css文件代码body::before{ content: "这是Css中引入的内容"; font-size: 40px;}script.js文件代码:document.write("这是js中引入的内容")cache.manifest文件代码:CACHE MANIFEST# version 1.0.1CACHE: 08离线访问.html script.js style.cssNETWORK: *

解释:在html代码中通过css的伪类和js的document.write函数分别输出两句话,模拟请求其它文件,然后在manifest(缓存清单)中定义了一个cache.manifest的缓存清单文件,文件里首先定义了当前软件的版本,下面的CACHE,代表断网情况下,从下面定义的文件中读取文件,NETWORK代表联网时候要读取的文件,星号代表全部。即断网时从我定义的文件中读取文件,联网时,读取全部文件,这样便可实现离线访问。

9.web网页存储

h5中提供sessionStorage和localStorage两种数据存储方式,前者存储的内容只在本次会话中存储,浏览器关闭数据消失,后者是可以永久存储在浏览器中,除非手动或通过程序删除。这两种存储方式比cookie存储的数据量大,而且可以存储对象数据(cookie只能存储字符串数据)。

<head> <meta charset="UTF-8"> <title>网页存储</title></head><body><textarea rows="5" cols="30" id="txt-data"></textarea><input type="button" value="设置数据" id="set-data"><input type="button" value="获取数据" id="get-data"><script> (function(){ var txtValue=document.querySelector("#txt-data"); console.log(txtValue.value); //设置数据 document.querySelector("#set-data").addEventListener("click",function (e) { //判断是否支持存储 if(window.sessionStorage){ sessionStorage.setItem("key",txtValue.value); }else{ console.log("还在用渣渣IE吗?升级吧。。。") } }) //获取数据 document.querySelector("#get-data").addEventListener("click",function (e) { //判断是否支持存储 if(window.sessionStorage){ txtValue.value=sessionStorage.getItem("key"); }else{ console.log("还在用渣渣IE吗?升级吧。。。") } }) })()</script></body>

10.操作文件Api

通过file表单选择文件,之后利用h5提供的文件api可以获取文件名,大小,类型,最后修改时间等文件信息。

<html lang="en"><head> <meta charset="UTF-8"> <title>操作文件</title> <link rel="stylesheet" href="css/bootstrap.css"></head><body> <div class="container"> <form> <input type="file" class="form-control hidden" id="btn_file" multiple> <input type="button" value="选择文件" class="btn btn-success" id="btn_select"> <div class="list-group" id="ul_list"> <!--待追加数据位置--> </div> </form> </div></body><script> (function(){ var btn_file=document.querySelector("#btn_file"); var btn_select=document.querySelector("#btn_select"); var ul_list=document.querySelector("#ul_list"); //利用“hidden”类将文件输入框隐藏,当点击“选择文件按钮时”,调用选择文件按钮,选择文件, // 因为input type="file"这个无法修改样式,把它隐藏,点击其它按钮时,在其它按钮中调用点击上传文件 btn_select.addEventListener("click",function(){ btn_file.click(); }) //选择文件完成后(即文件内容发生改变后) btn_file.addEventListener("change",function(e){ var files=btn_file.files; //获取所有文件,并遍历 for(var i=0;i<files.length;i++){ console.log(files[i]); var liem=document.createElement("li"); liem.setAttribute("class","list-group-item"); liem.innerHTML=' <h4 class="list-group-item-heading">'+files[i].name+'</h4>'+ '<p class="list-group-item-text">'+files[i].lastModifiedDate.toLocaleTimeString()+' '+(files[i].size/1024).toFixed(2)+'kb</p>' ul_list.appendChild(liem); } }) })()</script>

这里是用到了bootstrap的样式,因为file表单比较丑,而且无法改变样式,所以我们可以把文件表单隐藏(用hidden隐藏样式),提供一个按钮,当点击这个按钮时去雕砌选文件的弹框,multiple属性支持一次选择多个文件。

11.拖拽文件

拖拽文件时需要依次注册以下事件:

  • dragenter 注册拖拽进入事件

  • dragleave 注册拖拽离开事件

  • dragover 注册滑动事件(在这里面阻止默认事件)

  • drop 注册松手落地事件。 在拖动松手落地事件中判断拖进来的是文件还是图片地址还是文字,分别进行处理。e.dataTransfer获取拖动进来的信息。

<head> <meta charset="UTF-8"> <title>拖拽文件</title> <link rel="stylesheet" href="css/bootstrap.css"> <style> #target{ height: 200px; border: 5px dashed #c0c0c0; color: #505050; padding: 20px; font-size: 40px; cursor: pointer; text-align: center; -webkit-user-select: none; } #target.actived{ border-color: #888; color: #080808; box-shadow: 0 0 80px #e0e0e0 inset; } </style></head><body><div class="container"> <div class="page-header"> <h3>拖拽文件</h3> </div> <div class="jumbotron"> <p>我们这里测试拖拽文件</p> <img src="test.png"> </div> <div id="target"> 拖拽文件进来 <ul id="result" class="list-group"></ul> </div></div></body><script> (function(){ var target=document.querySelector("#target"); var fileList=document.querySelector('#result'); //注册拖拽进入事件 target.addEventListener("dragenter",function(){ this.classList.add("actived"); //添加样式 }) //注册拖拽文件离开事件 target.addEventListener("dragleave",function(){ this.classList.remove("actived"); //添加样式 }) //注册落地事件之前必须要先阻止默认事件,这里在滑动里面阻止默认事件 target.addEventListener("dragover",function(e){ e.preventDefault(); //阻止默认事件 e.stopPropagation(); }) //注册松手落地事件 target.addEventListener("drop",function(e){ //判断拖入进来的是文件,图片还是文字,分别处理 if(e.dataTransfer.files.length){ //拖入的是文件 var files=e.dataTransfer.files; for (var i=0; i < files.length; i++) { var li=document.createElement('li'); li.setAttribute('class', 'list-group-item'); // 创建信息的子节点 li.innerHTML='<h5 class="list-group-item-heading">' + files[i].name + '</h5><p class="list-group-item-text">' + files[i].lastModifiedDate.toLocaleDateString() + ' ' + files[i].lastModifiedDate.toLocaleTimeString() + ' ' + (files[i].size / 1024).toFixed(2) + 'KB</p>'; fileList.appendChild(li); } }else{ //不是文件 var data=e.dataTransfer.getData('text/plain'); //判断拖入的是文本还是图片 if(data){ //拖入的是文本,直接置换 target.innerHTML=data; }else{ //拖入的是图片 var imgElem=document.createElement("img"); imgElem.src=data; target.appendChild(imgElem); } } }) })()</script>

12.访问设备信息

h5提供了许多可以访问设备的Api,但是目前基本都应用在手机端,pc端使用非常有限。

  • 获取网络状态。

// × 所有PC浏览器现在都不支持 只有手机端的Firefoxvar connectionInfo=navigator.connection;或者下面这个方法:if (navigator.onLine){ console.log('online');}else{ console.log('offline');}
  • 获取重力感应方向

window.addEventListener('deviceorientation', function(event) { var a=event.alpha; // Y轴 上下方向 var b=event.beta; // Z轴 东西方向 var g=event.gamma; // X轴 南北方向});
  • 加速度计

window.addEventListener('devicemotion', function(e) { // 获取加速计数据 {x,y,z} var acceleration=e.accelerationIncludingGravity;});
  • 地理坐标

navigator.geolocation.getCurrentPosition(function(e) { // e.coords.longitude : 经度, e.coords.latitude : 纬度 document.querySelector('#result').innerHTML=JSON.stringify(e.coords);}, function(e) { document.querySelector('#result').innerHTML=JSON.stringify(e);});navigator.geolocation.watchPosition(success, error);

H5还有其它的一些特性,包括语义化标签,ARIA无障碍互联网应用,多媒体(包括音频,视频)等等,真正使用的时候再详细研究。