前端實現文件的斷點續傳

来源:http://www.cnblogs.com/imwtr/archive/2016/10/13/5957391.html
-Advertisement-
Play Games

早就聽說過斷點續傳這種東西,前端也可以實現一下 斷點續傳在前端的實現主要依賴著HTML5的新特性,所以一般來說在老舊瀏覽器上支持度是不高的 本文通過斷點續傳的簡單例子(前端文件提交+後端PHP文件接收),理解其大致的實現過程 還是先以圖片為例,看看最後的樣子 一、一些知識準備 斷點續傳,既然有斷,那 ...


早就聽說過斷點續傳這種東西,前端也可以實現一下

斷點續傳在前端的實現主要依賴著HTML5的新特性,所以一般來說在老舊瀏覽器上支持度是不高的

本文通過斷點續傳的簡單例子(前端文件提交+後端PHP文件接收),理解其大致的實現過程

還是先以圖片為例,看看最後的樣子

 

一、一些知識準備

斷點續傳,既然有斷,那就應該有文件分割的過程,一段一段的傳。

以前文件無法分割,但隨著HTML5新特性的引入,類似普通字元串、數組的分割,我們可以可以使用slice方法來分割文件。

所以斷點續傳的最基本實現也就是:前端通過FileList對象獲取到相應的文件,按照指定的分割方式將大文件分段,然後一段一段地傳給後端,後端再按順序一段段將文件進行拼接。

而我們需要對FileList對象進行修改再提交,在之前的文章中知曉了這種提交的一些註意點,因為FileList對象不能直接更改,所以不能直接通過表單的.submit()方法上傳提交,需要結合FormData對象生成一個新的數據,通過Ajax進行上傳操作。

 

二、實現過程

這個例子實現了文件斷點續傳的基本功能,不過手動的“暫停上傳”操作還未實現成功,可以在上傳過程中刷新頁面來模擬上傳的中斷,體驗“斷點續傳”、

有可能還有其他一些小bug,但基本邏輯大致如此。

 

1. 前端實現

首先選擇文件,列出選中的文件列表信息,然後可以自定義的做上傳操作

(1)所以先設置好頁面DOM結構

        <!-- 上傳的表單 -->
        <form method="post" id="myForm" action="/fileTest.php" enctype="multipart/form-data">
            <input type="file" id="myFile" multiple>
            <!-- 上傳的文件列表 -->
            <table id="upload-list">
                <thead>
                    <tr>
                        <th width="35%">文件名</th>
                        <th width="15%">文件類型</th>
                        <th width="15%">文件大小</th>
                        <th width="20%">上傳進度</th>
                        <th width="15%">
                            <input type="button" id="upload-all-btn" value="全部上傳">
                        </th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </form>
        <!-- 上傳文件列表中每個文件的信息模版 -->
        <script type="text/template" id="file-upload-tpl">
            <tr>
                <td>{{fileName}}</td>
                  <td>{{fileType}}</td>
                  <td>{{fileSize}}</td>
                  <td class="upload-progress">{{progress}}</td>
                  <td>
                      <input type="button" class="upload-item-btn"  data-name="{{fileName}}" data-size="{{totalSize}}" data-state="default" value="{{uploadVal}}">
                  </td>
              </tr>
        </script>

這裡一併將CSS樣式扔出來

        <style type="text/css">
            body {
                font-family: Arial;
            }
            form {
                margin: 50px auto;
                width: 600px;
            }

            input[type="button"] {
                cursor: pointer;
            }
            table {
                display: none;
                margin-top: 15px;
                border: 1px solid #ddd;
                border-collapse: collapse;
            }

            table th {
                color: #666;
            }
            table td,
            table th {
                padding: 5px;
                border: 1px solid #ddd;
                text-align: center;
                font-size: 14px;
            }
        </style>
View Code

 

(2)接下來是JS的實現解析

通過FileList對象我們能獲取到文件的一些信息

其中的size就是文件的大小,文件的分分割分片需要依賴這個

這裡的size是位元組數,所以在界面顯示文件大小時,可以這樣轉化

             // 計算文件大小
                    size = file.size > 1024
                        ? file.size / 1024  > 1024
                        ? file.size / (1024 * 1024) > 1024
                        ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
                        : (file.size / (1024 * 1024)).toFixed(2) + 'MB'
                        : (file.size / 1024).toFixed(2) + 'KB'
                        : (file.size).toFixed(2) + 'B';            

選擇文件後顯示文件的信息,在模版中替換一下數據

                    // 更新文件信息列表
                    uploadItem.push(uploadItemTpl
                        .replace(/{{fileName}}/g, file.name)
                        .replace('{{fileType}}', file.type || file.name.match(/\.\w+$/) + '文件')
                        .replace('{{fileSize}}', size)
                        .replace('{{progress}}', progress)
                        .replace('{{totalSize}}', file.size)
                        .replace('{{uploadVal}}', uploadVal)
                    );            

不過,在顯示文件信息的時候,可能這個文件之前之前已經上傳過了,為了斷點續傳,需要判斷併在界面上做出提示

