2024. 11. 12. 14:03ㆍ기술 창고/Javascript
이전에 canvas 로 데이터들을 통합하여 만들었었는데, 그 때 만들었던 쿠폰 canvas의 경우 저장하기 기능이 존재했기 때문에 이 canvas로 만들어진 데이터를 캡쳐한 이후에 다운로드 되는 기능이 존재해야 했습니다.
오늘은 이 기능을 javascript로 구현해보는 방법을 정리해보겠습니다.
HTML
<div class='col-12 couponImg' id="captureCoupon">
<!-- 노출 canvas 영역 -->
<canvas id="canvas" style="width: 100%;"></canvas>
</div>
<!-- canvas 저장 버튼 -->
<button type='button' onclick="capture();">
바코드 저장하기
</button>
하나로 만들어진 canvas 영역과 해당 canvas 데이터를 다운로드 받을 button 을 만들어주었습니다.
Javascript
// 쿠폰 + 바코드 스크린샷 다운로드
function capture() {
html2canvas(document.getElementById('canvas'), {
allowTaint: true,
useCORS: true
})
.then(function (canvas) {
var captureImgData = canvas.toDataURL('image/png');
var downloadLink = document.createElement('a');
downloadLink.href = captureImgData;
downloadLink.download = 'screenshot.png';
downloadLink.click();
});
}
버튼 동작 관련 Javascript 코드를 만들어줍니다.
저는 캡쳐를 하기위해 html2canvas 라이브러리를 import 받아 사용했습니다.
(# 저는 https://github.com/niklasvh/html2canvas/releases 사이트에서 latest 버전의 html2canvas.min.js 를 다운받아 프로젝트에 별도로 넣어놓고 import 받아 호출하여 사용했습니다.)
html2canvas를 통해 html 에 만들어진 canvas 영역을 호출하고 우선 설정값을 부여해줍니다.
- allowTaint : cross origin 허용 / 비허용 설정
- useCORS : CORS 보안을 사용한 서버로부터 이미지 접근 허용 설정
그 다음, html2canvas로 호출한 canvas를 image/png 형식의 데이터로 지정 설정합니다.
canvas를 다운로드 하기 위한 새로운 a 태그 구조를 생성하고, 해당 a 태그에 download 속성을 부여하면서, 다운로드 시 저장될 파일명을 설정해줍니다.
그리고 본격적으로 다운로드를 실행할 click 함수를 넣어 바로 다운로드가 실행될 수 있도록 합니다.
결과
이제 본격적으로 서버에 배포해서 모바일과 데스크탑 환경으로 각각 해당 버튼을 눌러 실행해보면,
정상적으로 모든 환경에서 캡쳐 후 다운로드 되고 canvas에 적용된 모든 내용들이 나오는 것을 확인할 수 있습니다.
+ 추가
그런데 위와 같은 방식을 적용했을 때 99프로 이상의 모바일 환경의 브라우저나 데스크탑의 브라우저 환경에서 정상적으로 동작되는 것을 확인할 수 있으나, 네이버 앱을 통해 실행하게 되면 동작되지 않을 것입니다.
(# 이상한 점은 ios 에서 네이버앱을 통해 실행했을 때는 된다는 것...)
이는 canvas의 데이터를 다운로드 하기 위해서는 위의 코드처럼 a 태그의 download, click 함수가 실행되야 하고 href 에 다운로드 받을 데이터의 경로가 들어가야 되는데, 위의 canvas.toDataURL('image/png') 함수를 실행했을 때, 형식은 png 이지만 전체적인 데이터 형식은 base64 기반의 암호화된 데이터로 들어가게 됩니다.
이 base64 기반의 암호화 데이터는 대부분의 플랫폼, 환경에서는 그대로 넣어도 무리없이 정상적으로 캡처 후, 다운로드가 되지만, 네이버 앱에서는 이 base64 기반의 데이터를 막아놓은 것으로 보입니다.
따라서, 네이버 앱까지 포괄적으로 정상적으로 캡처 후 다운로드가 되게 하기 위해서는 방식을 바꿀 필요가 있습니다.
Javascript (변경 후)
// 쿠폰 + 바코드 스크린샷 다운로드
function capture() {
const canvas = document.getElementById('canvas');
var captureImgData = canvas.toDataURL('image/png');
const imgData = atob(captureImgData.split(",")[1]);
const len = imgData.length;
const buf = new ArrayBuffer(len); // 비트를 담을 버퍼를 만든다.
const view = new Uint8Array(buf); // 버퍼를 8bit Unsigned Int로 담는다.
let blob, i;
for (i = 0; i < len; i++) {
view[i] = imgData.charCodeAt(i) & 0xff; // 비트 마스킹을 통해 msb를 보호한다.
}
// Blob 객체를 image/png 타입으로 생성한다. (application/octet-stream도 가능)
blob = new Blob([view], { type: "image/png" });
const url = window.URL.createObjectURL(blob); // blob:http://localhost:1234/28ff8746-94eb-4dbe-9d6c-2443b581dd30
var downloadLink = document.createElement('a');
downloadLink.href = url;
downloadLink.download = 'capture_coupon.png';
downloadLink.click();
}
수정한 Javascript 코드 로직을 보겠습니다.
canvas.toDataURL('image/png') 통해 암호화된 base64 기반의 데이터를 확인해보면 이렇습니다.
"data:image/png;base64, /9j/4Aadfnbljkndfasdfjlbgnladnfblnsldfnblsndfblxndjfngblsnrtnbu~~~"
base64 텍스트 뒤 쪽에 있는 암호화된 데이터가 실제 canvas의 암호화 데이터라고 보면 됩니다.
이를 우선 , 기호 기준으로 자르고 뒤 쪽의 실제 암호화 데이터를 디코딩하여 가지고 옵니다.
디코딩된 데이터의 길이를 가지고 ArrayBuffer를 만들고, 이 ArrayBuffer를 가지고 다시 8 bit Unsigned Int 형식의 Uint8Array 를 만들어줍니다.
이 Uint8Array에 디코딩된 데이터를 처음부터 한 글자씩 비트 마스킹(보안 처리) 하여 차곡차곡 넣어줍니다.
데이터가 전부 넣어진 Uint8Array를 blob 객체로 변환해주며, type 유형을 image/png 로 만들어줍니다.
window.URL.createObjectURL(blob) 을 통해 최종적으로 만들어진 blob 객체의 현재 존재하는 URL 경로를 추출해줍니다.
제 서버에서 이 URL 을 확인해보면, http://{배포 서버 ip 및 포트}/{난수화된 파일명} 형식으로 추출된 것을 확인할 수 있습니다.
이제 모든 준비는 끝났습니다.
이 추출한 URL을 가지고 다시 새로운 a 태그를 만들고, href에 추출한 url을 넣은 다음, download 속성을 부여하여 click() 함수를 호출하도록 합니다.
결과2
정상적으로 캡처 후 다운로드가 실행되면서 파일이 저장되었다는 알림이 나오는 것을 확인할 수 있습니다.
이제 모든 환경에서(아마도?) canvas 데이터를 캡처하여 다운로드 받을 수 있게 되었습니다.
'기술 창고 > Javascript' 카테고리의 다른 글
[Javascript] 이벤트 버블링 (Feat. 버블링 막기) (1) | 2024.11.20 |
---|---|
[Javascript] 이미지 및 텍스트 데이터 (+ 바코드 생성 데이터) 를 통합하여 하나의 데이터로 canvas 화 (4) | 2024.11.12 |
[Javascript] Object, Array Destructuring (분해) (0) | 2023.08.03 |
[Javascript] Arrow function (화살표 함수) (0) | 2023.08.01 |
[Javascript] Javascript ES6 Map(매핑) / Filter(필터) / Reduce(감소) / find(찾기) / findIndex(인덱스 찾기) (0) | 2023.08.01 |