Większość przeglądarek może uzyskać dostęp do kamery użytkownika.
Wiele przeglądarek ma teraz dostęp do danych wejściowych wideo i dźwięku od użytkownika. Jednak w zależności od przeglądarki może to być pełne, dynamiczne wrażenia w ramach strony lub funkcja delegowana do innej aplikacji na urządzeniu użytkownika. Co więcej, nie wszystkie urządzenia mają kamerę. Jak więc stworzyć obraz użytkownika, który działa dobrze wszędzie?
Zacznij od prostych i stopniowo przejdź do bardziej zaawansowanych
Jeśli chcesz stopniowo ulepszać swoje wrażenia, musisz zacząć od czegoś, co działa wszędzie. Najłatwiej jest poprosić użytkownika o wcześniej nagrany plik.
Prośba o adres URL
Jest to opcja najlepiej obsługiwana, ale najmniej satysfakcjonująca. Poproś użytkownika o podanie adresu URL i go użyj. W przypadku wyświetlania tylko obrazu działa wszędzie. Utwórz element img
, ustaw src
i gotowe.
Jeśli jednak chcesz w jakimś stopniu zmodyfikować obraz, sprawa się komplikuje. CORS uniemożliwia dostęp do rzeczywistych pikseli, chyba że serwer ustawi odpowiednie nagłówki i oznaczy obraz jako pochodzący z innego źródła. Jedynym praktycznym sposobem na obejście tego ograniczenia jest uruchomienie serwera proxy.
Dane wejściowe z pliku
Możesz też użyć prostego elementu danych pliku, w tym filtra accept
, który wskazuje, że chcesz używać tylko plików obrazów.
<input type="file" accept="image/*" />
Ta metoda działa na wszystkich platformach. Na komputerze użytkownik zostanie poproszony o przesłanie pliku z obrazem z systemu plików. W Chrome i Safari na iOS i Androidzie ta metoda pozwoli użytkownikowi wybrać aplikację do zrobienia zdjęcia, w tym umożliwić zrobienie zdjęcia bezpośrednio aparatem lub wybranie istniejącego pliku z obrazem.
Dane można następnie dołączyć do elementu <form>
lub zmodyfikować za pomocą kodu JavaScript, wykradając zdarzenie onchange
w elemencie wejściowym, a następnie odczytując właściwość files
zdarzenia 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>
Właściwość files
to obiekt FileList
, o którym opowiem później.
Opcjonalnie możesz też dodać do elementu atrybut capture
, który wskazuje przeglądarce, że wolisz pobrać obraz z kamery.
<input type="file" accept="image/*" capture />
<input type="file" accept="image/*" capture="user" />
<input type="file" accept="image/*" capture="environment" />
Dodanie atrybutu capture
bez wartości pozwala przeglądarce zdecydować, której kamery użyć, a wartości "user"
i "environment"
informują przeglądarkę, aby preferowała odpowiednio przednią i tylną kamerę.
Atrybut capture
działa na urządzeniach z Androidem i iOS, ale jest ignorowany na komputerach. Pamiętaj jednak, że w przypadku Androida użytkownik nie będzie już mógł wybrać istniejącej fotografii. Zamiast tego zostanie uruchomiona aplikacja aparatu systemowego.
Przeciągnij i upuść
Jeśli już dodajesz możliwość przesyłania pliku, możesz w kilka prostych sposobów nieco wzbogacić wrażenia użytkownika.
Pierwszym jest dodanie do strony celu upuszczenia, który umożliwia użytkownikowi przeciągnięcie pliku z pulpitu lub innej aplikacji.
<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>
Podobnie jak w przypadku danych wejściowych typu „plik”, możesz pobrać obiekt FileList
z właściwości dataTransfer.files
zdarzenia drop
.
Obsługa zdarzenia dragover
pozwala poinformować użytkownika, co stanie się po upuszczeniu pliku, za pomocą właściwości dropEffect
.
Funkcja przeciągania i upuszczania istnieje od dawna i jest dobrze obsługiwana przez główne przeglądarki.
Wklej ze schowka
Ostatnim sposobem na pobranie istniejącego pliku graficznego jest użycie schowka. Kod jest bardzo prosty, ale wygodę użytkowników trudno jest uzyskać w pełni.
<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
to kolejny obiekt FileList
).
Trudność w przypadku interfejsu Clipboard API polega na tym, że aby zapewnić pełną obsługę w różnych przeglądarkach, element docelowy musi być możliwy do wybrania i edytowania. Zarówno <textarea>
, jak i <input type="text">
spełniają te wymagania, podobnie jak elementy z atrybutem contenteditable
. Oczywiście służą też do edytowania tekstu.
Jeśli nie chcesz, aby użytkownik mógł wpisywać tekst, może być trudno sprawić, aby wszystko działało prawidłowo. Triki takie jak ukryte pole wejściowe, które jest wybierane po kliknięciu innego elementu, mogą utrudniać zachowanie dostępności.
Obsługa obiektu FileList
Ponieważ większość powyższych metod zwraca wartość FileList
, warto wyjaśnić, czym ona jest.
FileList
jest podobny do Array
. Zawiera klucze liczbowe i właściwość length
, ale nie jest właściwie tablicą. Nie ma metod tablic, takich jak forEach()
czy pop()
, i nie można jej iterować.
Oczywiście możesz uzyskać rzeczywiste tablice za pomocą funkcji Array.from(fileList)
.
Wpisy w obiekcie FileList
to obiekty File
. Są to dokładnie te same obiekty co Blob
, z tym wyjątkiem, że mają dodatkowe właściwości tylko do odczytu name
i 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>
W tym przykładzie zostanie znaleziony pierwszy plik, który ma typ MIME obrazu, ale można też obsługiwać wiele obrazów wybranych/wklejonych/upuszczonych jednocześnie.
Gdy masz dostęp do pliku, możesz z nim zrobić wszystko, co chcesz. Możesz na przykład:
- Narysuj go w elemencie
<canvas>
, aby móc nim manipulować. - Pobierz na urządzenie użytkownika.
- Prześlij go na serwer za pomocą
fetch()
Interaktywny dostęp do kamery
Teraz, gdy masz już podstawy, nadszedł czas na stopniowe ulepszanie.
Nowoczesne przeglądarki mogą uzyskiwać bezpośredni dostęp do kamer, co pozwala tworzyć wrażenia, które są w pełni zintegrowane ze stroną internetową, dzięki czemu użytkownik nie musi opuszczać przeglądarki.
Uzyskiwanie dostępu do aparatu
Dostęp do kamery i mikrofonu możesz uzyskać bezpośrednio, korzystając z interfejsu API w specyfikacji WebRTC getUserMedia()
. Użytkownik zostanie poproszony o dostęp do połączonych mikrofonów i kamer.
Obsługa getUserMedia()
jest całkiem dobra, ale nie wszędzie jest jeszcze dostępna. W szczególności nie jest ona dostępna w Safari 10 lub starszej, która w momencie pisania tego tekstu jest nadal najnowszą stabilną wersją.
Apple ogłosił, że będzie ona dostępna w Safari 11.
Jednak wykrycie zespołu pomocy jest bardzo proste.
const supported = 'mediaDevices' in navigator;
Gdy wywołujesz funkcję getUserMedia()
, musisz przekazać obiekt opisujący rodzaj multimediów, którego chcesz użyć. Te wybory nazywamy ograniczeniami. Istnieje kilka możliwych ograniczeń, takich jak preferowany aparat (przedni czy tylny), dźwięk i rozdzielczość strumienia.
Aby jednak uzyskać dane z kamery, potrzebujesz tylko jednego ograniczenia, a jest nim video: true
.
W przypadku powodzenia interfejs API zwróci obiekt MediaStream
zawierający dane z aparatu. Możesz go załączyć do elementu <video>
i odtworzyć, aby wyświetlić podgląd w czasie rzeczywistym, lub do elementu <canvas>
, aby uzyskać zrzut ekranu.
<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>
Samo w sobie nie jest zbyt przydatne. Możesz tylko pobrać dane wideo i je odtworzyć. Jeśli chcesz uzyskać obraz, musisz wykonać trochę dodatkowej pracy.
Zrób zrzut
Najlepszą opcją uzyskania obrazu jest przeniesienie kadru z filmu na płótno.
W przeciwieństwie do interfejsu Web Audio API nie ma dedykowanego interfejsu API do przetwarzania strumieniowego w przypadku filmów w internecie, więc aby zrobić zrzut ekranu z kamery użytkownika, musisz użyć trochę sztuczek.
Proces wygląda następująco:
- Utwórz obiekt canvas, który będzie zawierać ramkę z kamery.
- Uzyskiwanie dostępu do strumienia z kamery
- Dołącz go do elementu wideo.
- Jeśli chcesz uchwycić konkretny kadr, dodaj dane z elementu wideo do obiektu na osi czasu za pomocą
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>
Gdy masz dane z kamery zapisane w płótnie, możesz z nimi wiele zrobić. Możesz:
- Prześlij je bezpośrednio na serwer
- przechowywać lokalnie,
- Zastosuj do obrazu fajne efekty
Wskazówki
Zatrzymaj strumieniowanie z kamery, gdy nie jest potrzebne
Warto przestać używać kamery, gdy nie jest już potrzebna. Pozwoli to nie tylko zaoszczędzić baterię i moc obliczeniową, ale też zwiększy zaufanie użytkowników do aplikacji.
Aby zatrzymać dostęp do kamery, możesz po prostu wywołać funkcję stop()
w przypadku każdego ścieżki wideo dla strumienia zwracanego przez funkcję 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>
Prośba o dostęp do aparatu
Jeśli użytkownik nie zezwolił wcześniej Twojej witrynie na dostęp do kamery, gdy wywołasz funkcję getUserMedia()
, przeglądarka poprosi użytkownika o przyznanie Twojej witrynie dostępu do kamery.
Użytkownicy nie lubią otrzymywać prośby o dostęp do zaawansowanych urządzeń na swoim komputerze. Często blokują takie prośby lub ignorują je, jeśli nie rozumieją kontekstu, w którym zostały utworzone. Najlepiej prosić o dostęp do kamery tylko wtedy, gdy jest to konieczne. Gdy użytkownik przyzna dostęp, nie będzie już o to pytany. Jeśli jednak użytkownik odrzuci dostęp, nie będzie można go uzyskać ponownie, chyba że użytkownik ręcznie zmieni ustawienia uprawnień aparatu.
Zgodność
Więcej informacji o wdrożeniu w przeglądarce mobilnej i komputerowej:
Zalecamy też używanie pliku adapter.js, aby chronić aplikacje przed zmianami specyfikacji WebRTC i różnicami w prefiksach.