35

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。

这两天,碰到了不止一次前端下载的的问题。其实之前我写过一篇文章 download使用浅析,主要依靠 download 属性来实现浏览器端下载,因为是走浏览器的下载,所以没有进度条。今天我们就来说说我的解决方案。

  1. sf 的一个问题,需要显示进度条。答案地址问题地址
  2. 一个朋友的问题,下载的文件需要 headers 验证,无奈只能 ajax 拿数据,但是拉回来的还是字符串,需要自己处理。
  3. 一个朋友的问题,监测下载进度。

今天我们要讲什么?

  1. 如何使用 download 属性,下载文件。
    这节主要是讲如何使用,以及前端下载的核心操作。
  2. 下载文件,并显示进度条。
    这节是正常操作,如果你只为了解原理,看到这里就够了
  3. 其他数据类型如何互相转换
    这节就不一样了。因为之前的 api 是使用 blob 实现,但是 ajax 传回来的数据有好多种类型,我们如何将他们相互转换?

如何使用 download 属性,下载文件。

download使用浅析 这一文中已经介绍了,可以去看看。我这里简单说一下。<a>标签如果设置了 download 属性,他就会去下载这个地址。测试地址-原生 download 属性测试

下载文件,并显示进度条

下载文件上面已经实现了,那我们先说说如何显示进度条。

显示进度条

其实浏览器也是有进度条的,但是咱们拿不到。那我们就来模拟一下载,然后显示进度条。

  1. ajax 实现下载进度条,测试地址-显示进度条

    xhr = new XMLHttpRequest();
    xhr.open('get', file1.url);
    xhr.onprogress = (e)=>console.log(e)//e 就是一个 ProgressEvent 对象,其中 loaded 是已下载的, total 是总大小。
    xhr.send()
  2. fetch 实现下载进度条,测试地址-fetch显示进度条并下载
    fetch 的实现上来说有一些功能是没有的,比如 abort、进度等。那我们就需要去通过一些别的手段来模拟实现。
    实现代码如下,我们操作成读流,然后统计长度。
    clipboard.png

下载文件

进度条已经显示好了,那我们可以下载文件了。首先我们要分几种情况

  1. 缓存下载(一个资源如果已经下载完了,再次去访问)
    clipboard.png
  2. 本地下载(资源已经在浏览器中)

    1. blob url 下载 如这种地址 blob:https://www.lilnong.top/deb4c297-821c-4545-9b23-0fbdd76890c7
      clipboard.png
    2. base64 url 下载 如这种地址 data:application/octet-stream;base64,aGVsbG8gbGlub25n

        blob = new Blob(['hello linong'])
        freader = new FileReader()
        freader.readAsDataURL(blob)//将 blob 读成 dataurl
        freader.onload=e=>console.log(freader.result)// 异步的,所以需要回调里面拿

      clipboard.png

  3. 无缓存下载(资源没在本地,也没有缓存)

情况就是上面几种,那我们要做的其实就是统一一下流程

  1. ajax 拉取数据(显示进度条)
  2. 缓存了数据,然后下载缓存(因为是缓存,所以秒下)

    1. 浏览器缓存
      我比较推荐用这个,因为其他的方案都有大小或者兼容上的问题。但是这个缓存需要服务器设置需要走缓存。
    2. bloburl 本地缓存下载
      这个方案在移动端异常,pc端正常,感兴趣的小伙伴可以自己实现一下,毕竟学了这么多,得用起来才能变成自己的
    3. dataurl 本地缓存下载
      这个方案的支持会比 bloburl 好一点,但是只适用于小文件。

实现前端下载文件并显示进度条

我们让 ajax 直接返回 blob。然后构建 bloburl 用于下载。

downloadFile2 = (url)=>{
    var xhr = new XMLHttpRequest();
    xhr.open('get', url);
    xhr.responseType='blob';//这是精髓
    xhr.onprogress = onprogress2;//下载进度
    // .upload.onprogress 这个是上传的时候的进度
    xhr.onreadystatechange = ()=>{
        if(xhr.readyState == 4 && xhr.status == 200){
            nativeDownload(URL.createObjectURL(xhr.response))
        }
    }
    xhr.send()
}

其他类型转换为 blob

如果是一些封装过的 ajax,没办法使用 xhr.responseType='blob' 之类的,返回回来是字符串。那我们需要怎么出转换呢?

blob to *

blob 需要配合 FileReader 来读取

  1. blob to arrayBufferreadAsArrayBuffer
    通用的、固定长度的原始二进制数据缓冲区

    var fileReader = new FileReader();
    fileReader.readAsArrayBuffer(xhr.response);//xhr.reponse 是 blob 类型
    fileReader.onload = e=>console.log(fileReader.result);

    clipboard.png

  2. blob to DataURLreadAsDataURL

    Base64 是一组相似的二进制到文本(binary-to-text)的编码规则,使得二进制数据在解释成 radix-64 的表现形式后能够用 ASCII 字符串的格式表示出来。Base64 这个词出自一种 MIME 数据传输编码。 --MDN

    clipboard.png

  3. blob to TextreadAsText
    以字符串表示所读取的文件内容
    clipboard.png
  4. blob to BinaryStringreadAsBinaryString
    文件的原始二进制数据
    clipboard.png

* to blob

  1. arrayBuffer to blob
    new Blob([arrayBuffer], {type: 'image/jpeg'})
  2. base64 to blob

    (new Uint8Array(Array.from(atob(base64url.split(',')[1])).map(v=>v.charCodeAt()))).buffer
    //base64url.split(',')[1] //截取不要 data:images/jpeg;base64, 这串
    //atob //转换成 BinaryString
    //Array.from //转换成数组
    //map(v=>v.charCodeAt()) //转换成对应的 ascii 码
    //Uint8Array 转换成 Uint8Array 然后输出 buffer

    clipboard.png

  3. BinaryString to blob
    方案同上,因为上面的也是转换成了BinaryString
  4. Text to blob
    ajax 默认就是 Text 类型的返回值。这个我觉得是编码类型的转换,比如 utf-8 to ascii,目前我还没找到好的实现方法。

    clipboard.png

总结

之前就写过一篇AJAX 的进阶使用(Blob、ArrayBuffer、FormData、Document、JSON、Text),里面讲了这些支持的类型。
base64转换上传,也写过这样的。

前端目前需要操作的东西越来越多了。

在最上面那个问答里,有个库去实现 download 操作。实现原理可以自己去看看。

后记

参考资料

  1. Data URLs --MDN
  2. StringView
  3. 字符编码中ASCII、Unicode和UTF-8的区别
  4. String​.prototype​.char​CodeAt()
  5. DOMString
  6. rfc2397

linong
29.2k 声望9.5k 粉丝

Read-Search-Ask