使用JavaScript在新窗口中打开PDF字符串

89

我有一个格式化的PDF字符串,看起来像这样:

%PDF-1.73 0 obj<<< /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R/Contents 4 0 R>> endobj4 0 obj<> streamx��R=o�0��+��=|vL�R���l�-��ځ,���Ge�JK����{���Y5�����Z˯k�vf�a��`G֢ۢ��Asf�z�ͼ��`%��aI#�!;�t���GD?!���<�����B�b��

...

00000 n 0000000703 00000 n 0000000820 00000 n 0000000926 00000 n 0000001206 00000 n 0000001649 00000 n trailer << /Size 11 /Root 10 0 R /Info 9 0 R >>startxref2015%%EOF

我想在新窗口中打开这个字符串作为PDF文件。每当我使用window.open()并将字符串写入新标签时,它会认为该文本应该是HTML文档的内容。我希望它能识别出这是一个PDF文件。
非常感谢您的帮助。

1
你是如何“编写”这个字符串的?你的意思是它是由后端返回的吗(如果是,那么是哪个平台)?如果PDF数据是由后端生成的,请确保设置正确的Content-Type头。 - Ates Goral
最佳答案:在这种情况下,您不应该使用AJAX。 - Josh Stodola
1
它正在后端创建,但在服务器上保存不可能/优雅。此外,在生成pdf之前已经将内容写入页面...因此设置pdf标题将无法起作用。 - DaveC
2
@Josh Stodola 在这种情况下为什么不应该使用 AJAX。 - user1451111
@DaveC,您找到解决方法了吗? - Zeeshan Chaudhary
显示剩余3条评论
17个回答

127

仅供参考,以下是:

window.open("data:application/pdf," + encodeURI(pdfString)); 

在谷歌浏览器中不再起作用。昨天,我遇到了同样的问题,并尝试了这个解决方案,但没有成功(它是“不允许将顶层框架导航到数据URL”)。您无法直接在新窗口中打开数据URL。 但是,您可以将它包装在iframe中,并像以下示例一样在新窗口中打开。 =)

let pdfWindow = window.open("")
pdfWindow.document.write(
    "<iframe width='100%' height='100%' src='data:application/pdf;base64, " +
    encodeURI(yourDocumentBase64VarHere) + "'></iframe>"
)

7
在Chrome中可以正常工作,但一旦加载后会消耗CPU和GPU,而且选项卡显示仍在加载。尝试使用“iframe”,“embed”和“object”。 - Esamo
4
setTimeout(() => { pdfWindow.document.write(<iframe width='100%' height='100%' src='${url}'></iframe>); pdfWindow.document.title = ${title}; }, 0);这段代码中的 setTimeout-0 可以像这样解决我的问题。它会在下一个事件循环周期中执行函数,从而避免了阻塞主线程。函数将 url 插入到一个占满整个窗口的 <iframe> 中,并将文档标题设置为 title - Robin
4
这个很好用,文档在新标签页中打开。但是,当我点击右上角的下载图标时,什么都没有发生。如何使其可下载? - Akza
2
选项卡仍在加载的问题非常奇怪。 我们团队中有4个成员使用相同的按钮打开PDF文件,对于其中的两个人,它显示在Favicon中进行了加载,但是对于我们中的另外两个人,它不显示加载动画....每个人都使用相同版本的Chrome... - Ray
1
setTimeout(function(){ pdfWindow.stop() }, 1000); 在结尾处停止了加载问题。 - Peter
显示剩余3条评论

70
var byteCharacters = atob(response.data);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
  byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var file = new Blob([byteArray], { type: 'application/pdf;base64' });
var fileURL = URL.createObjectURL(file);
window.open(fileURL);

您从API或其他来源返回一个base64字符串。您也可以下载它。


