Sebagian besar browser dapat mengakses kamera pengguna.
Banyak browser kini mampu mengakses input video dan audio dari pengguna. Namun, bergantung pada browser, hal ini bisa menjadi pengalaman inline dan dinamis penuh, atau bisa didelegasikan ke aplikasi lain pada 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?
Mulailah dengan sederhana dan progresif
Jika ingin meningkatkan pengalaman secara bertahap, Anda harus memulai dengan sesuatu yang berfungsi di mana saja. Hal termudah untuk dilakukan adalah cukup meminta file yang sudah direkam kepada pengguna.
Meminta URL
Ini adalah opsi terbaik yang 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 yang sebenarnya, kecuali jika server menetapkan header yang sesuai dan Anda menandai gambar sebagai crossorigin; satu-satunya cara praktis untuk melakukannya 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 mengunggah file gambar dari sistem file. Di Chrome dan Safari pada iOS dan Android, metode ini akan memberi pengguna pilihan aplikasi yang akan digunakan untuk mengambil gambar, termasuk opsi mengambil foto secara langsung dengan kamera atau memilih file gambar yang sudah ada.
Data tersebut 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 tersebut, yang menunjukkan ke browser
bahwa Anda lebih suka menerima 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 yang akan digunakan, sedangkan
nilai "user"
dan "environment"
memberi tahu browser untuk masing-masing memilih kamera depan
dan belakang.
Atribut capture
berfungsi di Android dan iOS, tetapi diabaikan di desktop. Namun, perhatikan bahwa di Android, hal ini berarti pengguna tidak lagi memiliki opsi untuk memilih gambar yang ada. Sebagai gantinya, aplikasi kamera sistem akan langsung dimulai.
Tarik lalu lepas
Jika Anda sudah menambahkan kemampuan untuk mengupload file, ada beberapa cara mudah yang dapat dilakukan 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
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">
sesuai dengan
persyaratan 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 apa itu FileList
.
FileList
mirip dengan Array
. Parameter 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 Anda memiliki akses ke file tersebut, Anda bisa melakukan apa saja yang diinginkan padanya. Misalnya, Anda dapat:
- Gambarkan ke dalam elemen
<canvas>
sehingga Anda dapat memanipulasinya - Mendownload ke perangkat pengguna
- Upload ke server dengan
fetch()
Mengakses kamera secara interaktif
Setelah kamu memahami dasarmu, sekarang saatnya untuk meningkatkan kemampuanmu secara bertahap!
Browser modern dapat mendapatkan akses langsung ke kamera, sehingga Anda dapat membuat pengalaman yang terintegrasi penuh dengan halaman web, sehingga pengguna tidak perlu meninggalkan browser.
Memperoleh akses ke kamera
Anda dapat mengakses kamera dan mikrofon secara langsung menggunakan API dalam spesifikasi WebRTC
yang disebut getUserMedia()
. Ini akan meminta pengguna untuk
mengakses mikrofon dan kamera yang terhubung.
Dukungan untuk getUserMedia()
cukup baik, tetapi belum tersedia di mana-mana. Secara khusus, versi ini tidak tersedia di Safari 10 atau yang lebih rendah, yang pada saat penulisan ini masih merupakan versi stabil terbaru.
Namun, Apple telah mengumumkan
bahwa fitur ini akan tersedia di Safari 11.
Namun, sangat mudah untuk mendeteksi dukungan.
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, kemudian Anda dapat melampirkannya ke elemen <video>
dan memutarnya
untuk menampilkan pratinjau real time, atau melampirkannya 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>
Dengan sendirinya, ini menjadi tidak berguna. Yang dapat Anda lakukan hanyalah mengambil data video dan memutarnya. Jika ingin mendapatkan gambar, Anda harus melakukan sedikit pekerjaan tambahan.
Dapatkan ringkasan
Opsi terbaik yang didukung untuk mendapatkan gambar adalah menggambar bingkai dari video ke kanvas.
Tidak seperti Web Audio API, tidak ada API pemrosesan streaming khusus untuk video di web sehingga Anda harus melakukan sedikit peretasan untuk mengambil snapshot dari kamera pengguna.
Prosesnya adalah sebagai berikut:
- Buat objek kanvas yang akan menyimpan bingkai dari kamera
- Mendapatkan akses ke streaming kamera
- Lampirkan ke elemen video
- Saat Anda ingin mengambil frame yang tepat, 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 data dari kamera disimpan di kanvas, Anda dapat melakukan banyak hal dengannya. Anda dapat:
- Upload langsung ke server
- Menyimpannya secara lokal
- Menerapkan efek keren pada gambar
Tips
Menghentikan streaming dari kamera saat tidak diperlukan
Sebaiknya hentikan penggunaan kamera saat tidak diperlukan lagi. Hal ini tidak hanya menghemat baterai dan daya pemrosesan, tetapi juga membuat pengguna lebih yakin pada aplikasi Anda.
Untuk menghentikan akses ke kamera, Anda cukup memanggil stop()
pada 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 memberi situs Anda akses ke kamera, begitu Anda memanggil getUserMedia()
, browser akan meminta pengguna untuk memberikan izin ke kamera kepada situs Anda.
Pengguna tidak suka dimintai akses ke perangkat canggih di komputer mereka dan mereka akan sering memblokir permintaan tersebut, atau akan mengabaikannya jika tidak memahami konteks pembuatan perintah tersebut. Praktik terbaik adalah hanya meminta akses ke kamera saat pertama kali diperlukan. Setelah pengguna memberi akses, mereka tidak akan diminta lagi. Namun, jika pengguna menolak akses, Anda tidak bisa mendapatkan akses lagi, kecuali jika mereka mengubah setelan izin kamera secara manual.
Kompatibilitas
Informasi selengkapnya tentang implementasi browser seluler dan desktop:
Sebaiknya gunakan juga shim adapter.js untuk melindungi aplikasi dari perubahan spesifikasi WebRTC dan perbedaan awalan.