在很多網站看過一種方便設計 - 從別處選取圖片複製到剪貼簿,在網頁空白處按 Ctrl-V,便會將圖片上傳到網站。

總想著,改天我要為自己的網站也加上這個酷功能,就今天吧!

在 IE 退役及Edge 改用 Chromium 引擎後,瀏覽器回到大一統的和平局面(令人想起當年 IE 市佔 95% 的時光),現在寫桌機用網頁輕鬆許多,很少需要煩惱跨瀏覽器問題。加上 HTML5 標準成熟與普及,要從剪貼簿拿到圖片內容遠比想像簡單,用內建 API 短短幾行便能搞定。(最重要的是現在還多了 Github Copilot 魔法)

<!DOCTYPE html>
<html>
<head>
    <title>從剪貼簿貼上圖片</title>
</head>
<body>
    <p>按 Ctrl-V 貼上複製的圖片</p>
    <div id="panel"></div>
    <script>
        document.addEventListener('paste', function (e) {
            const items = e.clipboardData && e.clipboardData.items;
            if (!items) return;
            for (let i = 0; i < items.length; i++) {
                if (items[i].type.indexOf('image') !== -1) {
                    const reader = new FileReader();
                    reader.onload = function (e) {
                        var image = new Image();
                        image.src = e.target.result;
                        document.getElementById('panel').appendChild(image);
                    };
                    reader.readAsDataURL(items[i].getAsFile());
                }
            }
        });
    </script>
</body>
</html>

程式原理很簡單:傾聽 paste 事件,逐一檢查 e.clipboardData.items 的資料選項有沒有圖檔類( Content Type 為 "image/*"),若有就用 FileReader.readAsDataURL() 讀入 DataTransferItem.getAsFile(),轉為 Data URI 格式在網頁插入圖片元素,輕鬆秒殺。

下一步是將其轉為上傳檔案。

基於安全 <input type="file"> 挑選檔案向來限定人工操作,禁止 JavaScript 染指,因此過去我都是採行繞道作法,例如:用 <input type="hidden"> 傳送 Data URI 編碼、改用 AJAX 方式上傳... 等。不過,隨者 HTML 標準成熟普及,現在多了 File API 可用,允許 JavaScript 透過 <input type="file"> .files 屬性 (FileList 型別)讀取或設定上傳內容。有個眉角是 FileList 內含的檔案項目不可修改,需透過 DataTransfer 物件建立整個 FileList 指定給 <input type="file">.files,檔名則由依當下日期時間指定動態產生。

寫了一個簡單 ASPX 驗證:

<%@ Page Language="C#" %>
    <script runat="server">
    protected void Page_Load(object sender, EventArgs e)
        {
            if (Request.Files.Count > 0) {
            HttpPostedFile file = Request.Files[0];
                var fileData = new byte[file.ContentLength];
                file.InputStream.Read(fileData, 0, file.ContentLength);
                Response.ContentType = "text/html";
                Response.Write("<body>");
                Response.Write("<p>" + file.FileName + " (" + file.ContentLength + " bytes) uploaded</p>");
                Response.Write("<img src='data:" + file.ContentType + ";base64," + Convert.ToBase64String(fileData) + "' />");
                Response.Write("</body>");
                Response.End();
            }
        }
    </script>
    <!DOCTYPE html>
    <html>

    <head>
        <title>貼圖檔上傳</title>
        <style>
            html, body { height: 100%; padding: 0; margin: 0; }
            .container {
                display: flex; flex-direction: column; 
                height: 100%; padding: 12px; box-sizing: border-box;
            }
            form { height: 40px; }
            iframe {
                flex-grow: 1; width: 100%;
            }
        </style>
    </head>

    <body>
        <div class="container">
            <form method="post" target="result" enctype="multipart/form-data">
                <input type="file" name="file" id="uploadFiles" />
                <input type="submit" value="上傳" />
            </form>
            <iframe name="result"></iframe>
        </div>
        <script>
            document.addEventListener('paste', function (e) {
                const items = e.clipboardData.items;
                for (let i = 0; i < items.length; i++) {
                    if (items[i].type.indexOf('image') !== -1) {
                        let file = items[i].getAsFile();
                        const fileExt = file.name.split('.').pop();
                        file = new File([file],
                            `Img${new Date().toISOString().substring(0, 19).replace(/[-T:]/g, '')}.${fileExt}`,
                            { type: file.type });
                        const dataTransfer = new DataTransfer();
                        dataTransfer.items.add(file);
                        document.getElementById('uploadFiles').files = dataTransfer.files;
                        break;
                    }
                }
            });
        </script>

    </html>

就醬,從今天起,我寫的網站也能貼圖片上傳功能囉~ (灑花)

註:用 Edge/Chrome/Firefox 測過都沒問題,以上做法應可安心用在供桌機瀏覽的企業內部網站。

補充一個小技巧:若想保留 jpg/gif 圖檔格式,不想一律轉成 png,則不要用軟體或瀏覽器開啟圖檔再複製影像,請選取檔案本體複製、貼上,這樣可上傳原格式檔案,示範如下:

This article explains how to implement clipboard pasting of images on a webpage for uploading to the website.


Comments

# by Ike

for 語法的索引值如果沒有其他用途,推薦使用 for...of

Post a comment