通過查詢本地看是否有相應的數據(這裡的做法是當本地記錄的是已經上傳100%時,就直接是重新上傳而不是繼續上傳了)

             // 初始通過本地記錄,判斷該文件是否曾經上傳過
                    percent = window.localStorage.getItem(file.name + '_p');

                    if (percent && percent !== '100.0') {
                        progress = '已上傳 ' + percent + '%';
                        uploadVal = '繼續上傳';
                    }    

顯示了文件信息列表

點擊開始上傳,可以上傳相應的文件

上傳文件的時候需要就將文件進行分片分段

比如這裡配置的每段1024B,總共chunks段(用來判斷是否為末段),第chunk段,當前已上傳的百分比percent

需要提一下的是這個暫停上傳的操作,其實我還沒實現出來,暫停不了無奈ing...

 

接下來是分段過程

            // 設置分片的開始結尾
                    var blobFrom = chunk * eachSize, // 分段開始
                        blobTo = (chunk + 1) * eachSize > totalSize ? totalSize : (chunk + 1) * eachSize, // 分段結尾
                        percent = (100 * blobTo / totalSize).toFixed(1), // 已上傳的百分比
                        timeout = 5000, // 超時時間
                        fd = new FormData($('#myForm')[0]);

                    fd.append('theFile', findTheFile(fileName).slice(blobFrom, blobTo)); // 分好段的文件
                    fd.append('fileName', fileName); // 文件名
                    fd.append('totalSize', totalSize); // 文件總大小
                    fd.append('isLastChunk', isLastChunk); // 是否為末段
                    fd.append('isFirstUpload', times === 'first' ? 1 : 0); // 是否是第一段(第一次上傳)
            // 上傳之前查詢是否以及上傳過分片
                    chunk = window.localStorage.getItem(fileName + '_chunk') || 0;
                    chunk = parseInt(chunk, 10);

文件應該支持覆蓋上傳,所以如果文件以及上傳完了,現在再上傳,應該重置數據以支持覆蓋(不然後端就直接追加blob數據了)

            // 如果第一次上傳就為末分片,即文件已經上傳完成,則重新覆蓋上傳
                    if (times === 'first' && isLastChunk === 1) {
                        window.localStorage.setItem(fileName + '_chunk', 0);
                        chunk = 0;
                        isLastChunk = 0;
                    }

這個times其實就是個參數,因為要在上一分段傳完之後再傳下一分段,所以這裡的做法是在回調中繼續調用這個上傳操作

