a标签实现下载操作

591 阅读4分钟

a标签实现下载操作

实验目标

  • 使用html a标签实现下载、修改下载名称功能【同源】
  • 使用js 代码创建a标签实现下载、修改下载名称功能【同源】
  • 非同源资源使用a标签实现下载、修改下载名称功能,比如前后端分离项目调用后端接口下载资源、下载第三方资源【非同源】

实验依赖

vscode、express、node、live server(vscode插件)

源码:github.com/skylar2826/…

实验过程

实验一:html a标签实现下载

核心源码

<ul>
  <li>
    未指明download, pdf文件类型可解析,走预览模式:
    <a href="/1.pdf" rel="noopener">1.pdf 预览</a>
  </li>
  <li>
    指明download, 走下载:
    <a href="/1.pdf" download rel="noopener">1.pdf 下载</a>
  </li>
  <li>
    修改下载名称:
    <a href="/1.pdf" download="2.pdf" rel="noopener"
      >1.pdf 下载名称改为2.pdf</a
    >
  </li>
  <li>
    未指明download, exe文件类型不可解析,走下载:
    <a href="/1.exe" rel="noopener">1.exe 下载</a>
  </li>
</ul>

演示

aa098dc780af4b5f93debcffef9b51d8tplv-k3u1fbpfcp-watermark.gif

小结

  • rel: noopener 保证安全打开新链接[1];

  • a标签默认行为为浏览器及其插件可解析文件走预览,不可解析文件走下载;

    • 可解析文件,如txt、pdf, 不可解析文件,如exe;
  • 配置download参数,表明走下载操作;

    • download参数仅在同源文件下生效[2];
  • 未配置download属性会发送请求,请求依据responseHeader content-type[7]字段判断文件类型;

  • download属性值为修改后的文件名称,download=""则使用原名称;

实验二:js 创建a标签实现下载

常见使用场景

使用组件库封装组件,但需要在某组件click时触发下载操作; 由于组件被封装,不能通过添加html标签形式实现下载.

核心源码

html源码
<ul>
      <li onclick="downloadFile()">1.pdf 下载</li>
      <li onclick="downloadFile('2.pdf')">1.pdf 下载名称改为2.pdf</li>
      <li onclick="downloadFileByOpen('1.pdf')">1.pdf 下载走window.open</li>
      <li onclick="downloadFileByOpen('1.exe')">1.exe 下载走window.open</li>
    </ul>
js源码
function downloadFile(rename) {
    const aTag = document.createElement("a");
    aTag.href = "/1.pdf";
    aTag.rel = "noopener";
    // download=''取文件原名称
    aTag.download = "";
    if (rename) {
      // 修改下载名称:
      aTag.download = rename;
    }
    document.body.appendChild(aTag);
    aTag.click();
    aTag.remove();
 }
 function downloadFileByOpen(file) {
    window.open('http://127.0.0.1:5500/front/' + file)
  }

演示

1.gif

小结

  • 可以通过创建a标签或者window.open方式下载文件

  • window.open相当于创建target="_blank"的a标签

    • 默认能解析走解析,不能解析走下载
    • href若是后端接口则参考实验三:非同源文件下载

实验三:非同源文件下载

核心源码

html源码
<ul>
  <li>
    <a href="http://localhost:8000/getExe1" download rel="noopener">1.exe 不能解析走下载</a>
  </li>
  <li>
    <a href="http://localhost:8000/getPdf1" download rel="noopener">1.pdf 能解析走预览</a>
  </li>
  <li onclick="downloadAsyncFileByBlob()">1.pdf blob形式可修改名称</li>
  <li onclick="downloadAsyncFileByResponse()">
    1.pdf 配置response conten-type形式,统一走下载
  </li>
</ul>
js 源码
function downloadAsyncFileByBlob() {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        const blob = new Blob([xhr.responseText]);
        const aTag = document.createElement("a");
        aTag.href = URL.createObjectURL(blob);
        aTag.rel = "noopener";
        // 修改下载名称:
        aTag.download = "新名字.pdf";
        document.body.appendChild(aTag);
        aTag.click();
        aTag.remove();
      }
    };
    xhr.open("GET", "http://localhost:8000/getPdf1", true);
    xhr.send();
}
function downloadAsyncFileByResponse() {
    const aTag = document.createElement('a');
    aTag.href="http://localhost:8000/getPdf1ByOctet";
    aTag.rel="noopener";
    // download不生效,原因:非同源
    // aTag.download="新名字2.pdf";
    document.body.appendChild(aTag);
    aTag.click();
    aTag.remove();
}
sever 源码
app.get('/getPdf1', (req, res) => {
  res.set('Access-Control-Allow-Origin', '*')
  res.sendFile(`${__dirname}/static/1.pdf`);
})
app.get('/getExe1', (req, res) => {
  res.set('Access-Control-Allow-Origin', '*')
  res.sendFile(`${__dirname}/static/1.exe`);
})
app.get('/getPdf1ByOctet', (req, res) => {
  res.set('Access-Control-Allow-Origin', '*')
  // res.set('Content-Type', 'application/octet-stream');
  // 不设置这个参数,下载的文件名称就是“getPdf1ByOctet”
  res.set('Content-Disposition','attachment;filename=back.pdf');
  res.sendFile(`${__dirname}/static/1.pdf`);
})

演示

e1cb95e0f3564211b65ad27cda409895tplv-k3u1fbpfcp-watermark.gif

小结

  • http://127.0.0.1:5500/1.html向localhost:8000请求跨域了,所以设置Access-Control-Allow-Origin=*;

    • 域名、端口、协议任一不同,都是跨域;
  • 非同源请求,download属性不生效;浏览器及其插件可解析走预览,不可解析走下载;

期望浏览器统一走下载怎么处理呢?

方案一:配置responseHeader Content-Disposition=attachment;

  • Content-Disposition[8]指示回复内容以何种形式展示,inline表示以内联形式展示,attachment表示以附件形式下载并保存到本地

  • Content-Type[7]告诉客户端实际返回内容的类型;

  • 配置Content-Disposition=attachment表示以附件形式下载并保存到本地可以实现非同源文件统一下载功能;配置Content-Type="application/octet-stream"表示返回文件是文件流的格式,浏览器默认处理文件流的方式是下载,也可以实现非同源文件统一下载的功能;

    • 下载文件名称默认为请求url的最后一段,如localhost:8000/getA, 则文件名称是getA
    • Content-Disposition=attachment;filename=xxx.exe可修改文件名称为xxx.exe

方案二:创建Blob:URL,blob:URL为同源URL,配置download属性表明进行下载操作

期望修改下载文件名称怎么处理呢?
  • 创建blob同源URL,再使用download属性修改文件名称
  • Content-Disposition=attachment;filename=xxx.exe也可修改文件名称为xxx.exe

相关资料

  1. rel=noopener
  2. a标签 download属性
  3. blob相关资料
  4. XMLHttpRequest状态码
  5. a标签download属性不起作用
  6. application/octet-stream
  7. Content-Type
  8. Content-Disposition