您现在的位置是:首页 > 正文

前端实现图片下载的方法

2024-04-01 04:43:51阅读 3

一、介绍

​ 在前一段时间的工作中,我在工作中遇到了一个点击按钮下载图片的场景,这在业务中是很常见的一种场景,但比较特别的是,我这次是需要在移动端的浏览器以及微信内部浏览器等场景下能实现点击下载。经过多次的探索和实践,我最终发现,以我目前的能力和能查阅的资料,在如今国内的复杂移动端浏览器环境下(百度浏览器、QQ浏览器、UC浏览器、夸克浏览器以及各大手机厂商的内置浏览器等等),他们各自拥有着不同的安全策略,在移动端实现一种覆盖所有主流浏览器的通用点击下载是不可能的,因为我所了解到的各种实现方法基本都是依赖于<a>标签或者<form>表单进行下载,但是在大多数移动端浏览器中,会默认对这种行为进行拦截,从而无法实现下载。

​ 而且还有一点是:在任何方案下,前端都无法绕过跨域的限制,所以需要图片所在的服务器对你当前域名开放权限,否则是无法下载的,最多能做到查看图片。

​ 最终解决方案为:跟需求方和产品沟通后,决定修改页面逻辑,提示用户长按保存图片,从而实现下载。当然如果哪位大佬还有跟好的方案能够实现在移动端的点击下载,那也欢迎在评论区多多指教分享。

​ 当然,我前期所做的各种方案的探索,自然也不是无用功,这些方案在PC端可以正常使用,只是在移动端会出现各种各样的问题,也正是因此有了这篇博客,分享多种图片下载的方法。

二、具体方案

1、直接使用<a>标签的download属性(推荐)

​ 这是我最开始所尝试的方案,该方案的优点在于:代码简单清晰,只需在<a>标签中增加download属性,并将图片地址赋给href属性即可。缺点在于:该方法只能下载同源URL或blob:data: 协议的文件,对于非同源的图片只能实现查看。

​ 我的业务中想下载的图片,放置在另一台资源服务器下,不在同一个域名下,遂放弃该方案。

实现代码:

<a href="https://example.com/image.jpg" download="image.jpg">下载图片</a>
2、canvas对象+<a>标签(推荐)

​ 这是我之前PC端所实现的方案,通过canvas对象将图片转成base64格式,然后将其赋予<a>标签的href属性,通过click事件触发下载。该方案可以在PC端实现下载同源图片或非同源但图片的服务器端设置了CORS的图片,但在移动端发现在小米(红米)、华为、vivo等手机的原生浏览器中可以下载,但是各大浏览器会拦截<a>标签的下载行为,具体表现形式有所不同,但最终无法实现下载。

实现代码:

function download(item) {
  // 创建图片Image对象
  let image = new Image();
  // 以CORS的方式去请求这张图片 服务端需要设置CORS进行配合
  image.setAttribute("crossOrigin", "anonymous");
  // 图片加载完成后进行操作
  image.onload = function () {
    // 生成canvas图像
    let canvas = document.createElement("canvas");
    canvas.width = image.width;
    canvas.height = image.height;
    let context = canvas.getContext("2d");
    context.drawImage(image, 0, 0, image.width, image.height);
    // 得到图片的base64编码数据
    let url = canvas.toDataURL("image/png"); 
    // 生成一个a元素
    let a = document.createElement("a"); 
     // 创建一个单击事件
    let event = new MouseEvent("click");
    // 设置download属性 即下载后的文件名称
    a.download = item.diplomaCode || "photo"; 
    // 将生成的base64编码数据设置为a.href属性
    a.href = url; 
    // 触发a的单击事件 进行下载
    a.dispatchEvent(event); 
  };
  // 设置图片地址 开始加载
  image.src = item.imgUrl;
}
3、ajax请求(blob或base64格式)+<a>标签(推荐)

