在前一段时间的工作中,我在工作中遇到了一个点击按钮下载图片的场景,这在业务中是很常见的一种场景,但比较特别的是,我这次是需要在移动端的浏览器以及微信内部浏览器等场景下能实现点击下载。经过多次的探索和实践,我最终发现,以我目前的能力和能查阅的资料,在如今国内的复杂移动端浏览器环境下(百度浏览器、QQ浏览器、UC浏览器、夸克浏览器以及各大手机厂商的内置浏览器等等),他们各自拥有着不同的安全策略,在移动端实现一种覆盖所有主流浏览器的通用点击下载是不可能的,因为我所了解到的各种实现方法基本都是依赖于标签或者
表单进行下载,但是在大多数移动端浏览器中,会默认对这种行为进行拦截,从而无法实现下载。
而且还有一点是:在任何方案下,前端都无法绕过跨域的限制,所以需要图片所在的服务器对你当前域名开放权限,否则是无法下载的,最多能做到查看图片。
最终解决方案为:跟需求方和产品沟通后,决定修改页面逻辑,提示用户长按保存图片,从而实现下载。当然如果哪位大佬还有跟好的方案能够实现在移动端的点击下载,那也欢迎在评论区多多指教分享。
当然,我前期所做的各种方案的探索,自然也不是无用功,这些方案在PC端可以正常使用,只是在移动端会出现各种各样的问题,也正是因此有了这篇博客,分享多种图片下载的方法。
标签的download
属性(推荐) 这是我最开始所尝试的方案,该方案的优点在于:代码简单清晰,只需在标签中增加
download
属性,并将图片地址赋给href
属性即可。缺点在于:该方法只能下载同源URL或blob:
、data:
协议的文件,对于非同源的图片只能实现查看。
我的业务中想下载的图片,放置在另一台资源服务器下,不在同一个域名下,遂放弃该方案。
实现代码:
<a href="https://example.com/image.jpg" download="image.jpg">下载图片a>
canvas
对象+
标签(推荐) 这是我之前PC端所实现的方案,通过canvas
对象将图片转成base64格式,然后将其赋予标签的
href
属性,通过click
事件触发下载。该方案可以在PC端实现下载同源图片或非同源但图片的服务器端设置了CORS
的图片,但在移动端发现在小米(红米)、华为、vivo等手机的原生浏览器中可以下载,但是各大浏览器会拦截标签的下载行为,具体表现形式有所不同,但最终无法实现下载。
实现代码:
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;
}
标签(推荐) 该方案是通过ajax请求,获取到blob
或base64
格式的图片文件流,然后在通过标签实现下载。该方法可以在PC端正常下载同源图片或非同源但图片的服务器端设置了
CORS
的图片,但在移动端同样会被各大浏览器拦截标签的下载行为,从而最终无法实现下载。
如果想要获取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();
}
domtoimage
+
标签(不推荐) 该方法是我在经过上面的实践之后发现,都是通过将数据转换成base64或blob类型的数据之后,然后再通过标签进行下载,然后我就想到了,将图片渲染到页面上之后,然后在通过
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>
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();
}
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