일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- KAKAO
- BFS
- 코테
- computerscience
- CS
- 국비지원
- 너비우선탐색
- 호이스팅
- github
- 부트캠프
- 알고리즘
- 그리디
- 코딩테스트
- 국비지원취업
- DFS
- LinkSnap
- 패스트캠퍼스
- 프론트엔드개발자
- git
- 컴퓨터과학
- js
- 야놀자
- html/css/js
- 컴퓨터공학
- Javascript
- 백준
- nodejs
- 자바스크립트
- CSS
- cpu
- Today
- Total
My Boundary As Much As I Experienced
프론트엔드 단에서 '유저 서명이 박힌 이미지' 다운로드 기능 구현하기 (feat. canvas 태그로 이미지 로드 후 text 넣기, a태그로 download 링크 구현) 본문
프론트엔드 단에서 '유저 서명이 박힌 이미지' 다운로드 기능 구현하기 (feat. canvas 태그로 이미지 로드 후 text 넣기, a태그로 download 링크 구현)
Bumang 2024. 8. 9. 10:33
구현해야 될 기능 - 이미지 다운로드 기능 구현하기
이번에 내가 제안한 아이디어를 바로 기능으로 구현할 수 있는 기회가 주어졌다.
바로, 자신의 KYC스코어를 인증서 이미지로 저장하거나,
이를 X(전 트위터)에 공유할 수 있는 기능을 구상하였다.
이때, 인증서 이미지를 다운받는 과정은 따로 백엔드 없이
프론트엔드에서 이미지에 텍스트만 박아서 다운로드 받게 할 수 있을 것 같았고,
서버 호출을 줄이기 위해 프론트엔드 단에서 기능구현을 마무리해보겠다고 제안했다.
구현 아이디어 1단계: Share모달을 실행할 때 canvas에 이미지를 로드하기
useEffect에 generateImage라는 핸들러로 nickname, kycScore, maxScore 등을 제공한다.
useEffect(() => {
generateImage(user?.nickname ?? 'User', kycScore, maxScore);
// eslint-disable-next-line
}, []);
그리고 GenerateImage는 아래처럼 구성되어 있다.
캔버스를 포착하여 이미지를 채워넣고, describeNickname과 describeScore를 실행시켜준다.
// generateImage함수는 아래와 같다.
const generateImage = (
nickname: string,
kycScore: number,
maxScore: number
) => {
const canvas = canvasRef.current;
if (!canvas) return; // 타입가드
const ctx = canvas.getContext('2d')!; // 캔버스를 생성
const image = new Image(); // 새로운 이미지를 생성
image.src = '/SHARE.png'; // 이미지 템플릿 로드 (그런데 스코어랑 유저네임은 비어져 있는..)
// 닉네임 새기기 함수
const describeNickname = (nickname: string) => {
...
};
// 스코어 새기기 함수
const describeScore = (
kycScore: number,
maxScore: number,
round: number
) => {
...
};
// 이미지가 로드되면 캔버스에 이미지를 꽉차게 로드하고 이름과 스코어를 새긴다.
image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
describeNickname(nickname);
describeScore(kycScore, maxScore, round);
};
};
describeNickname
describeNickname은 아래처럼 생겼다.
canvas 상에 x, y 좌표를 계산하여 닉네임을 새겨준다.
const describeNickname = (nickname: string) => {
// 텍스트 설정
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 50px Poppins';
// 텍스트 위치 계산 (이미지 하단 중앙)
const x = 130;
const y = canvas.height - 170; // 하단에서 170픽셀 위
// 텍스트 추가
ctx.fillText(nickname, x, y);
};
describeScore
describeScore은 아래처럼 생겼다.
이 역시 canvas 상에 x, y 좌표를 계산하여 닉네임을 새겨준다.
이때 SCORE / MAXSCORE 식으로 표기해줘야하는데,
SCORE와 MAXSCORE의 폰트스타일이 달라 한 번에 처리할 순 없었다.
그리고 SCORE가 최대 4자리, 최소 1자리여서, `${/ MAXSCORE}`의 위치가 동적으로 바뀌어야 했다.
(안 그러면 매우 벙벙해지거나 글자가 겹치거나... 둘 중 하나가 된다.)
그런데 찾아보니 canvas에선measureText라는 메소드가 있어서, 텍스트의 길이를 측정할 수 있었다.
const textWidth = ctx.measureText(SCORE).width;
을 통해 SCORE의 길이를 찾아내고, 적절한 위치에 MAXSCORE까지 잘 기입할 수 있었다.
const describeScore = (
kycScore: number,
maxScore: number,
round: number
) => {
const SCORE = kycScore.toString();
const MAXSCORE = maxScore.toString();
const ROUND = round.toString();
ctx.fillStyle = '#FFFFFF';
ctx.font = '700 128px Poppins';
let x = 700;
let y = 514;
ctx.fillText(ROUND, x, y);
// 유저스코어 텍스트 설정
// ctx.fillStyle = '#FFFFFF';
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, '#54FF60');
gradient.addColorStop(0.2, '#0BFFC5');
ctx.fillStyle = gradient;
ctx.font = 'bold 100px Poppins';
// 텍스트 위치 계산 (이미지 하단 중앙)
x = 130;
y = canvas.height - 370; // 하단에서 170픽셀 위
const textWidth = ctx.measureText(SCORE).width;
// 유저스코어 추가
ctx.fillText(SCORE, x, y);
// 최대스코어 설정 및 추가
ctx.fillStyle = '#FFFFFF';
ctx.font = '600 60px Poppins';
x = x + textWidth;
ctx.fillText(` / ${MAXSCORE}(max)`, x, y);
};
구현 아이디어 2단계: <a> 태그로 브라우저 다운로드 구현하기
이제 다운로드를 구현할 차례다. 브라우저에서 다운로드를 어떻게 구현할 수 있을까?
나는 <a> 태그에 href 속성과 download 속성을 사용하여 다운로드 링크를 제공하는 방법으로 구현하였디.
a태그.. 그냥 하이퍼링크를 만드는 기능만 있는줄 알았는데 아니었다.
<a> 태그의 href와 download 속성
- href 속성: <a> 태그의 href 속성은 리소스의 경로를 지정합니다. 이 리소스는 웹 페이지일 수도 있고, 파일일 수도 있습니다. 브라우저는 href에 지정된 URL을 통해 파일을 가져오게 됩니다.
- download 속성: 이 속성은 브라우저에 해당 링크를 클릭할 때 리소스를 새 창에서 열지 않고, 파일로 다운로드하도록 지시합니다. 또한, 다운로드될 파일의 이름도 지정할 수 있습니다.
라고 한다.
나는 handleDownload라는 핸들러를 만들어서, 다운로드 버튼을 클릭했을 때
a태그를 절차적으로 생성하여 download파일 이름을 설정하고, a태그에 url을 설정하고,
클릭을 실행하였다.
const handleDownload = () => {
const canvas = canvasRef.current!;
const link = document.createElement('a');
link.download = `${user?.nickname}_KYC.png`;
link.href = canvas.toDataURL('image/png');
link.click();
};
당면한 문제점 - 모바일 브라우저에선 a태그 다운로드가 막힌 브라우저가 존재
a태그를 이용한 다운로드는 pc브라우저에서는 chrome, firefox, safari 모두 다 동작하였다.
하지만 a태그가 다운로드로 동작하지 않는 모바일 브라우저가 존재했다.
(웹3 사용자들이 많이 사용하는 brave브라우저에서는 download가 제대로 작동하지 않았고,
safari에서도 동작하지 않았다..)
FileSaver.js를 사용하여 구현
그래서 하는 수 없이 javascript로 FileSaver.js라는 콘텐츠 다운로드 라이브러리를 사용했다.
fileSaver의 saveAs 함수를 통해 canvas의 이미지를 Blob데이터로 환산하여 다운로드했다.
import { saveAs } from 'file-saver';
const handleDownload = () => {
const canvas = canvasRef.current!;
canvas.toBlob(function (blob: any) {
saveAs(blob, `${user?.nickname ?? 'user'}_KYC.png`);
});
};
'FrontEnd > Frontend etc.' 카테고리의 다른 글
Zustand를 사용할 때 immer를 사용하면 편한 점은 무엇일까 (0) | 2024.09.23 |
---|---|
RN) React-Navigation에서 useRoute 개념 및 사용법 (0) | 2024.09.05 |
WebShare API로 모바일 공유 기능 구현하기 (feat. navigator객체) (0) | 2024.07.31 |
프론트엔드 환경에 WEB3 지갑 SDK 연결 (MetaMask, Wepin) (0) | 2024.07.30 |
모던 브라우저에서 오디오 자동재생을 구현하기 위한 노력.. (4) | 2024.07.27 |