​ 该方案是通过ajax请求,获取到blobbase64格式的图片文件流,然后在通过<a>标签实现下载。该方法可以在PC端正常下载同源图片或非同源但图片的服务器端设置了CORS的图片,但在移动端同样会被各大浏览器拦截<a>标签的下载行为,从而最终无法实现下载。

​ 如果想要获取blob类型的数据,则只需要前端配置ajax请求的responseType属性即可,无需后端配合。如果想要获取base64格式的数据,则需要后端进行配合修改数据,前端无需做特别配置。

实现代码

// 获取blob类型的数据进行下载
function download2(item) {
  // 创建ajax请求
  var xhr = new XMLHttpRequest();
  xhr.open("GET", item.imgUrl, true);
  // 设置请求的响应数据为 blob 类型
  xhr.responseType = "blob";
	// 请求成功后
  xhr.onload = function () {
    if (this.status === 200) {
      // 将响应数据转换成blob对象
      var blob = new Blob([this.response], { type: "image/jpeg" });
      // 将blob转成一个url对象
      var url = URL.createObjectURL(blob);
      // 创建a标签并进行点击下载
      var a = document.createElement("a");
      a.href = url;
      a.download = item.diplomaCode || "photo";
      a.click();
      // 清除临时的url对象 解放内存
      URL.revokeObjectURL(url);
    }
  };
	// 发送请求
  xhr.send();
}
4、domtoimage+<a>标签(不推荐)

​ 该方法是我在经过上面的实践之后发现,都是通过将数据转换成base64或blob类型的数据之后,然后再通过<a>标签进行下载,然后我就想到了,将图片渲染到页面上之后,然后在通过domtoimage这个依赖包将图片的dom元素转成base64格式的图片,然后再下载。
事实证明,这种方案多少有点脱裤子放屁的嫌疑,当然因为同上面一样的限制,无法在移动端渲染,以及无法实现对跨域图片的下载。

实现代码

<img src="https://..." alt="" id="ZS1" />

<script>
function download3() {
  // 通过依赖根据图片渲染的dom元素生成base64格式的图片
  domtoimage.toPng(document.querySelector(`#ZS1`)).then((dataUrl) => {
    // 创建一个a元素
    let a = document.createElement("a"); 
    // 设置download属性
    a.download = "xkw证书"; 
    // 将生成的base64设置为a.href属性
    a.href = dataUrl; 
    // 点击下载
    a.click();
  });
}
</script>
5、form表单(未验证,仅供参考)

​ 该方案是借助form表单元素实现图片的下载,需要服务端设置图片流的响应头Content-Disposition的值,相当于将图片以文件附件的形式进行下载,具体相关内容请查看参考文档中的第二、第三条。

​ 但该方案需要服务端进行配合,且会影响其他使用该图片的项目,因此我没有进行实际验证,如果有验证过的大佬,可以在评论区说一下。

实现代码

function download4() {
	// 创建一个隐藏的表单
	const form = document.createElement('form');
	form.style.display = 'none';
  // 请求地址即图片地址
	form.action = '/imgurl....';
	// 设置提交请求为 post
	form.method = 'post';
	form.target = '_blank';
	document.body.appendChild(form);
	const params = {;
		name: 'test.jpg';
	};
	// 创建input来传递参数
	for (let key in params) {;
		let input = document.createElement('input');
		input.type = 'hidden';
		input.name = key;
		input.value = params[key];
		form.appendChild(input);
	};
  // 进行提交请求
	form.submit();
	form.remove();
}
6、iframe(未验证,不推荐)

​ 该方案只在IE中被支持,需要借助document.execCommand('SaveAs')命令,将图片通过iframe打开并保存,要求图片是同源的。

实现代码

function download5(item) {
  const iframe = document.createElement("iframe");
  iframe.style.display = "none";
  iframe.onload = () => {
    iframe.contentWindow.document.execCommand("SaveAs");
    document.body.removeChild(iframe);
  };
  iframe.src = item.imgUrl;
  document.body.appendChild(iframe);
}

