href标签下载属性 - 如何强制下载外部托管的图像?

6

我想制作一个<a href链接,当点击它时,会弹出“另存为”对话框。在所有现代浏览器上,使用HTML5的download属性应该有这种行为。

当目标是外部托管的图像文件时,这种方法不起作用。 <a href链接将导航到图片而不是下载它。(在Imgur和Tumblr上测试)

<a href="https://istack.dev59.com/440u9.webp" download>
  <img src="https://istack.dev59.com/440u9.webp" width="200"/>
</a>

如果图片托管在您的服务器上,类似的HTML代码可以正常工作,例如:

<a href="440u9.png" download>
  <img src="440u9.png" width="200"/>
</a>

另一个例子-这个例子可以在W3Schools Tryit Editor中工作,但是如果您将HTML复制到另一个沙盒(如JSFiddle),则无法工作。

<a href="https://www.w3schools.com/images/myw3schoolsimage.jpg" download>
  <img src="https://www.w3schools.com/images/myw3schoolsimage.jpg" width="104" height="142">
</a>


可能的原因:

  • 图片URL有问题:不是,URL没有问题,在浏览器中可以将URL作为普通图片打开
  • 使用https链接可能会因安全性问题而无法使用:不是,如果URL指向相同的域名,则带有https:的URL可以使用,已在W3Schools Tryit Editor上测试
  • 服务器端重定向:一个http://...链接可以被重定向到https://...,所以这可能是失败的原因之一,但https://...链接也无法正常工作
  • 特定主机抑制服务器上的下载属性?这种情况是否可能?下载属性应该仅控制客户端浏览器操作,而不是服务器端的操作,对吗?

有什么解决办法吗?我已尝试了在这里这里讨论的Javascript下载方法,但最终都需要类似于点击href链接的浏览器操作。

有关下载链接的深入讨论,包括在PHP中设置application/force-download属性。

相关的StackOverflow问题:强制外部下载URL(答案似乎是不正确的)。

2个回答

4
结果表明,以我所要求的方式是无法完成的。现代浏览器出于安全考虑阻止跨域下载。
参见: 如何使Firefox和Chrome下载特定名称的图像 强制<a download />下载图像而不是打开图像链接的URL

如果下载文件必须与原始文件完全相同,目前我能找到的唯一解决方案是:
  1. 服务器必须首先使用httpClient请求获取目标资源,在服务器本地将其保存到磁盘上,然后提供一个带有URI的<a href='tmp.jpg' download>链接,该URI为已保存的文件。或者,某些服务器框架能够创建模拟文件访问的字节流,在这种情况下,资源可以缓存到内存中而不是保存到磁盘上。

    缺点:数据成本等于文件大小的两倍(一次进,一次出),往返延迟,使用服务器内存/文件系统。可能需要在页面服务之前在服务器上本地存储每个具有下载链接的可能文件(或者可以使用WebSockets异步完成,用户单击下载链接后再提供文件,但会有延迟)。对于大文件或高使用率服务器,这些都是严重的缺点。

  2. 在新标签页中打开目标URL,让客户端切换到该标签页,然后手动右键另存为...

    缺点:这正是我们试图避免的!

我仍然在思考是否有一些技巧可以实现这一点,比如提供一个本地URL,并让服务器将对该特定资源的请求重定向到外部网站。


3
我的解决方案:将图像下载到画布中,然后将画布图像下载到本地驱动器。
代码如下:
    <canvas id="downloadCanvas"></canvas>

    <script>
        var strDataURI = "https://78.media.tumblr.com/33f1b72778ca7352979b0f4555f08f02/tumblr_pfega8eUyH1r68v93o1_640.jpg";
        var img = new Image;
        img.crossOrigin = "Anonymous";
        img.src = strDataURI;


        var canvas = document.getElementById('downloadCanvas');
        var ctx = canvas.getContext('2d');

        img.onload = function(){

          canvas.width = img.width;
          canvas.height = img.height;
          ctx.drawImage(img,0,0);

          var dwBtn = document.getElementById("dw0");
            var dwLink = canvas.toDataURL('image/jpeg', 1.0);

          download(dwLink, "image.jpeg");
        };


    function download(dataurl, filename) {
      var a = document.createElement("a");
      a.href = dataurl;
      a.setAttribute("download", filename);
      var b = document.createEvent("MouseEvents");
      b.initEvent("click", false, true);
      a.dispatchEvent(b);
      return false;
    }

</script>

正如OP所要求的那样,他需要捕获的图像具有相同的扩展名。(伪代码)

更改以下行:

  var ext = ExtensionFromUrl(url);
  var fileName = NameFromUrl(url);

      var dwLink = canvas.toDataURL('image/'+ext, 1.0);
      download(dwLink, fileName+"."+ext);

请注意:此答案不适用于GIF图像。此外,由于它们是捕获的图像,透明通道将会丢失!

1
@radfast 谢谢。如果我的代码对你有用,请不要忘记点击“接受答案”按钮! - Nick
测试更详细,这是一个聪明的解决方法 - 但它从画布编码了一个新的JPEG文件,而不是下载原始的服务文件。因此,保存到磁盘的文件与我从右键另存为得到的文件不同。 - radfast
@radfast 是的,这是一个不同的文件。它是从画布中捕获的图像。要更改名称或扩展名,您需要修改代码以从URL或任何其他来源获取它们,就像您希望的那样。但是,在更详细地测试后,你可以责怪我,因为这种解决方法无法处理GIF文件。我也会在我的答案中指出这一点,以便对于那些想要提出更好的解决方案的人来说,他必须知道其中的问题所在。 - Nick
同时,我正在寻找一种方法使我的Web应用程序从外部链接服务器端下载字节流(我的服务器将从外部链接获取文件),然后在完成后将其作为本地文件提供给客户端(现在托管在我的服务器上)。这不是一个非常好的解决方案,因为它会在服务器上创建瓶颈并导致数据使用量增加一倍。 - radfast

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接