National Taiwan University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

台灣大學 AQI 62 24°C PM2.5 11
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 21

檔案上傳與預覽

input type=file、FileReader 與上傳前預覽
Interaction實作 15 minute(s)

Learning Objectives

  • 用 input type="file" 讓使用者選擇本機檔案,並用 multiple、accept 設定數量與可接受的檔案類型
  • 從 FileList 與 File 物件取得檔名、大小與類型,並說明表單上傳檔案需要 enctype="multipart/form-data"
  • 用 FileReader 或 URL.createObjectURL 在上傳前預覽圖片
  • 用 label 搭配隱藏的檔案欄位,做出自訂樣式的上傳按鈕

選擇本機檔案

交作業、換大頭貼、匯入成績表,都要讓使用者從自己的電腦選檔案。HTML 用 <input type="file">:

<input type="file" id="upload">                          <!-- 一次選一個 -->
<input type="file" id="photos" multiple>                 <!-- 一次可以選好幾個 -->
<input type="file" accept="image/png,image/gif">         <!-- 只接受 png 與 gif -->
<input type="file" accept=".pdf">                        <!-- 只接受 PDF -->
<input type="file" accept=".csv,.xls">                   <!-- 多種副檔名用逗號分隔 -->
<input type="file" accept="image/*">                     <!-- 所有圖片(jpg、png、gif…) -->

