대부분의 브라우저는 사용자 카메라에 액세스할 수 있습니다.
이제 대부분 브라우저는 사용자의 동영상 및 오디오 입력에 액세스할 수 있는 기능이 있습니다. 그러나 브라우저에 따라 완전한 동적 인라인 환경일 수도 있고 사용자 기기의 다른 앱에 위임될 수도 있습니다. 게다가 일부 기기에는 카메라가 없습니다. 그렇다면 어디서나 잘 작동하는 사용자 제작 이미지를 사용하는 환경을 어떻게 만들 수 있을까요?
단순하고 점진적으로 시작
환경을 점진적으로 개선하려면 어디서나 작동하는 기술로 시작해야 합니다. 가장 간단한 방법은 사용자에게 사전에 녹음된 파일을 요청하는 것입니다.
URL 요청
가장 잘 지원되지만 가장 만족스럽지 않은 옵션입니다. 사용자에게 URL을 제공받은 후 사용합니다. 이미지를 표시하기만 하면 어디에서나 작동합니다. img
요소를 만들고 src
를 설정하면 됩니다.
하지만 어떤 식으로든 이미지를 조작하고 싶다면 좀 더 복잡합니다. 서버가 적절한 헤더를 설정하고 개발자가 이미지를 crossorigin으로 표시하지 않는 한 CORS로 인해 실제 픽셀에 액세스할 수 없습니다. 이를 해결하는 유일한 실용적인 방법은 프록시 서버를 실행하는 것입니다.
파일 입력
이미지 파일만 원하는지 나타내는 accept
필터를 비롯한 간단한 파일 입력 요소를 사용할 수도 있습니다.
<input type="file" accept="image/*" />
이 메서드는 모든 플랫폼에서 작동합니다. 데스크톱에서는 사용자에게 파일 시스템에서 이미지 파일을 업로드하라는 메시지가 표시됩니다. iOS 및 Android의 Chrome 및 Safari에서 이 방법을 통해 사용자는 카메라로 직접 사진을 찍거나 기존 이미지 파일을 선택하는 등 이미지를 캡처하는 데 사용할 앱을 선택할 수 있습니다.
그런 다음 데이터를 <form>
에 첨부하거나 입력 요소에서 onchange
이벤트를 수신하여 JavaScript로 조작한 다음 이벤트 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 이하에서는 사용할 수 없습니다.
하지만 Safari 11에서 사용할 수 있다고 Apple에서 발표했습니다.
하지만 지원을 감지하는 것은 매우 간단합니다.
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 사양 변경과 접두사 차이로부터 앱을 보호하는 것이 좋습니다.