進度條

[JavaScript] 上傳圖片不難,那上傳同時預覽呢?

很多程式功能看起來是一件事,但其實它們只是被設計成剛好一起發生。

作者: 縱裕 更新日期:

此文章也有影片介紹,可以搭配影片一起學習!

01. 圖檔上傳同時預覽 (所屬課程)


以下正式開始文章


我們今天要講的功能如下(選取後預覽):



 

 

如果要上傳圖片的話,在單純使用HTML的情況。

我們會使用

<form action="/somewhere/to/upload" enctype="multipart/form-data">

<input name="progressbarTW_img" type="file" accept="image/gif, image/jpeg, image/png">

</form>

 

類似這樣的HTML tag 去上傳

主要的重點也就只有 enctype="multipart/form-data" 必須要加而已

上傳圖片或是上傳一般檔案對於電腦而言是沒有差異的

(這裡我加入了 accept="image/gif, image/jpeg, image/png" 去限制檔案類別)

 

 

但是上傳圖片同時預覽這功能,對於初學者而言好像就有點頭疼了。

因為在HTML或是CSS裡面是找不到相關的敘述。

因此這個功能必須要使用JavaScript

 

 

無論JavaScript多麼神奇,在瀏覽器上最後的顯示還是由HTML來執行,

所以其實我們要的結果就是:

當<input type="file">選取以後

讓一個本來沒有圖片顯示的<img> 顯示出圖片來。

(也可以用JavaScript DOM的方式動態的產生出<img>)

 

 

因此這裡我們要知道

1. input 選取後發出的event

2. 從event中取得檔案

3. 把檔案跟<img>做連結

 

 

這裡其實Stackoverflow上面有個非常棒的解答,應該已經造福了全世界不知道多少工程師了。

https://stackoverflow.com/questions/4459379/preview-an-image-before-it-is-uploaded

 

 

首先他在<input type="file"> 的 change event 綁上的一個函數,

這個事件會在input的value改變時被呼叫,

他綁定的方式有用到jQuery, 所以必須要在input上面放一個ID

<input type="file" id="imgInp">

並且之後要顯示圖片,所以在input後面他擺了一個<img>並且都放進<form>裡。

 

 

把一些不必要的都去掉,簡單改寫後會變成下面

<form action="/somewhere/to/upload" enctype="multipart/form-data">

   <input name="progressbarTW_img" type="file" id="imgInp" accept="image/gif, image/jpeg, image/png"/ >

   <img id="preview_progressbarTW_img" src="#" />

</form>

 

接下來就是JavaScript的部分

用jQuery綁定事件,記得script必須要放在form的後面喔(多後面都可以,或是掛在document ready上)

不然會找不到<input>

   $("#imgInp").change(function(){
      //當檔案改變後,做一些事 
     readURL(this);   // this代表
   });

 

這中間的readURL就是重點,也是我們還不知道的地方。

所以我們這邊直接貼上他的程式碼。

function readURL(input){
  if(input.files && input.files[0]){
    var reader = new FileReader();
    reader.onload = function (e) {
       $("#preview_progressbarTW_img").attr('src', e.target.result);
    }
    reader.readAsDataURL(input.files[0]);
  }
}

裡面的input 就是我們丟進去的this,也就是<input type="file">,

當<input type="file">被DOM變成Object的時候,如果他有選擇到檔案,

會被放在input.files裡面,而且是一個Array

(因為input如果寫成 <input type="file" multiple> 的時候是可以複選的)

 

 

change這個event有只代表改變,並不代表有檔案。

所以我們必須先看有沒有檔案,而且因為單選的關係,所以有檔案一定是在第0個。

因此用if( input.files && input.files[0] ){ ...  } 來做判斷。

這是一個很常見的寫法,在if - else中如果碰到NULL就會自動判斷成false

如果 A && B 中的前方的A已經是NULL的話,B並不會被執行。

以此例,如果A 為NULL 而B被執行的話程式會死掉。

因為(NULL).files[0] 這並不符合程式邏輯,會顯示NULL沒有files這個function。

另外要提的是這邊得到的並不是完整的檔案,而是檔案的路徑。

