diff --git a/src/components/common/share/NativeShareButton.tsx b/src/components/common/share/NativeShareButton.tsx index a2301ba..667415c 100644 --- a/src/components/common/share/NativeShareButton.tsx +++ b/src/components/common/share/NativeShareButton.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { NavigatorShareIcon } from '@/assets/SocialIcons'; import { ImageButton } from '@/components/common/buttons'; @@ -11,7 +11,7 @@ const NativeShareButton = ({ sharedMessage }: NativeShareButtonProps) => { const [show, setShow] = useState(false); useEffect(() => { - if (!(navigator.share === undefined || navigator.canShare(sharedMessage) === false)) { + if (navigator.share !== undefined) { setShow(true); } }, [sharedMessage]); diff --git a/src/components/results/CertificateAndShareModal.tsx b/src/components/results/CertificateAndShareModal.tsx index 407b92e..a01d0c0 100644 --- a/src/components/results/CertificateAndShareModal.tsx +++ b/src/components/results/CertificateAndShareModal.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { useRef, useState } from 'react'; import { toSvg } from 'html-to-image'; import { useRouter } from 'next/router'; @@ -8,7 +8,6 @@ import { DownloadIcon } from '@/assets/Icons'; import Portal from '@/components/Portal'; import { Background, ModalContainer } from '@/components/common/Modal'; import { CloseButton, ShareButton } from '@/components/common/buttons'; -import Card from '@/components/event/Card'; import Certificate from '@/components/results/Certificate'; import ShareButtons from '@/components/results/ShareButtons'; import useModalAnimation from '@/hooks/useModalAnimation'; @@ -20,10 +19,7 @@ interface CertificateAndShareModalProps { const createImage = async (url: string): Promise => { return new Promise((resolve, reject) => { const img = new Image(); - img.onload = () => - setTimeout(() => { - resolve(img); - }, 200); + img.onload = () => resolve(img); img.decode = async () => resolve(img); img.onerror = reject; img.crossOrigin = 'anonymous'; @@ -65,20 +61,25 @@ const toPng = async (node: HTMLDivElement) => { const CertificateAndShareModal = ({ onClose }: CertificateAndShareModalProps) => { const { show, animationAfterClose } = useModalAnimation(onClose); + const [imgData, setImgData] = useState(''); const ref = useRef(null); const router = useRouter(); - const certificateDownload = () => { + const getImgData = () => { + if (imgData) return imgData; if (ref.current === null) { - return; + return ''; } + return toPng(ref.current); + }; - toPng(ref.current).then(dataUrl => { - const link = document.createElement('a'); - link.download = '임명장.png'; - link.href = dataUrl; - link.click(); - }); + const certificateDownload = async () => { + const dataUrl = await getImgData(); + const link = document.createElement('a'); + link.download = '임명장.png'; + link.href = dataUrl; + link.click(); + setImgData(dataUrl); }; const redirectHome = () => { @@ -93,13 +94,12 @@ const CertificateAndShareModal = ({ onClose }: CertificateAndShareModalProps) => - + 이미지 저장하기! 홈으로 돌아가기 - diff --git a/src/components/results/ConvertImage.ts b/src/components/results/ConvertImage.ts new file mode 100644 index 0000000..9525c38 --- /dev/null +++ b/src/components/results/ConvertImage.ts @@ -0,0 +1,56 @@ +import { toSvg } from 'html-to-image'; + +export const createImage = async (url: string): Promise => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.decode = async () => resolve(img); + img.onerror = reject; + img.crossOrigin = 'anonymous'; + img.src = url; + }); +}; + +export const toPng = async (node: HTMLDivElement) => { + const { offsetWidth: width, offsetHeight: height } = node; + const multiple = 2; + + const svgDataUrl = await toSvg(node); + + const canvas = document.createElement('canvas'); + const offscreenCanvas = canvas.transferControlToOffscreen(); + offscreenCanvas.width = width * multiple; + offscreenCanvas.height = height * multiple; + const context = offscreenCanvas.getContext('2d', { alpha: false }); + if (context === null) return ''; + + const img: HTMLImageElement = await createImage(svgDataUrl); + let done = false; + const onFrame = () => { + context.drawImage(img, 0, 0, width * multiple, height * multiple); + if (canvas.toDataURL('image/png', 1.0).length > 204800) done = true; + if (!done) { + window.requestAnimationFrame(onFrame); + } + }; + onFrame(); + + return new Promise((resolve: (url: string) => void) => { + setTimeout(() => { + const url = canvas.toDataURL('image/png', 1.0); + resolve(url); + }, 500); + }); +}; + +export const convertDataUrlToFile = (url: string) => { + if (url === '') return undefined; + return fetch(url) + .then(response => response.blob()) + .then(blob => { + const ext = url.split('.').pop(); // url 구조에 맞게 수정할 것 + const filename = url.split('/').pop() || '영수증.png'; // url 구조에 맞게 수정할 것 + const metadata = { type: `image/${ext}` }; + return new File([blob], filename, metadata); + }); +}; diff --git a/src/components/results/ShareButtons.tsx b/src/components/results/ShareButtons.tsx index 6011b89..93ed3c0 100644 --- a/src/components/results/ShareButtons.tsx +++ b/src/components/results/ShareButtons.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useEffect, useState } from 'react'; import styled from 'styled-components'; @@ -7,17 +7,42 @@ import KakaoButton from '@/components/common/share/KakaoButton'; import LinkButton from '@/components/common/share/LinkButton'; import NativeShareButton from '@/components/common/share/NativeShareButton'; import TwitterButton from '@/components/common/share/TwitterButton'; +import { convertDataUrlToFile } from '@/components/results/ConvertImage'; import { sharedMessage } from '@/constant'; -const ShareButtons = () => { +interface ShareButtonsProps { + imgData: string; +} + +const ShareButtons = ({ imgData }: ShareButtonsProps) => { + const [NativeShareFile, setNativeShareFile] = useState(null); const { title = '', url = '' } = sharedMessage; + + useEffect(() => { + try { + (async () => { + const file = await convertDataUrlToFile(imgData); + if (file && navigator.share !== undefined) { + setNativeShareFile(file); + } + })(); + } catch (e) { + console.error('convertDataUrlToFile error', e); + } + }, [imgData]); + return ( - - - - - + {NativeShareFile ? ( + + ) : ( + <> + + + + + + )} ); };