接下來就是真正的文件上傳操作了,用Ajax上傳,因為用到了FormData對象,所以不要忘了在$.ajax({}加上這個配置processData: false

上傳了一個分段,通過返回的結果判斷是否上傳完畢,是否繼續上傳

                    success: function(rs) {
                            rs = JSON.parse(rs);

                            // 上傳成功
                            if (rs.status === 200) {
                                // 記錄已經上傳的百分比
                                window.localStorage.setItem(fileName + '_p', percent);

                                // 已經上傳完畢
                                if (chunk === (chunks - 1)) {
                                    $progress.text(msg['done']);
                                    $this.val('已經上傳').prop('disabled', true).css('cursor', 'not-allowed');
                                    if (!$('#upload-list').find('.upload-item-btn:not(:disabled)').length) {
                                        $('#upload-all-btn').val('已經上傳').prop('disabled', true).css('cursor', 'not-allowed');
                                    }
                                } else {
                                    // 記錄已經上傳的分片
                                    window.localStorage.setItem(fileName + '_chunk', ++chunk);

                                    $progress.text(msg['in'] + percent + '%');
                                    // 這樣設置可以暫停,但點擊後動態的設置就暫停不了..
                                    // if (chunk == 10) {
                                    //     isPaused = 1;
                                    // }
                                    console.log(isPaused);
                                    if (!isPaused) {
                                        startUpload();
                                    }

                                }
                            }
                            // 上傳失敗,上傳失敗分很多種情況,具體按實際來設置
                            else if (rs.status === 500) {
                                $progress.text(msg['failed']);
                            }
                        },
                        error: function() {
                            $progress.text(msg['failed']);
                        }

繼續下一分段的上傳時,就進行了遞歸操作,按順序地上傳下一分段

截個圖..

這是完整的JS邏輯,代碼有點兒註釋了應該不難看懂吧哈哈

  1     <script type="text/javascript" src="jquery.js"></script>
  2         <script type="text/javascript">
  3             // 全部上傳操作
  4             $(document).on('click', '#upload-all-btn', function() {
  5                 // 未選擇文件
  6                 if (!$('#myFile').val()) {
  7                     $('#myFile').focus();
  8                 }
  9                 // 模擬點擊其他可上傳的文件
 10                 else {
 11                     $('#upload-list .upload-item-btn').each(function() {
 12                         $(this).click();
 13                     });
 14                 }
 15             });
 16 
 17             // 選擇文件-顯示文件信息
 18             $('#myFile').change(function(e) {
 19                 var file,
 20                     uploadItem = [],
 21                     uploadItemTpl = $('#file-upload-tpl').html(),
 22                     size,
 23                     percent,
 24                     progress = '未上傳',
 25                     uploadVal = '開始上傳';
 26 
 27                 for (var i = 0, j = this.files.length; i < j; ++i) {
 28                     file = this.files[i];
 29 
 30                     percent = undefined;
 31                     progress = '未上傳';
 32                     uploadVal = '開始上傳';
 33 
 34                     // 計算文件大小
 35                     size = file.size > 1024
 36                         ? file.size / 1024  > 1024
 37                         ? file.size / (1024 * 1024) > 1024
 38                         ? (file.size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
 39                         : (file.size / (1024 * 1024)).toFixed(2) + 'MB'
 40                         : (file.size / 1024).toFixed(2) + 'KB'
 41                         : (file.size).toFixed(2) + 'B';
 42 
 43                     // 初始通過本地記錄,判斷該文件是否曾經上傳過
 44                     percent = window.localStorage.getItem(file.name + '_p');
 45 
 46                     if (percent && percent !== '100.0') {
 47                         progress = '已上傳 ' + percent + '%';
 48                         uploadVal = '繼續上傳';
 49                     }
 50 
 51                     // 更新文件信息列表
 52                     uploadItem.push(uploadItemTpl
 53                         .replace(/{{fileName}}/g, file.name)
 54                         .replace('{{fileType}}', file.type || file.name.match(/\.\w+$/) + '文件')
 55                         .replace('{{fileSize}}', size)
 56                         .replace('{{progress}}', progress)
 57                         .replace('{{totalSize}}', file.size)
 58                         .replace('{{uploadVal}}', uploadVal)
 59                     );
 60                 }
 61 
 62                 $('#upload-list').children('tbody').html(uploadItem.join(''))
 63                     .end().show();
 64             });
 65 
 66             /**
 67              * 上傳文件時,提取相應匹配的文件項
 68              * @param  {String} fileName   需要匹配的文件名
 69              * @return {FileList}          匹配的文件項目
 70              */
 71             function findTheFile(fileName) {
 72                 var files = $('#myFile')[0].files,
 73                     theFile;
 74 
 75                 for (var i = 0, j = files.length; i < j; ++i) {
 76                     if (files[i].name === fileName) {
 77                         theFile = files[i];
 78                         break;
 79                     }
 80                 }
 81 
 82                 return theFile ? theFile : [];
 83             }
 84 
 85             // 上傳文件
 86             $(document).on('click', '.upload-item-btn', function() {
 87                 var $this = $(this),
 88                     state = $this.attr('data-state'),
 89                     msg = {
 90                         done: '上傳成功',
 91                         failed: '上傳失敗',
 92                         in: '上傳中...',
 93                         paused: '暫停中...'
 94                     },
 95                     fileName = $this.attr('data-name'),
 96                     $progress = $this.closest('tr').find('.upload-progress'),
 97                     eachSize = 1024,
 98                     totalSize = $this.attr('data-size'),
 99                     chunks = Math.ceil(totalSize / eachSize),
100                     percent,
101 	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • css參考手冊: http://www.phpstudy.net/css3/ http://www.css88.com/book/css/ ...
  • 一、JS實現瀑布流 index.html:頁面結構 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>瀑布流佈局</title> 6 <link rel="stylesheet" href= ...
  • [1]queue() [2]dequeue() [3]clearQueue() ...
  • 聖杯佈局和雙飛翼佈局主要用來解決以下問題: 1.三列佈局,兩側定寬,中間自適應。2.中間欄在瀏覽器中優先載入渲染。 解決思路: 首先把中間的div寫到前面,然後左側,然後右側。這樣就解決了第二個問題,中間的div優先渲染。 但是這樣會存在一個問題,寫到前面的也會顯示在前面。 為瞭解決這個問題,我們讓 ...
  • 構建了公司網站之後,接下來就可以考慮設計一個線上商店了。 此次的設計以上一章的設計為基礎, 只是添加了一個包含如下元素的新頁面: □ 包含商品小圖、標題和說明的產品網格; □ 位於左側的變懶,用於按類別、品牌等篩選商品; □ 方便用戶導航的麵包屑和分頁鏈接。 大家先看一看Zappos (http:/ ...
  • 對於網址欄的URL不同的操作方式有不同的載入資源、獲取數據的方式,下麵的詳細過程針對"在地址欄輸入URL,按enter(回車)鍵載入資源"此種操作方式做解析,其它的方式的過程大同小異,差異會在後面再做分析。 1. 瀏覽器開啟一個線程來處理這個請求,對URL判斷如果是http協議就按照web方式處理; ...
  • Function類型 ECMAScript中最有意思的就是函數了,有意思的根源,在於函數實際上是對象。每個函數都是Function的實例,具有屬性和方法。而重要的一點是,函數名,不過是指向函數的指針,不會與某個函數綁定。 1.函數定義 (1)創建函數有函數聲明法和函數表達式法。(2)函數名僅僅是指向 ...
  • Sublime Text是個小巧便捷的編輯器,除了眾多好用的插件外,還有它自帶的快捷鍵,打代碼事半功倍,不會用的趕緊看看吧! ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...