HTML必須要在<form> submit以後才會讀取檔案。

因此我們這邊必須要用JavaScript來讀取檔案

 

 

var reader = new FileReader();

 

 

這就是此預覽功能的重點,JavaScript預設提供這個Object。

用途是用來讀取檔案,標準的API

可以參考

https://developer.mozilla.org/en/docs/Web/API/FileReader

 

 

如果要FileReader去讀檔案,必須給他一個檔案Object。

它拿到檔案Object後會驅動onload事件

給它URL的方式就是用 readAsDataURL( /*裡面放檔案Object*/ )

所以我們才會放入 reader.readAsDataURL(input.files[0]);

onload事件讀出來的會是一個接近二進位檔案,所以可以直接給<img>

所以這邊直接改變img 的 src 就可以了。

而這邊讀取出來的檔案,與<form>送出去的檔案雖然是同一個檔案,

但是在記憶體裡面其實是不同的物件。

尤其是在預覽的時候,<form>根本還沒有讀取它要送出去的圖檔。

(例外:如果你是要寫成用AJAX送的話,或許可以用同一個圖檔,但不見得會有好處)

 

以下用Console.log印出結果給大家看一下。

Console.log 印出 input.files[0]:

 

Console.log 印出 reader onload 中 e.target.result (讀出來的file)

 

Console.log 印出 改變後的<Img>

 

 

所以結論就是,其實預覽與上傳是完全不同的功能,只是剛好一起發生而已。

寫程式很多時候想的要跟用程式不太一樣。

這會是初學者一開始比較需要練習的想法。

拆分功能,會是一個合格的工程師的基本能力。

但事實上這種能力不夠好卻被趕鴨子上架的工程師業界也還是滿多的。

因此如果還不夠熟練也不用氣餒,現在開始練也還來得及。

 

 

以下為完整程式碼(略微改編)

<!-- JavaScript & jQuery 版本-->

<!-- HTML part -->

<form action="/somewhere/to/upload" enctype="multipart/form-data">

   <input type="file" id="progressbarTWInput" accept="image/gif, image/jpeg, image/png"/ >

   <img id="preview_progressbarTW_img" src="#" />

</form>

<!-- JavaScript part -->

<script>

$("#progressbarTWInput").change(function(){

  readURL(this);

});

 

function readURL(input){

  if(input.files && input.files[0]){

    var reader = new FileReader();

    reader.onload = function (e) {

       $("#preview_progressbarTW_img").attr('src', e.target.result);

    }

    reader.readAsDataURL(input.files[0]);

  }

}

</script>

 

 

這邊也簡單改一個純JavaScript的版本,給大家參考。

<!-- 純 JavaScript 版本-->

<!-- HTML part -->

<form action="/somewhere/to/upload" enctype="multipart/form-data">

   <input type="file" onchange="readURL(this)" targetID="preview_progressbarTW_img" accept="image/gif, image/jpeg, image/png"/ >

   <img id="preview_progressbarTW_img" src="#" />

</form>

<!-- JavaScript part -->

<script>

function readURL(input){

  if(input.files && input.files[0]){

    var imageTagID = input.getAttribute("targetID");

    var reader = new FileReader();

    reader.onload = function (e) {

       var img = document.getElementById(imageTagID);

       img.setAttribute("src", e.target.result)

    }

    reader.readAsDataURL(input.files[0]);

  }

}

</script>

 

 

如果對於跟後端連接上傳有興趣的話,可以參考我們Ruby On Rails的課程內容(也就是開頭影片)。如果您用的是不同的程式語言,就需要找出該程式語言或框架相對應的程式碼。

無論是何種程式語言都必須與瀏覽器溝通,這部分並不會不一樣。

 

 

不過有時候你可能想一次上傳預覽多張圖檔,這時候你可以參考下面這篇文章喔!

[JavaScript] 上傳圖片同時預覽一張不夠,那你有試過兩張嗎?

 

 


最後,如果你喜歡我們的文章,別忘了到我們的FB粉絲團按讚喔!!

Medium picture

縱裕

錄課程錄到快死掉了啊!!!