三、参考文档

MDN的a标签文档

参考博客

Content-Disposition

网站文章

  • mybatis运行原理详解

    mybatis运行原理详解

    第一部分:项目结构user_info表:没什么好说的就3个字段User实体类:@Datapublic class User { private Long id; private Strin...

    2024-04-01 04:43:44
  • Leetcode刷题详解——子集

    Leetcode刷题详解——子集

    在递归时我们需要保证递归结束时当前的状态与进行递归操作前的状态不变,而当我们在选择进行步骤2进行递归时,当前状态会发生变化,因此我们需要在递归结束时撤回添加操作,即进行回溯。数组一定存在2^(数组长度...

    2024-04-01 04:43:36
  • 计算机毕业设计/课程设计系列基于SpringBoot的校园问答论坛管理系统

    计算机毕业设计/课程设计系列基于SpringBoot的校园问答论坛管理系统

    本课程演示的是一款基于SpringBoot的校园问答论坛管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。1. 包含:项目源码、项目文档、数据库脚本、软件工具等...

    2024-04-01 04:43:28
  • DVWA渗透测试演示(中)

    DVWA渗透测试演示(中)

    续DVWA渗透测试演示(上):六、DVWA之FileInclusion:(1)实验原理:PHP文件包含漏洞的产生原因是在通过PHP函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预料之外...

    2024-04-01 04:42:53
  • [计算机网络]应用层协议,HTTP,SMTP,DNS

    [计算机网络]应用层协议,HTTP,SMTP,DNS

    应用层应用层协议原理网络应用程序体系结构规定如何在各种端系统上组织应用程序,由研发者设计客户机/服务器服务器:对外提供服务的一系列硬件和软件客户机:使用服务器提供的服务服务器7*24小时提供服务永久性...

    2024-04-01 04:42:47
  • VS 2005中使用C#创建及调用WebService完整实例

    关于WebService的概念及相关说明,在这里就不多说了,直接说如何实现IIS配置安装IIS(Windows 默认安装即可),确认可以从其他IP访问本机的默认主页,安装ASP.NET 2.0到IIS...

    2024-04-01 04:42:40
  • win10如何调整计算机时间同步,Win10系统如何设置时间同步间隔?修改时间同步频率的方法...

    win10如何调整计算机时间同步,Win10系统如何设置时间同步间隔?修改时间同步频率的方法...

    Win10系统如何设置时间同步间隔?如果系统时间并没有那么精确,偏差很多,你一定会去勾选“自动同步时间”的功能吧?!可是左等右等也不见调整时间,是这项功能没用吗?其实系统时间同步的频率是有间隔的,且这...

    2024-04-01 04:41:58
  • ES集群中节点与分片的区别

    ES集群中节点与分片的区别

    ES集群中节点与分片的区别 一开始我也搞混了,以为分片就是节点 节点:节点就是我们一个个的主机,你也可以理解为一个个的ES 分片:分片就是将原来存放在一个节点上面的数据进行分片,然后存放到不同的节点。...

    2024-04-01 04:41:50
  • android 从视频中提取音频及录音后合并到视频

    1.前段时间想开发一个app,用来提取mp4中的音频然后进行裁剪MediaExtractor extractor = new MediaExtractor();//新建提取器extractor.setDataSource(…);int numTracks = extractor.getTrackCount();for (int i = 0; i < numTracks; ++i) ...

    2024-04-01 04:41:44
  • MySQL可重复读和读已提交实现原理,深入理解MVCC。

    MySQL可重复读和读已提交实现原理,深入理解MVCC。

    1.隔离级别 MySQL中隔离级别分为4种,提未交读、读已提交、可重复读、串行化。同时MySQL默认隔离级别为可重复读。 图片 查看MySQL隔离级别 SELECT @@tx_isolation 设置...

    2024-04-01 04:41:03