Sebagian besar browser dapat mengakses kamera pengguna.
Banyak browser kini memiliki kemampuan untuk mengakses input video dan audio dari pengguna. Namun, bergantung pada browser, pengalaman ini mungkin berupa pengalaman inline dan dinamis penuh, atau dapat didelegasikan ke aplikasi lain di perangkat pengguna. Selain itu, tidak semua perangkat memiliki kamera. Jadi, bagaimana Anda dapat membuat pengalaman yang menggunakan gambar buatan pengguna yang berfungsi dengan baik di mana saja?
Mulai dengan sederhana dan bertahap
Jika ingin meningkatkan pengalaman secara bertahap, Anda harus memulai dengan sesuatu yang berfungsi di mana saja. Cara termudah untuk melakukannya adalah dengan meminta file yang telah direkam sebelumnya kepada pengguna.
Meminta URL
Ini adalah opsi yang paling didukung, tetapi paling tidak memuaskan. Minta pengguna untuk memberikan URL kepada Anda, lalu
gunakan URL tersebut. Untuk hanya menampilkan gambar, ini berfungsi di mana saja. Buat elemen img
, tetapkan
src
, dan Anda selesai.
Namun, jika Anda ingin memanipulasi gambar dengan cara apa pun, prosesnya akan sedikit lebih rumit. CORS mencegah Anda mengakses piksel sebenarnya, kecuali jika server menetapkan header yang sesuai dan Anda menandai gambar sebagai crossorigin; satu-satunya cara praktis untuk mengatasinya adalah dengan menjalankan server proxy.
Input file
Anda juga dapat menggunakan elemen input file sederhana, termasuk filter accept
yang menunjukkan bahwa Anda hanya
ingin file gambar.
<input type="file" accept="image/*" />
Metode ini berfungsi di semua platform. Di desktop, pengguna akan diminta untuk mengupload file gambar dari sistem file. Di Chrome dan Safari di iOS dan Android, metode ini akan memberi pengguna pilihan aplikasi yang akan digunakan untuk mengambil gambar, termasuk opsi mengambil foto langsung dengan kamera atau memilih file gambar yang ada.
Data kemudian dapat dilampirkan ke <form>
atau dimanipulasi dengan JavaScript dengan memproses peristiwa onchange
pada elemen input, lalu membaca properti files
dari peristiwa target
.
<input type="file" accept="image/*" id="file-input" />
<script>
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', (e) =>
doSomethingWithFiles(e.target.files),
);
</script>
Properti files
adalah objek FileList
, yang akan saya bahas lebih lanjut nanti.
Secara opsional, Anda juga dapat menambahkan atribut capture
ke elemen, yang menunjukkan kepada browser bahwa Anda lebih memilih untuk mendapatkan gambar dari kamera.
<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />
Menambahkan atribut capture
tanpa nilai memungkinkan browser menentukan kamera mana yang akan digunakan, sedangkan
nilai "user"
dan "environment"
memberi tahu browser untuk memilih kamera depan dan belakang,
masing-masing.
Atribut capture
berfungsi di Android dan iOS, tetapi diabaikan di desktop. Namun, perlu diperhatikan bahwa
di Android, hal ini berarti pengguna tidak akan lagi memiliki opsi untuk memilih gambar
yang ada. Sebagai gantinya, aplikasi kamera sistem akan dimulai secara langsung.
Tarik lalu lepas
Jika Anda sudah menambahkan kemampuan untuk mengupload file, ada beberapa cara mudah untuk membuat pengalaman pengguna sedikit lebih kaya.
Yang pertama adalah menambahkan target drop ke halaman Anda yang memungkinkan pengguna menarik file dari desktop atau aplikasi lain.
<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>
Serupa dengan input file, Anda bisa mendapatkan objek FileList
dari properti dataTransfer.files
dari peristiwa drop
;
Pengendali peristiwa dragover
memungkinkan Anda memberi tahu pengguna apa yang akan terjadi saat mereka melepas file
dengan menggunakan
properti dropEffect
.
Tarik lalu lepas sudah ada sejak lama dan didukung dengan baik oleh browser utama.
Menempelkan dari papan klip
Cara terakhir untuk mendapatkan file gambar yang ada adalah dari papan klip. Kode untuk ini sangat sederhana, tetapi pengalaman pengguna sedikit lebih sulit untuk dilakukan dengan benar.
<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
adalah objek FileList
lainnya.)
Bagian yang sulit dengan API papan klip adalah, untuk dukungan lintas browser penuh, elemen target
harus dapat dipilih dan diedit. <textarea>
dan <input type="text">
cocok
di sini, begitu juga elemen dengan atribut contenteditable
. Namun, alat ini juga jelas dirancang untuk
mengedit teks.
Mungkin sulit untuk membuat ini berfungsi dengan lancar jika Anda tidak ingin pengguna dapat memasukkan teks. Trik seperti memiliki input tersembunyi yang dipilih saat Anda mengklik beberapa elemen lain dapat mempersulit pemeliharaan aksesibilitas.
Menangani objek FileList
Karena sebagian besar metode di atas menghasilkan FileList
, saya harus menjelaskan sedikit tentang FileList
.
FileList
mirip dengan Array
. Array ini memiliki kunci numerik dan properti length
, tetapi sebenarnya bukan array. Tidak ada metode array, seperti forEach()
atau pop()
, dan tidak dapat di-iterasi.
Tentu saja, Anda bisa mendapatkan Array yang sebenarnya menggunakan Array.from(fileList)
.
Entri FileList
adalah objek File
. Objek ini sama persis dengan objek Blob
,
kecuali bahwa objek ini memiliki properti hanya baca name
dan lastModified
tambahan.
<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>
Contoh ini menemukan file pertama yang memiliki jenis MIME gambar, tetapi juga dapat menangani beberapa gambar yang dipilih/disalin/di-drop sekaligus.
Setelah memiliki akses ke file, Anda dapat melakukan apa pun yang Anda inginkan dengan file tersebut. Misalnya, Anda dapat:
- Gambarkan ke dalam elemen
<canvas>
sehingga Anda dapat memanipulasinya - Mendownloadnya ke perangkat pengguna
- Upload ke server dengan
fetch()
Mengakses kamera secara interaktif
Setelah Anda memahami dasar-dasarnya, saatnya untuk meningkatkan kualitas secara bertahap.
Browser modern dapat mendapatkan akses langsung ke kamera, sehingga Anda dapat membuat pengalaman yang terintegrasi sepenuhnya dengan halaman web, sehingga pengguna tidak perlu keluar dari browser.
Mendapatkan akses ke kamera
Anda dapat langsung mengakses kamera dan mikrofon menggunakan API dalam spesifikasi
WebRTC yang disebut getUserMedia()
. Tindakan ini akan meminta
akses ke mikrofon dan kamera yang terhubung.
Dukungan untuk getUserMedia()
cukup baik, tetapi belum tersedia di mana-mana. Secara khusus, fitur ini tidak
tersedia di Safari 10 atau yang lebih lama, yang pada saat panduan ini ditulis masih merupakan versi stabil terbaru.
Namun, Apple telah mengumumkan
bahwa fitur ini akan tersedia di Safari 11.
Namun, dukungan sangat mudah dideteksi.
const supported = 'mediaDevices' in navigator;
Saat memanggil getUserMedia()
, Anda harus meneruskan objek yang menjelaskan jenis media yang
Anda inginkan. Pilihan ini disebut batasan. Ada beberapa kemungkinan batasan, yang mencakup
hal-hal seperti apakah Anda lebih memilih kamera depan atau belakang, apakah Anda menginginkan audio, dan
resolusi yang Anda inginkan untuk streaming.
Namun, untuk mendapatkan data dari kamera, Anda hanya memerlukan satu batasan, yaitu video: true
.
Jika berhasil, API akan menampilkan MediaStream
yang berisi data dari
kamera, lalu Anda dapat melampirkan MediaStream
ke elemen <video>
dan memutarnya
untuk menampilkan pratinjau real time, atau melampirkan MediaStream
ke <canvas>
untuk mendapatkan
snapshot.
<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>
Hal ini tidak terlalu berguna. Yang dapat Anda lakukan hanyalah mengambil data video dan memainkannya. Jika ingin mendapatkan gambar, Anda harus melakukan sedikit pekerjaan tambahan.
Mengambil snapshot
Opsi terbaik yang didukung untuk mendapatkan gambar adalah menggambar frame dari video ke kanvas.
Tidak seperti Web Audio API, tidak ada API pemrosesan streaming khusus untuk video di web sehingga Anda harus menggunakan sedikit peretasan untuk mengambil snapshot dari kamera pengguna.
Prosesnya adalah sebagai berikut:
- Membuat objek kanvas yang akan menyimpan frame dari kamera
- Mendapatkan akses ke streaming kamera
- Melampirkannya ke elemen video
- Jika Anda ingin mengambil frame yang akurat, tambahkan data dari elemen video
ke objek kanvas menggunakan
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>
Setelah memiliki data dari kamera yang disimpan di kanvas, Anda dapat melakukan banyak hal dengannya. Anda dapat:
- Menguploadnya langsung ke server
- Menyimpan secara lokal
- Menerapkan efek funky ke gambar
Tips
Menghentikan streaming dari kamera jika tidak diperlukan
Sebaiknya berhenti menggunakan kamera jika Anda tidak lagi membutuhkannya. Hal ini tidak hanya akan menghemat baterai dan daya pemrosesan, tetapi juga akan memberikan kepercayaan kepada pengguna terhadap aplikasi Anda.
Untuk menghentikan akses ke kamera, Anda cukup memanggil stop()
di setiap trek video
untuk streaming yang ditampilkan oleh getUserMedia()
.
<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>
Meminta izin untuk menggunakan kamera secara bertanggung jawab
Jika pengguna belum pernah memberikan akses situs Anda ke kamera, browser akan meminta pengguna untuk memberikan izin situs Anda ke kamera saat Anda memanggil getUserMedia()
.
Pengguna tidak suka dimintai akses ke perangkat canggih di komputer mereka dan mereka akan sering memblokir permintaan tersebut, atau mereka akan mengabaikannya jika tidak memahami konteks pembuatan perintah. Sebaiknya hanya minta akses ke kamera saat pertama kali diperlukan. Setelah pengguna memberikan akses, mereka tidak akan ditanya lagi. Namun, jika pengguna menolak akses, Anda tidak dapat mendapatkan akses lagi, kecuali jika mereka mengubah setelan izin kamera secara manual.
Kompatibilitas
Informasi selengkapnya tentang penerapan browser seluler dan desktop:
Sebaiknya gunakan shim adapter.js untuk melindungi aplikasi dari perubahan spesifikasi WebRTC dan perbedaan awalan.