HTML5練習-從桌面拖拉檔案到網頁

這年頭,網頁如果不支援從桌面或檔案總管直接拖拉檔案,想自稱HTML5都心虛,只能稱作HTML4.5(誤),老闆客戶還會不時打你臉: "Gmail、OneDrive(SkyDrive)、DropBox幾百年前就有了! 為什麼你到現在還做不出來?" (網頁攻城獅內心的OS: 你有給我Google或Microsoft等級的薪水嗎?) 搞網頁好辛苦,客戶老闆上網胡亂逛,不小心看到酷炫網站,馬上把規格放進專案,還說什麼瀏覽器打開HTML、CSS、JavaScript都能看,快點抄一抄給我做出來... 想到這我都鼻酸了,Web Developer們應該團結起來,技術衝那麼快是要逼死誰,我數一二三,大家一起放手吧... orz


"大家不要這麼辛苦,好不好?" "好,大家都不要這麼辛苦。" "一、二、三!"

話說回來,既然走了技術這行就得認命,還是乖乖學習怎麼實現檔案拖拉吧!

先看執行效果。目標如下,網頁左上角有個圖檔拖放區,由檔案總管選取多個檔案,拖拉至拖放區放下,網頁接收選取的檔案清單,透過HTML5 File API讀取圖檔轉為Data URI作為<img> src,在頁面呈現圖示清單,點選圖示時可檢視原圖。

【原理解析】

只簡單說明原理,細節請直接看程式碼及註解:

  1. 拖放操作: 在圖檔拖放區元素加掛ondragover、ondragleave、ondrop事件,滑鼠移拉物件進入離開時切換底色,放下物件時透過transferData.files取得檔案資訊。為避免拖拉操作觸發原有瀏覽器行為,記得要呼叫event.stopPropagation()及event.preventDefault()。
  2. 檔案資訊: event.transferData.files裡的檔案資訊有name, type, size等資訊,基於安全不包含檔案路徑等資訊,但可當成File API參數讀出內容。
  3. 讀取檔案: 透過HTML5 File API FileReader(),將圖檔讀入轉成Data URI,可直接做為<img> src顯示圖檔。【延伸閱讀: [HTML5] HTML5 File API
  4. 清單及檢視: 採用Knockout MVVM實作清單、檢視介面。

程式範例如下: Live Demo

<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>HTML5 拖拉圖檔顯示在網頁上</title>
    <style>
        .drop-zone {
            position: absolute; top: 6px; width: 120px; height: 90px; 
            background-color: green; color: white; text-align: center;
        }
        .drop-zone.hover {
            background-color: blue;
        }
        .img-list {
            position: absolute; height: 90px; background-color: #444;
            top: 6px; left: 135px; right: 6px; overflow-y: hidden;
            overflow-x: auto; white-space: nowrap;
        }
        .thumbnail {
            max-width: 100px; max-width: 75px; vertical-align: top;
            margin: 3px; cursor: pointer;
            border: 1px solid transparent;
        }
        .thumbnail:hover {
            border: 1px solid red;
        }
        .display {
            position: absolute; top: 110px; 
            left: 6px; right: 6px; bottom: 6px;
            padding: 12px;
        }
    </style>
</head>
<body>
    <div class="drop-zone"><span>圖檔拖放區</span></div>
    <div data-bind ="foreach: images" class="img-list">
        <img data-bind="attr: { src: dataUri }, click: $root.currImage" class="thumbnail" />
    </div>
    <fieldset class="display" data-bind="with: currImage">
        <legend>
            <span data-bind="text: name"></span>
            <span data-bind="text: size"></span>
        </legend>
        <div>
            <img data-bind="attr: { src: dataUri }" />
        </div>
    </fieldset>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js "></script>
    <script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.0.0.js "></script>
    <script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.core.min.js"></script>
<script>
        $(function () {
            var $drop = $(".drop-zone");
            //抑制瀏覽器原有的拖拉操作效果
            function stopEvent(evt) {
                evt.stopPropagation();
                evt.preventDefault();
            }
            $drop.bind("dragover", function (e) {
                //滑鼠經過上方時加入特效
                stopEvent(e);
                $(e.target).addClass("hover");
            }).bind("dragleave", function (e) {
                //滑鼠移開時移除特效
                stopEvent(e);
                $(e.target).removeClass("hover");
            }).bind("drop", function (e) {
                //拖放操作完成事件
                stopEvent(e);
                $(e.target).removeClass("hover");
                //由dataTransfer.files取得檔案資訊
                var files = e.originalEvent.dataTransfer.files;
                var imageFiles = $.map(files, function (f, i) {
                    //只留下type為image/*者,例如: image/gif, image/jpeg, image/png...
                    return f.type.indexOf("image") == 0 ? f : null;
                });
                //清除ViewModel
                vm.images.removeAll(); vm.currImage(null);
                //逐一讀入各圖檔,取得DataURI,顯示在網頁上
                $.each(imageFiles, function (i, file) {
                    //使用File API讀取圖檔內容轉為DataUri
                    var reader = new FileReader();
                    reader.onload = function (e) {
                        //將檔名、檔案大小、DataURI放入ViewModel
                        vm.images.push({
                            name: file.name,
                            size: kendo.format("{0:n0} bytes", file.size),
                            dataUri: e.target.result
                        })
                    }
                    reader.readAsDataURL(file);
                });
            });
 
            function myViewModel() {
                var self = this;
                self.images = ko.observableArray();
                self.currImage = ko.observable();
            }
            var vm = new myViewModel();
            ko.applyBindings(vm);
        });
    </script>
</body>
</html>

【注意】IE要IE10以上才開始支援File API,IE9哭哭!

【參考資料】

  1. HTML5 File Drag & Drop API
  2. HTML5 drag and drop asynchronous multi file upload with ASP.NET WebAPI
  3. [HTML5] HTML5 File API by 小朱
歡迎推文分享:
Published 08 March 2014 12:13 PM 由 Jeffrey
Filed under: , ,
Views: 24,132



意見

# 佑翔 said on 09 March, 2014 08:43 PM

chrome還支援拖曳資料夾哦!!

也支援把檔案拖出來("DownloadURL")

# WWE said on 12 March, 2014 10:57 PM

黑大您好,謝謝您的教學

原理解析中的第三點

3. 讀取檔案 的延伸閱讀:沒有資料@@

# Jeffrey said on 13 March, 2014 12:55 AM

to WWE, 謝謝提醒。編輯時出錯,已修正。

# axer said on 23 November, 2014 12:23 PM

感謝你無私的分享

老闆客戶還會不時打你臉: Gmail、OneDrive、DropBox幾百年前就有了! 為什麼你到現在還做不出來。

非常有同感耶。

還記得有人和我說,他以前也想做google map,只差沒資金,結果google偷了他的創意

# 胡忠晞 said on 18 January, 2015 10:27 PM

var imageFiles = $.map(files, function (f, i) {

這裡用 grep 比較直覺吧! 我被拐到了!

var imageFiles = $.grep(files, function(file) {

    return ~file.type.indexOf("image");

});

# Jeffrey said on 19 January, 2015 03:28 AM

to Jax, 嘿,用$.grep()的確簡潔多了,多年來我一直忘了它的存在(羞),謝謝提醒,已筆記。

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<March 2014>
SunMonTueWedThuFriSat
2324252627281
2345678
9101112131415
16171819202122
23242526272829
303112345
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication