大多數瀏覽器都可以存取使用者的相機。
許多瀏覽器現在都能存取使用者的影片和音訊輸入內容。不過,視瀏覽器而定,這可能會是完整的動態內嵌體驗,也可能會委派給使用者裝置上的其他應用程式。此外,並非所有裝置都有相機那麼,您要如何打造使用者產生圖片的體驗,讓圖片在任何地方都能正常運作呢?
從簡單開始,逐步增加
如果想要逐步提升體驗,您必須先從任何地方都能順暢使用。最簡單的方式就是請使用者提供預錄檔案。
要求提供網址
這是最受支援但最不滿意的選項。請使用者提供網址,然後使用該網址。只要顯示圖片,這項功能在任何地方都適用。建立 img
元素、設定 src
,即可完成。
不過,如果您想以任何方式操控圖片,情況就會複雜一些。除非伺服器設定適當的標頭,且您將圖片標示為跨來源,否則 CORS 會阻止您存取實際像素。唯一可行的解決方法是執行 Proxy 伺服器。
檔案輸入
您也可以使用簡單的檔案輸入元素,包括 accept
篩選器,指出您只需要圖片檔案。
<input type="file" accept="image/*" />
這個方法適用於所有平台。在桌上型電腦上,它會提示使用者從檔案系統上傳圖片檔。在 iOS 和 Android 裝置上的 Chrome 和 Safari 中,此方法可讓使用者選擇要透過哪個應用程式擷取圖片,包括直接使用相機拍照,或選擇現有的圖片檔。
接著,可以將資料附加至 <form>
,或透過 JavaScript 操控,方法是監聽輸入元素的 onchange
事件,然後讀取事件 target
的 files
屬性。
<input type="file" accept="image/*" id="file-input" />
<script>
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', (e) =>
doSomethingWithFiles(e.target.files),
);
</script>
files
屬性是 FileList
物件,稍後我會詳細說明。
您也可以選擇在元素中加入 capture
屬性,向瀏覽器指出您希望從相機取得圖片。
<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />
新增 capture
屬性時沒有值,可讓瀏覽器決定要使用哪一個相機,而 "user"
和 "environment"
值則會指示瀏覽器分別採用前置與後置鏡頭。
capture
屬性適用於 Android 和 iOS,但會在電腦上遭到忽略。但請注意,在 Android 上,這表示使用者將無法再選擇現有的相片。系統相機應用程式將直接啟動。
拖曳
如果您已新增上傳檔案的功能,可以透過幾個簡單的方式,讓使用者體驗更豐富。
第一個方法是將放置目標新增至頁面,讓使用者從電腦或其他應用程式拖曳檔案。
<div id="target">You can drag an image file here</div>
<script>
const target = document.getElementById('target');
target.addEventListener('drop', (e) => {
e.stopPropagation();
e.preventDefault();
doSomethingWithFiles(e.dataTransfer.files);
});
target.addEventListener('dragover', (e) => {
e.stopPropagation();
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
});
</script>
與檔案輸入類似,您可以從 drop
事件的 dataTransfer.files
屬性取得 FileList
物件;
dragover
事件處理常式可讓您透過 dropEffect
屬性,向使用者傳送捨棄檔案時會有什麼影響。
拖放功能已有很長一段時間,許多主要瀏覽器都支援這種拖曳方式。
從剪貼簿貼上
最後一種取得現有圖片檔案的方式,是從剪貼簿取得。這段程式碼很簡單,但要提供正確的使用者體驗,則需要花點功夫。
<textarea id="target">Paste an image here</textarea>
<script>
const target = document.getElementById('target');
target.addEventListener('paste', (e) => {
e.preventDefault();
doSomethingWithFiles(e.clipboardData.files);
});
</script>
(e.clipboardData.files
是另一個 FileList
物件)。
剪貼簿 API 的難題在於,如果要提供完整的跨瀏覽器支援,目標元素必須同時具備可選取和可編輯的功能。<textarea>
和 <input type="text">
都符合這項要求,含有 contenteditable
屬性的元素也是如此。但這些功能顯然也設計用於編輯文字。
如果您不希望使用者能夠輸入文字,可能就很難讓這項功能順利運作。例如,當您點選其他元素時,系統會選取隱藏的輸入內容,這類技巧可能會使無障礙功能的維護更加困難。
處理 FileList 物件
由於上述方法大多都會產生 FileList
,所以我應該稍微說明一下。
FileList
與 Array
類似,它具有數字鍵和 length
屬性,但「實際上」並非陣列。沒有 forEach()
或 pop()
等陣列方法,也無法進行疊代。當然,您可以使用 Array.from(fileList)
取得實際的陣列。
FileList
的項目是 File
物件。這些物件與 Blob
物件完全相同,只是多了 name
和 lastModified
唯讀屬性。
<img id="output" />
<script>
const output = document.getElementById('output');
function doSomethingWithFiles(fileList) {
let file = null;
for (let i = 0; i < fileList.length; i++) {
if (fileList[i].type.match(/^image\//)) {
file = fileList[i];
break;
}
}
if (file !== null) {
output.src = URL.createObjectURL(file);
}
}
</script>
此範例會尋找具有圖片 MIME 類型的第一個檔案,但也可以處理同時選取/貼上/捨棄多張圖片。
取得檔案存取權後,您可以對檔案執行任何操作。舉例來說,您可以執行下列操作:
- 將其繪製為
<canvas>
元素中,以便進行操控 - 下載到使用者的裝置
- 使用
fetch()
將其上傳至伺服器
以互動方式存取攝影機
您已掌握基礎知識,現在是時候逐步改善了!
現代瀏覽器可直接存取相機,讓您建構與網頁完全整合的體驗,使用者不必離開瀏覽器。
取得相機存取權
您可以使用 WebRTC 規格中的 getUserMedia()
API,直接存取攝影機和麥克風。這會提示使用者存取已連接的麥克風和相機。
支援 getUserMedia()
雖然很好,但目前尚未支援所有地區。特別要注意的是,這項功能不適用於 Safari 10 以下版本,而這也是本文撰寫時的最新穩定版。不過,Apple 已宣布 Safari 11 將支援這項功能。
不過,偵測支援功能非常簡單。
const supported = 'mediaDevices' in navigator;
呼叫 getUserMedia()
時,您必須傳入描述所需媒體類型的物件。這些選項稱為限制條件。以下列舉幾個可能的限制,包括偏好前置或後置鏡頭、需要的音訊以及偏好的串流解析度。
不過,如要從攝影機取得資料,您只需要一個限制,也就是 video: true
。
如果成功,API 會傳回包含相機資料的 MediaStream
,您可以將其附加至 <video>
元素並播放,以顯示即時預覽畫面,或是將其附加至 <canvas>
以取得快照。
<video id="player" controls playsinline autoplay></video>
<script>
const player = document.getElementById('player');
const constraints = {
video: true,
};
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
player.srcObject = stream;
});
</script>
這項資訊本身並沒有太大用處。您只能擷取影片資料並播放。您必須額外執行一些步驟,才能取得圖片。
取得快照
如要取得圖片,最佳的支援選項是將影片的某個影格繪製到畫布上。
與 Web Audio API 不同,網路上沒有專門用於處理影片的串流 API,因此您必須使用一些駭客手法,才能從使用者的相機擷取快照。
程序如下:
- 建立會容納相機影格的影格物件
- 取得攝影機串流的存取權
- 將其附加至影片元素
- 如要擷取精確的圖格,請使用
drawImage()
將影片元素的資料新增至畫布物件。
<video id="player" controls playsinline autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
const player = document.getElementById('player');
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const captureButton = document.getElementById('capture');
const constraints = {
video: true,
};
captureButton.addEventListener('click', () => {
// Draw the video frame to the canvas.
context.drawImage(player, 0, 0, canvas.width, canvas.height);
});
// Attach the video stream to the video element and autoplay.
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
player.srcObject = stream;
});
</script>
將相機的資料儲存在畫布中後,您可以執行許多操作。您可以採取以下做法:
- 直接上傳至伺服器
- 儲存在本機
- 為圖片套用有趣的特效
提示
在不需要時停止攝影機的串流功能
建議你不再需要時就停止使用攝影機。這樣不僅能節省電池和處理能力,也能讓使用者對您的應用程式產生信心。
如要停止存取攝影機,您只需針對 getUserMedia()
傳回的串流,在每個影片音軌上呼叫 stop()
即可。
<video id="player" controls playsinline autoplay></video>
<button id="capture">Capture</button>
<canvas id="canvas" width="320" height="240"></canvas>
<script>
const player = document.getElementById('player');
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const captureButton = document.getElementById('capture');
const constraints = {
video: true,
};
captureButton.addEventListener('click', () => {
context.drawImage(player, 0, 0, canvas.width, canvas.height);
// Stop all video streams.
player.srcObject.getVideoTracks().forEach(track => track.stop());
});
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
// Attach the video stream to the video element and autoplay.
player.srcObject = stream;
});
</script>
以負責任的態度使用相機權限
如果使用者先前未授予網站相機存取權,您一呼叫 getUserMedia()
,瀏覽器就會提示使用者授予網站相機權限。
使用者不喜歡在電腦上收到存取強大裝置的提示,因此經常封鎖要求;或者,如果使用者不瞭解要求所建立提示的背景資訊,就會忽略這項要求。最佳做法是僅在首次需要時要求存取攝影機。使用者授予存取權後,系統就不會再要求授權。不過,如果使用者拒絕授予存取權,除非他們手動變更相機權限設定,否則您將無法再次取得存取權。
相容性
進一步瞭解如何導入行動裝置和電腦版瀏覽器:
我們也建議使用 adapter.js 墊片,避免應用程式受到 WebRTC 規格變更和前置字元差異的影響。