Capturing an Image from the User

Many browsers now have the ability to access video and audio input from the
user. However, depending on the browser it might be a full dynamic and inline
experience, or it could be delegated to another app on the user's device. On top
of that, not every device even has a camera. So how can you create an experience
that uses a user generated image that works well everywhere?

Start simple and progressively

If you want to progressively enhance your experience, you need to start with something that works
everywhere. The easiest thing to do is simply ask the user for a pre-recorded file.

Ask for a URL

This is the best supported but least satisfying option. Get the user to give you a URL, and then
use that. For just displaying the image this works everywhere. Create an img element, set the
src and you're done.

Though, if you want to manipulate the image in any way, things are a bit more complicated.
CORS prevents you from
accessing the actual pixels unless the server sets the appropriate headers and you
mark the image as crossorigin
; the only practical way around that is to run a proxy server.

File input

You can also use a simple file input element, including an accept filter that indicates you only
want image files.

<input type="file" accept="image/*">

This method works on all platforms. On desktop it will prompt the user to
upload an image file from the file system. In Chrome and Safari on iOS and Android this method will
give the user a choice of which app to use to capture the image, including the option of taking a
photo directly with the camera or choosing an existing image file.

The data can then be attached to a <form> or manipulated with JavaScript by
listening for an onchange event on the input element and then reading
the files property of the event target.

Adding the capture attribute without a value let's the browser decide which camera to use, while
the "user" and "environment" values tell the browser to prefer the front and rear cameras,
respectively.

The capture attribute works on Android and iOS, but is ignored on desktop. Be aware, however,
that on Android this means that the user will no longer have the option of choosing an existing
picture. The system camera app will be started directly, instead.

Drag and drop

If you are already adding the ability to upload a file, there are a couple of easy ways that you
can make the user experience a little richer.

The first is to add a drop target to your page that allows the user to drag in a file from the
desktop or another application.

The tricky part with the clipboard API is that, for full cross-browser support, the target element
needs to be both selectable and editable. Both <textarea> and <input type="text"> fit the bill
here, as do elements with the contenteditable attribute. But these are also obviously designed for
editing text.

It can be difficult to make this work smoothly if you don't want the user to be able to input
text. Tricks like having a hidden input that gets selected when you click on some other element may
make maintaining accessibility harder.

Handling a FileList object

Since most of the above methods produce a FileList, I should talk a little about what that is.

A FileList is similar to an Array. It has numeric keys and a length property, but it isn't
actually an array. There are no array methods, like forEach() or pop(), and it isn't iterable.
Of course, you can get a real Array by using Array.from(fileList).

The entries of the FileList are File objects. These are exactly the same as Blob objects
except that they have additional name and lastModified read-only properties.

This example finds the first file that has an image MIME type, but it could also handle multiple
images being selected/pasted/dropped at once.

Once you have access to the file you can do anything you want with it. For
example, you can:

Draw it into a <canvas> element so that you can manipulate it

Download it to the user's device

Upload it to a server with fetch()

Access the camera interactively

Now that you've covered your bases, it's time to progressively enhance!

Modern browsers can get direct access to cameras, allowing you to build
experiences that are fully integrated with the web page, so the user need never
leave the browser.

Acquire access to the camera

You can directly access a camera and microphone by using an API in the WebRTC
specification called getUserMedia(). This will prompt the user for
access to their connected microphones and cameras.

Support for getUserMedia() is pretty good, but it isn't yet everywhere. In particular, it is not
available in Safari 10 or lower, which at the time of writing is still the latest stable version.
However, Apple have announced
that it will be available in Safari 11.

It's very simple to detect support, however.

const supported = 'mediaDevices' in navigator;

Warning: Direct access to the camera is a powerful feature. It requires consent
from the user, and your site MUST be on a secure origin (HTTPS).

When you call getUserMedia(), you need to pass in an object that describes what kind of media you
want. These choices are called constraints. There are a several possible constraints, covering
things like whether you prefer a front- or rear-facing camera, whether you want audio, and your
preferred resolution for the stream.

To get data from the camera, however, you need just one constraint, and that is video: true.

If successful the API will return a MediaStream that contains data from
the camera, and you can then either attach it to a <video> element and play it
to show a real time preview, or attach it to a <canvas> to get a
snapshot.

Ask permission to use camera responsibly

If the user has not previously granted your site access to the camera then
the instant that you call getUserMedia() the browser will prompt the user to
grant your site permission to the camera.

Users hate getting prompted for access to powerful devices on their machine
and they will frequently block the request, or they will ignore it if they don't
understand the context for which the prompt has been created. It is best
practice to only ask to access the camera when first needed. Once the user has
granted access they won't be asked again. However, if the user rejects access,
you can't get access again, unless they manually change camera permission
settings.

Warning: Asking for access to the camera on page load will result in most of
your users rejecting access to it.