2
这似乎是迄今为止最好的解决方案。其他解决方案似乎只能偶尔起作用。我一直在尝试对服务器的字符串响应进行编码,但这似乎是唯一可靠的方法。谢谢! - Drew
1
有没有一种方法可以混淆URL。我想要的是文件名。现在它显示为编码的base64值? - Learn AspNet
谢谢你。这救了我的一周。最终只需将PDF作为Blob下载并将其用作示例中的字节数组即可。const blob = new Blob([assetData], { type: mimeType }); resolve(URL.createObjectURL(blob)); - mgamsjager
谢谢,伙计。在这个解决方案中如何更改浏览器标题? - Ajas Aju
2
@himanshu-chawla 真是太棒了的回答!顺便问一下-是否有一种方法可以定义默认文件名,以便在点击下载按钮时,它不会像“6a5e4cd0-2db4-4c8c-80b8-15b77f911700.pdf”这样看起来,而是像“myFile.pdf”这样的? - neggenbe
显示剩余6条评论

25

你可能想要尝试使用数据URI。它看起来会像这样。

window.open("data:application/pdf," + escape(pdfString));

我没有立即能够使这个工作起来,可能是因为提供的二进制字符串的格式。当使用数据URI时,我通常也会使用base64编码的数据。如果您能够从后端传递编码的内容,您可以使用...

window.open("data:application/pdf;base64, " + base64EncodedPDF);

希望这是你所需的正确方向。请注意,因为IE6/7不支持数据URI,所以这在IE6/7上根本不起作用。


2
也不会在IE 8+中工作,因为URL大小很可能会超过IE对URL施加的2048个字符限制。 - Jeff
我相信这个限制不适用于数据URI,只适用于URL。IE8有一个32KB的数据URI限制,在IE9中被移除了。 - Morgan ARR Allen
2
太好了,但是window.open(url)需要一个URL而不是URI。即使使用数据URI,IE也会截断2048个字符后的任何内容。在IE中尝试此jsfiddle,您会发现它无法正常工作:http://jsfiddle.net/bpdj7ksv/ - Jeff
这对我不起作用 window.open('data:application/pdf;base64,' + btoa(unescape(encodeURIComponent(resp))), "_blank", "toolbar=yes, scrollbars=yes, resizable=yes"); - vini

21

这个对我起作用了。

window.open("data:application/octet-stream;charset=utf-16le;base64,"+data64);

这一个也行

 let a = document.createElement("a");
 a.href = "data:application/octet-stream;base64,"+data64;
 a.download = "documentName.pdf"
 a.click();

我将代码的第二部分放在一个JavaScript函数中,并将其调用为按钮的onclick函数,得到了非常好的答案。 - Hojjat

18
window.open("data:application/pdf," + escape(pdfString)); 
上述问题是将编码后的内容粘贴到URL中,这会限制URL中的内容长度,因此PDF文件加载失败(因为内容不完整)。

6
谷歌浏览器检测到此内容为弹出窗口并进行了阻止。 - aldo.roman.nurena
3
由于微软决定强制实施2048个字符的URL长度限制,因此此代码在MSIE中无法正常工作。 - Jeff
2
此限制不适用于数据URI,仅适用于普通导航URL。参考:http://msdn.microsoft.com/en-us/library/cc848897(v=vs.85).aspx - Morgan ARR Allen
有人知道为什么打开窗口后无法与其交互吗?就像这样 var wdw = window.open("data:application/pdf," + escape(pdfString)); wdw.print(); 打印函数没有任何作用。我甚至尝试使用 setTimeout 来延迟打印,但也不起作用。好像它失去了对窗口对象的引用。如果我做同样的事情但传入实际的网址,它就能工作。 - jtate
当内容长度过长时,您是如何解决这样的问题的? - Hala

17

我知道这是一个相当古老的问题,但今天我遇到了同样的情况并想出了以下解决方案:

doSomethingToRequestData().then(function(downloadedFile) {
  // create a download anchor tag
  var downloadLink      = document.createElement('a');
  downloadLink.target   = '_blank';
  downloadLink.download = 'name_to_give_saved_file.pdf';

  // convert downloaded data to a Blob
  var blob = new Blob([downloadedFile.data], { type: 'application/pdf' });

  // create an object URL from the Blob
  var URL = window.URL || window.webkitURL;
  var downloadUrl = URL.createObjectURL(blob);

  // set object URL as the anchor's href
  downloadLink.href = downloadUrl;

  // append the anchor to document body
  document.body.appendChild(downloadLink);

  // fire a click event on the anchor
  downloadLink.click();

  // cleanup: remove element and revoke object URL
  document.body.removeChild(downloadLink);
  URL.revokeObjectURL(downloadUrl);
});

