事發總有原因,這段程式與 IE 轉 Edge/Chrome 有關。

上回提到因為 Edge/Chrome 都支援用瀏覽器顯示 PDF,若在 window.open() 新開視窗用 <a target="_blank"> 開啟 PDF 連結,PDF 會開在底下瀏覽器的新頁籤,導致操作動線混亂。我想到在 <a> 加註 download 的解法,讓強迫瀏覽器下載檔案,用 Acrobat Reader 之類軟體開啟可避開問題。不過很快接到通報,若使用者將 Edge/Chrome 設成預設開啟 PDF 的應用程式,便會出現「下載 PDF 檔存到本機,然後 PDF 開在底層 Edge 的新頁籤」的可笑結果,靠北! 我繞一大圈是為了回到原點...

另一方面,也有人反映,既然瀏覽器能直接開啟 PDF,強迫下載到本機再開啟是開倒車。上回有讀者也提到這點,只看一次的檔案沒必要特別儲存,將來還要花工夫刪除,不無道理。

幾經琢磨,從善如流,微調前端後端設法解決問題。

第一個要處理的是上回的老問題:如何避免 Edge/Chrome 在新頁籤開啟 PDF?簡單有效的做法是改用 window.open 加上 popup=yes 參數,為防止新開視窗被其他視窗覆寫,我想到一次開滿全螢幕的做法,確保使用者一定會看到,也不會因點其他程式被覆蓋。開滿全螢幕可透過 window.open() 的 top、left、width、height 等參數實現。其中有個眉角:在多螢幕環境,目前所在螢幕的左上角不一定是 0, 0!

以下圖為例,1 號螢幕解析度為 1280x720,因擔任主螢幕左上角 top 與 left 均為 0;2 號螢幕緊接 1 號螢幕右側,故 left 為 1280,其頂端高於 1 號螢幕,top 為 -308:

要查詢所在螢幕左上角座標有兩個非標準化的 API screen.availTopscreen.availLeft 可用,雖然非標準 API,但 PC 環境的 Chrome/Edge 都支援;螢幕寬度及高度可由 screen.availWidth 及 screen.availHeight 取得。(另有 screen.width 及 screen.heght 為螢幕完整尺寸,但可能包含無法用到的工作列區域,用 availWidth/availHeight 比較準)

以下示範例以全螢幕開啟連結的三種寫法,分別使用 jQuery、古老的 onclick="" 事件,以及 addEventListener() 將 <a href="some.pdf" > 改為全螢幕新視窗開啟。三種做法都有加上 return false 或 e.preventDefault(),目的在取消原本點擊 <a> 的開啟連結動作,不然原網頁會被換掉。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
		<title>PDF 檢視</title>
		<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    </head>
    <body>
		<ul>
			<li><a href="test.pdf" class="fullwin">開啟 PDF</a> (jQuery)</li>
			<li><a href="test.pdf" onclick="openInFullScreenWin(this.href);return false">開啟 PDF</a>
				 (onclick)</li>
			<li><a href="test.pdf" id="pdfLink">開啟 PDF</a> (addEventListener)</li>
			
		</ul>
		<script>
			$(document).on('click', '.fullwin', function() {
				openInFullScreenWin(this.href);
				return false;
			});
			document.getElementById('pdfLink').addEventListener('click', function(e) {
				openInFullScreenWin(e.target.href);
				e.preventDefault();
			});
			function openInFullScreenWin(url) {
				window.open(url, '_blank', 'popup=yes,top=' + screen.availTop + 
					',left=' + screen.availLeft + 
					',width=' + screen.availWidth + 
					',height=' + screen.availHeight);
			}
		</script>
    </body>
</html>

前端處理完,若連結是靜態 PDF 檔,到這裡就結束了。若 PDF 內容由 ASP.NET 動態傳回,則還有一些注意事項。動態傳回 PDF 內容的 ASP.NET 程式(例如:download-pdf.aspx)通常會像下面這樣設定 ContentType 為 application/octet-stream 並加上 Content-Disposition: attachment; filename="檔案名(記得要編碼)"指定下載檔名。

<%@Page Language="C#"%>
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        Response.ContentType = "appliation/octet-stream";
        Response.AddHeader("Content-Disposition", 
            "attachment; filename=\""" + Uri.EscapeDataString("test.pdf") + "\"");
        Response.BinaryWrite(SimuReadPdfContent());
        Response.End();
    }
    byte[] SimuReadPdfContent() 
    {
        //...傳回 PDF 內容
    }
</script>

這種寫法會觸發瀏覽器執行下載動作,如想在瀏覽器直接開啟,ContentType 要改成 application/pdf,並移除 Content-Disposition: attachment。

<%@Page Language="C#"%>
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        Response.ContentType = "appliation/pdf";
        Response.BinaryWrite(SimuReadPdfContent());
        Response.End();
    }
    byte[] SimuReadPdfContent() 
    {
        //...傳回 PDF 內容
    }
</script>

這樣子 PDF 即可直接用瀏覽器檢視,但有個小問題是另存檔案時,預設檔名為 ASPX 名稱:

克服這個問題,還是要加 Content-Dispoistion 但 attachement 改成 inline

    void Page_Load(object sender, EventArgs e)
    {
        Response.ContentType = "appliation/octet-stream";
        Response.AddHeader("Content-Disposition", 
            "inline; filename=\""" + Uri.EscapeDataString("test.pdf") + "\"");
        Response.BinaryWrite(SimuReadPdfContent());
        Response.End();
    }

最終成果 - 以全螢幕瀏覽器視窗顯示 PDF 文件,並可指定另存檔名。

Tipe of window.open() a url in fullscreen size to prevent from overlayed by other windows.


Comments

# by SL

用 iframe + request full screen 不行嗎?

# by Jeffrey

to SL, 又學到新東西了,requestFullscreen 應該也是種解法,感謝分享。

# by 小黑

nice,讚嘆黑大

Post a comment