accept 可以寫副檔名(.pdf),也可以寫網際網路媒體類型(MIME type,例如 image/png、image/*)。

accept 只是提示:檔案選擇視窗會先篩出符合的檔案,但使用者仍然可以切換成「所有檔案」選別的。 所以 JavaScript 還是要檢查檔案類型;檔案真的送到伺服器時,伺服器也一定要再檢查一次。

這一課的程式只在你的瀏覽器裡讀取檔案,不會上傳到任何伺服器。 你在練習裡選的檔案不會離開你的電腦。

FileList 與 File 物件

使用者選好檔案時,檔案欄位會發生 change 事件。input.files 是一個 FileList(類陣列),裡面每一項是一個 File 物件:

input.addEventListener('change', () => {
  console.log(input.files.length);       // 選了幾個
  const file = input.files[0];           // 第一個檔案;沒選時是 undefined
  console.log(file.name, file.size, file.type);
});
File 的屬性 意思
name 檔名(不含資料夾路徑:瀏覽器不讓網頁知道檔案放在你電腦的哪裡)
size 檔案大小,單位是位元組(byte)
type MIME 類型,例如 'image/png';瀏覽器判斷不出來時是空字串
lastModified 最後修改時間(毫秒數);投影片的 lastModifiedDate 是舊的寫法

FileList 和上一課的 NodeList 一樣不是陣列,可以用 for...of 逐一處理,或用 Array.from(input.files) 轉成陣列。

用表單上傳檔案:傳統的表單送出要上傳檔案時,<form> 必須設定 method="post" 與 enctype="multipart/form-data"; 預設的編碼方式(application/x-www-form-urlencoded)只會送出檔名,不會送出檔案內容。

讀出檔案的資訊

執行動畫裡沒有網頁可以選檔案,所以用 new File([內容], 檔名, { type }) 自己做三個 File 物件,它們和 input.files 裡的檔案是同一種物件。注意最後一行:size 和字串的 length 不一樣。

互動練習:讀取選到的檔案

按「執行」後,在預覽裡按檔案欄位,選一個或幾個你電腦裡的檔案(只會在這個框裡讀取,不會上傳)。依任務修改 JavaScript;最後一個任務要改 HTML 分頁。

想一想:選擇檔案

1.

<input type="file" accept="image/*"> 的意思是什麼?

2.

使用者選了 D:\作業\report.pdf,input.files[0].name 是什麼?

3.

用傳統的 <form> 送出檔案時,<form> 的 enctype 要設成什麼?

第 1 次作答

上傳前預覽圖片

使用者選好照片後,先在頁面上顯示出來讓他確認,是很常見的設計。流程是:

  1. 監聽檔案欄位的 change 事件
  2. 從 input.files[0] 取得 File 物件,檢查 type 是不是圖片
  3. 讀取檔案,得到一個 <img> 可以使用的網址
  4. 把網址指定給 <img> 的 src

第 3 步有兩種做法:

// 做法一:FileReader 把檔案讀成 data: 開頭的網址(檔案內容直接寫在網址裡)
const reader = new FileReader();
reader.addEventListener('load', () => {
  preview.src = reader.result;          // 'data:image/png;base64,iVBORw0…'
});
reader.readAsDataURL(file);             // 讀檔是非同步的:讀完才發生 load 事件

// 做法二:URL.createObjectURL 產生一個指向這個檔案的 blob: 網址,不必讀完整個檔案
preview.src = URL.createObjectURL(file);
// 不再需要時(例如換了一張圖),用 URL.revokeObjectURL(網址) 釋放

FileReader 還有 readAsText(file)(讀成文字,例如讀 CSV)。它是非同步的:呼叫 readAs… 之後, 要等 load 事件發生才能讀 reader.result,在那之前 result 是 null。 現在也可以用 await file.text() 直接讀出文字(後面學到 async/await 時會用到)。

互動練習:圖片預覽

寫一個 change 處理器,依任務檢查檔案、顯示資訊並預覽圖片。改完按「執行」,再到預覽裡選一張你電腦裡的圖片(只在這個框裡顯示,不會上傳)。

自訂上傳按鈕

瀏覽器內建的檔案按鈕(「選擇檔案/未選擇任何檔案」)幾乎改不了樣式,連 cursor: pointer 都不一定有效。投影片介紹了兩種做法:

偽裝法(舊做法):把檔案欄位放進一個做成按鈕樣子的父元素裡,再把檔案欄位的透明度設成 0、蓋在父元素上。 缺點是有些瀏覽器不能調整檔案欄位的大小,父元素太大時會點不到透明的欄位。

label 法(建議):點 <label for="f"> 等於點 id="f" 的欄位。把檔案欄位藏起來,改把 <label> 做成你要的按鈕樣式, 裡面要放文字或圖片都可以:

<input type="file" id="f" class="visually-hidden" accept="image/*">
<label for="f" class="upload-btn">選擇照片</label>

藏起檔案欄位時,不要用 display: none 或 visibility: hidden:那樣鍵盤使用者就沒辦法用 Tab 移到這個欄位、按空白鍵開啟選擇視窗。 改用「視覺上隱藏」(visually hidden)的寫法,把元素縮成 1 像素並裁掉,畫面上看不到、但鍵盤與螢幕閱讀器仍然找得到:

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.visually-hidden:focus + .upload-btn { outline: 3px solid #00b297; }   /* 鍵盤移到欄位時,在按鈕上顯示外框 */

另一種做法是放一個普通的 <button>,在它的 click 處理器裡呼叫 fileInput.click() 開啟選擇視窗。

互動練習:用 label 自訂上傳按鈕

「選擇照片」是一個 <label>,按它就會開啟檔案選擇視窗。現在原本的檔案欄位還看得到,請在 CSS 分頁寫出 .visually-hidden 的規則把它藏起來,再完成 JavaScript 的任務。

想一想:預覽與自訂按鈕

1.

下面的程式會印出什麼?

const r = new FileReader();
r.readAsText(new File(['hi'], 'a.txt'));
console.log(r.result);
2.

關於 URL.createObjectURL(file),下列哪一個說法正確?

3.

用 <label for="f"> 自訂上傳按鈕時,為什麼不建議用 display: none 隱藏檔案欄位?

4.

在這一課的練習裡選了一張照片並顯示預覽,照片會被送到 Uedu 的伺服器嗎?

第 1 次作答

實作:檢查副檔名

accept 只是提示,程式還要自己檢查。完成 isAccepted(name, accept):accept 是用逗號分隔的副檔名(例如 .png,.jpg),檔名的副檔名(不分大小寫)在清單裡就回傳 true。

你的輸出
預期輸出

重點精華回顧

  • <input type="file"> 讓使用者選擇本機檔案;multiple 可以一次選多個,accept 用副檔名或 MIME 類型(image/*)限制選擇視窗顯示的檔案。
  • accept 只是提示,程式與伺服器都要再檢查檔案類型。
  • input.files 是 FileList,每一項是 File 物件:name(不含路徑)、size(位元組)、type(MIME 類型)。
  • 傳統表單上傳檔案要設定 method="post" 與 enctype="multipart/form-data"。
  • 上傳前預覽:FileReader 的 readAsDataURL 讀成 data: 網址(非同步,讀完才發生 load),或用 URL.createObjectURL 產生 blob: 網址,用完以 revokeObjectURL 釋放。
  • 自訂上傳按鈕:用 <label for> 做成按鈕,檔案欄位以 visually hidden 的方式隱藏,不要用 display: none。
  • 選擇與預覽檔案都只在瀏覽器裡進行,檔案不會因此被上傳。
這一課完成了!
Next lesson:正規表示法與表單驗證
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。