Blob
해당 포스팅은 https://ko.javascript.info/blob 을 공부할 목적으로 번역한 내용을 기반으로 나름대로 정리해본 포스팅입니다. 글 내의 예시코드 및 내용들을 자세히 보고 싶으시면 위의 링크 참고 부탁드립니다.
1. Blob
ArrayBuffer 는 ECMA 표준중 하나이다.
브라우저에서는 File API에 설명된 추가적인 고수준 객체들이 있다. 그중 하나가 Blob이다.
ㄴ 웹 애플리케이션에서 파일과 이미지를 다루고 데이터를 처리하는 데 유용한 도구
보통 Blob 객체가 사용될 때는 대표적으로 아래정도가 있다.
- 파일 업로드 및 다운로드할 때 : 사용자가 업로드한 파일이나 서버에서 다운로드한 파일을 Blob 객체로 처리
- 이미지 처리, 프리뷰, 다운로드, 업로드할 때
ㄴ Canvas API 등 다른 방법도 존재 - 웹 오디오 및 비디오 스트리밍할 때 : 오디오 및 비디오 데이터를 Blob 객체로 다루어 재생
- 웹 워커와 메시지 전달할 때 : 웹 워커 간에 메시지를 전달하고 데이터를 공유하기 위해 Blob 객체를 사용
Blob은 선택적인 문자열 type(일반적으로 MIME 유형)과 blobParts (문자열 + BufferSource)로 이루어져 있다.
- 참고
- MIME (Multipurpose Internet Mail Extensions) 유형은 보통 확장자와 함께 사용되며, 각 유형은 해당 데이터의 형식을 나타낸다. 일반적인 MIME 유형에는 다음과 같은 것들이 있다:
- text/plain: 일반 텍스트 파일
- text/html: HTML 문서
- image/jpeg: JPEG 이미지
- image/png: PNG 이미지
- application/pdf: PDF 문서
- application/json: JSON 데이터
- audio/mpeg: MP3 오디오 파일
- video/mp4: MP4 비디오 파일
다음은 Blob 객체를 만드는 코드이다.
new Blob(blobPars, options)
blobParts
는 Blob/BufferSource/String 값들의 배열options
는 선택적인 객체로서 다음과 같은 속성을 가질 수 있음type
: Blob의 유형으로, 일반적으로 MIME 유형, 예를 들어, image/pngendings
: Blob을 현재 운영 체제의 새 줄로 변환하기 위해 줄 끝(\r\n
or\n
)을 변환해야하는지 여부를 나타냄. 기본적으로 "transparent"로 설정되어 있어 아무것도 하지 않지만, "native"로 설정할 수도 있(변환 수행).
아래 코드는 여러 형태의 Blob 객체를 만드는 예시이다.
// create Blob from a string
let blob = new Blob(["<html>…</html>"], {type: 'text/html'});
// please note: the first argument must be an array [...]
// create Blob from a typed array and strings
let hello = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" in binary form
let blob = new Blob([hello, ' ', 'world'], {type: 'text/plain'});
1.1 Blob.slice
만들어진 Blob 객체를 잘라낼수도 있다.
blob.slice([byteStart], [byteEnd], [contentType]);
byteStart
– 어디서부터 자를지byteEnd
– 어디까지 자를지contentType
– 잘라진 Blob 객체의 타
Array.slice 와 비슷하게 사용된다.
☆ Blob 객체는 불변(immutable)
- Blob 내의 데이터를 직접 변경할 수는 없다.
- 그러나 Blob에서 일부를 잘라내어 새로운 Blob 객체를 만들거나, 그들을 새로운 Blob으로 혼합하는 등의 작업은 가능하다
- 이 동작은 JavaScript 문자열과 유사하다. 문자열 내의 문자를 직접 변경할 수는 없지만, 올바르게 수정된 새로운 문자열을 만들 수 있다
2. Blob as URL
Blob은 <a>, <img> 또는 다른 태그에서 그 내용을 보여주기 위해 URL로 쉽게 사용될 수 있다.
type 덕분에 Blob 객체를 다운로드/업로드할 수 있으며, 이 타입은 네트워크 요청에서 Content-Type으로 자연스럽게 사용된다.
아래는 간단한 예제코드이다. 링크를 클릭하면 동적으로 생성된 "hello world" 내용의 Blob을 파일로 다운로드할 수 있다.
<!-- download attribute forces the browser to download instead of navigating -->
<a download="hello.txt" href='#' id="link">Download</a>
<script>
let blob = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
</script>
HTML 없이 JavaScript에서 동적으로 링크를 생성하고 link.click()을 통해 클릭을 시뮬레이트하여 자동으로 다운로드를 시작할 수도 있다. 다음은 HTML 없이 동적으로 생성된 Blob을 사용자가 다운로드하도록 유도하는 유사한 코드이다.
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
2.1. createObjectURL
URL.createObjectURL
은 Blob을 가져와서 해당 Blob을 위한 고유한 URL을 생성한다. 이 URL의 형식은 blob:<origin>/<uuid>
이다.
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
각각의 URL이 URL.createObjectURL
에 의해 생성될 때, 브라우저는 내부적으로 URL과 Blob 사이의 매핑을 저장한다.
이런 식으로 생성된 URL은 Blob에 접근할 수 있게 해준다.
생성된 URL과 그에 대한 링크는 현재 문서 내에서만 유효하다. 문서가 열려 있는 동안만 유효하며, 이 URL을 사용하여 Blob을 <img>, <a> 등 다른 객체에 참조할 수 있다고 한다.
그러나 Blob에 대한 매핑이 존재하는 동안, Blob 자체는 메모리에 남아 있게 된다.
이러한 매핑은 문서가 언로드될 때 자동으로 지워지므로, Blob 객체는 에플리케이션을 닫게 되면 메모리에서 해제된다.
2.2. 메모리 이슈
그러나 애플리케이션이 장기간 실행되는 경우, 이러한 Blob은 메모리에 계속 남아있게 된다.
따라서 URL을 생성하면, Blob은 메모리에 계속 남아 있게 되며, 필요하지 않은 경우에도 메모리를 사용하게 된다.
이 문제를 해결하기 위해 URL.revokeObjectURL(url)
은 내부 매핑에서 참조를 제거하여 Blob이 삭제되고, 메모리가 해제될 수 있도록 한다.
URL.revokeObjectURL(link.href);
마지막 예제에서는 Blob이 한 번만 사용되도록 하기 위해 즉시 URL.revokeObjectURL(link.href)
를 호출하는 걸 볼 수 있다.
이전의 HTML 링크를 사용하는 예제에서는 URL.revokeObjectURL(link.href)
를 호출하지 않는다.
왜냐하면 HTML 에서 URL.revokeObjectURL 을 해주게 되면 링크 매핑이 안되기 때문이다.
넣어주더라도 click 이벤트 헨들러에서 넣어주어야 할 것으로 보인다.
(위의 이유로 createElement 를 활용해 revoke 해주는 형태를 주로 쓰긴함)
3. Blob to base64
URL.createObjectURL
대체로 Blob을 base64로 인코딩된 문자열로 변환할 수 있다.
- [참고] base64
- 이진 데이터를 텍스트로 인코딩하는 데 사용되는 인코딩 방식.
- 이진 데이터는 컴퓨터가 이해하는 바이트 형태로 표현되는 반면, Base64는 ASCII 문자로 이루어진 텍스트 문자열로 표현
이 인코딩은 이진 데이터를 0에서 64까지의 ASCII 코드를 가진 문자열로 나타낸다. 더 중요한 점은 이 인코딩을 "data-urls"에서 사용할 수 있다는 것이다.
ㄴ "data-urls"은 데이터를 포함하는 URL 형식으로, 이미지, 오디오, 비디오 등의 다양한 미디어 타입을 포함할 수 있다. 이러한 URL은 일반적인 웹 페이지 URL과 마찬가지로 사용할 수 있으며, 이미지나 파일과 같은 리소스를 표현하고 포함하는 데 유용하다.
ㄴ HTTP 요청 수를 줄이고 페이지의 성능을 향상시킬 수 있다.
data-urls 은 다음과 같은 형식을 가지고 있다.→ data:[<mediatype>][;base64],<data>.
이러한 URL은 "일반" URL과 마찬가지로 어디서든 사용할 수 있다.
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
- 사용처
- 로컬의 이미지를 img 태그의 인라인 형태로 url 지정
- 사용자가 업로드한 이미지를 cdn 에 저장후 → cdn url 받고 미리보기 형태로 보여주는게 아닌, 그냥 브라우저 레벨에서 data 변환후 data-url 로도 미리보기 이미지를 보여줄수있다.
브라우저는 문자열을 디코딩하여 이미지를 표시한다
Blob을 base64로 변환하기 위해 내장된 FileReader 객체를 사용할 것이다.
이 객체는 다양한 형식의 Blob에서 데이터를 읽을 수 있다.
(FileReader 객체는 다음장에서 나온다,
그냥 단순히 blob 객체 내부의 데이터를 읽고 변환 및 처리해주는 아이라고 생각하면 편하다)
다음은 Blob을 base64를 통해 다운로드하는 예시이다.
let link = document.createElement('a');
link.download = 'hello.txt';
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
let reader = new FileReader();
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload
reader.onload = function() {
link.href = reader.result; // data url
link.click();
};
정리하면 Blob 의 URL 링크를 만드는 2가지 방식은 아래와 같다.
- createObjectURL 로 만드는 방식
- base64 로 인코딩된 문자열로 변환후 URL로 만드는 방식
이 중 보통은 URL.createObjectURL(blob) 이 빠르고 간단해서 주로 사용되곤 한다.
URL.createObjectURL(blob)
ㄴ 메모리가 걱정되면 revoke
ㄴ 직접 blob 에 접근 가능하다. encoding, decoding 이 필요없다.
Blob to data url
ㄴ revoke 할 필요가 없음
ㄴ 대용량의 Blob 객체의 경우 성능와 메모리 이슈가 있을 수 있다.
4. Image to blob
이미지, 이미지 부분 또는 페이지 스크린샷의 Blob을 생성할 수 있다.
이는 어딘가에 업로드하기 편리하다.
이미지 작업은 <canvas> 요소를 통해 수행되는데 그 방법은 아래와 같다.
- 이미지 (또는 일부분)을 canvas에 canvas.drawImage를 사용하여 그린다.
- .toBlob(callback, format, quality) 캔버스 메서드를 호출한다. 이는 Blob을 생성하고 완료되면 콜백을 실행한다.
아래 예제에서는 이미지를 단순히 복사하지만, 이미지에서 잘라내거나 캔버스에서 변형한 후 Blob을 만들 수 있다.
// take any image
let img = document.querySelector('img');
// make <canvas> of the same size
let canvas = document.createElement('canvas');
canvas.width = img.clientWidth;
canvas.height = img.clientHeight;
let context = canvas.getContext('2d');
// copy image to it (this method allows to cut image)
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas
// toBlob is async opereation, callback is called when done
canvas.toBlob(function(blob) {
// blob ready, download it
let link = document.createElement('a');
link.download = 'example.png';
link.href = URL.createObjectURL(blob);
link.click();
// delete the internal blob reference, to let the browser clear memory from it
URL.revokeObjectURL(link.href);
}, 'image/png');
canvas.toBlob 의 콜백함수 시점에 처리된 이미지 데이터를 다운로드할 때 blob 이 사용된 예시라고 이해하면 쉽다.
async/await 키워드를 사용해줄 수도 있다.
let blob = await new Promise(resolve => canvasElem.toBlob(resolve, 'image/png'));
5. From Blob to ArrayBuffer
Blob 생성자는 거의 모든 것에서 Blob을 생성할 수 있게 해주며, 이는 BufferSource를 포함한 모든 것에서 생성할 수 있다
그러나 만약 낮은 수준의 처리를 수행해야 한다면, FileReader를 사용하여 가장 낮은 수준의 ArrayBuffer를 얻을 수도 있다. 이를 통해 더 세밀한 처리를 함으로써 다양한 작업을 수행해줄 수 있다.
// get arrayBuffer from blob
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(blob);
fileReader.onload = function(event) {
let arrayBuffer = fileReader.result;
};
6. 정리
- ArrayBuffer, Uint8Array 및 기타 BufferSource는 "바이너리 데이터"를 나타낸다. 한편, Blob은 "타입 + 바이너리 데이터"를 나타낸다.
- 이로써 Blobs는 브라우저에서 흔한 업로드/다운로드 작업에 편리하다.
- Blob 을 URL 로 변환할 수 있는 방법은 URL.createObjectUrl 과 FIleReader.readAsDataURl 이 있다.
- XMLHttpRequest, fetch 등과 같은 웹 요청을 수행하는 메서드는 Blob과 같은 바이너리 타입과 함께 네이티브로 작동할 수 있다.
- Blob과 낮은 수준의 바이너리 데이터 타입 간에 쉽게 변환할 수 있다.
- TypedArray에서 Blob을 만들 수 있음 (new Blob(...) 생성자 사용)
- FileReader를 사용하여 Blob에서 ArrayBuffer를 얻은 다음, 낮은 수준의 바이너리 처리를 위해 이를 view로 만들 수 있음