1
这是一个很好的解决方案!但根据MDN,只能在IE10+中使用。 - Van Coding
在 Chrome 上,我得到了“document.body.append不是一个函数”的错误,所以我使用了以下代码:document.getElementsByTagName('body')[0].appendChild(downloadLink); - manuel-84
谢谢@manuel-84,你发现了一个错别字。一开始应该是appendChild - Chris Cashwell
请注意,添加下载属性(如示例中所示)将导致文件下载而不是在新窗口/选项卡中打开。这在Safari中不受支持。 - Zemljoradnik
@RickyJiao downloadedFile 是从 AJAX 请求(如 fetch)返回的原始文件,因此在 PDF 的情况下,downloadedFile.data 是二进制的。 - Chris Cashwell
显示剩余2条评论

12
一个由@Noby Fujioka更新的答案版本:
function showPdfInNewTab(base64Data, fileName) {  
  let pdfWindow = window.open("");
  pdfWindow.document.write("<html<head><title>"+fileName+"</title><style>body{margin: 0px;}</style></head>");
  pdfWindow.document.write("<body><embed width='100%' height='100%' src='data:application/pdf;base64, " + encodeURI(base64Data)+"#toolbar=0&navpanes=0&scrollbar=0'></embed></body></html>");
  pdfWindow.document.close();
}

4

我想在@Noby Fujioka的回答上补充一点,Edge不支持以下内容:

window.open("data:application/pdf," + encodeURI(pdfString));

对于Edge浏览器,我们需要将其转换为Blob格式,具体方法如下:
//If Browser is Edge
            if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                var byteCharacters = atob(<Your_base64_Report Data>);
                var byteNumbers = new Array(byteCharacters.length);
                for (var i = 0; i < byteCharacters.length; i++) {
                    byteNumbers[i] = byteCharacters.charCodeAt(i);
                }
                var byteArray = new Uint8Array(byteNumbers);
                var blob = new Blob([byteArray], {
                    type: 'application/pdf'
                });
                window.navigator.msSaveOrOpenBlob(blob, "myreport.pdf");
            } else {
                var pdfWindow = window.open("", '_blank');
                pdfWindow.document.write("<iframe width='100%' style='margin: -8px;border: none;' height='100%' src='data:application/pdf;base64, " + encodeURI(<Your_base64_Report Data>) + "'></iframe>");
            }

3
根据之前的回答: escape()函数现已被弃用,
请使用encodeURI()encodeURIComponent()
以下是适用于我的情况的示例:
window.open("data:application/pdf," + encodeURI(pdfString)); 

愉快的编码!


因为这会打开一个新标签,在URL中是PDF内容,所以有一个PDF大小限制吗?我尝试了许多解决方案,只有这个可行,但是...如果我有一个大小为2MB的PDF文件呢? - Mistre83
1
@Mistre83 - 我不完全明白你的问题,但我已经在一些相当大的PDF文件上使用过它,并且它有效。现在无法测试,但可以尝试在一个大的PDF文件上使用它,应该可以工作。如果有大小限制,我会认为它相当大,比如2-4GB(纯猜测)。 - EaziLuizi

2
我在处理 FedEx 货运请求时遇到了问题。我使用 AJAX 执行请求。响应包括追踪号码、费用以及包含运输标签的 PDF 字符串。
以下是我的做法:
添加一个表单:
<form id='getlabel' name='getlabel' action='getlabel.php' method='post' target='_blank'>
<input type='hidden' id='pdf' name='pdf'>
</form>

使用JavaScript将隐藏字段的值填充为PDF字符串并提交表单。

其中getlabel.php:

<?
header('Content-Type: application/pdf');
header('Content-Length: '.strlen($_POST["pdf"]));
header('Content-Disposition: inline;');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
print $_POST["pdf"];
?>

这种方法的问题在于没有办法通知用户文档何时已经下载完成。 - crush
我相信target='_blank'会通知用户吗? - Dan Ross

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