Never give up

React - make html element to pdf(Feat. jspdf, html2canvas) 본문

WEB

React - make html element to pdf(Feat. jspdf, html2canvas)

대기만성 개발자 2022. 7. 2. 00:16
반응형

이번에는 특정 html element를 pdf로 만들어서 활용하는 예제를 만들어봤습니다

 

먼저 사용한 라이브러리는 2개로

 

jspdf : pdf를 만들 수 있는 라이브러리

(링크: https://www.npmjs.com/package/jspdf)

 

html2canvas : html element를 canvas에 그려 이미지 파일로 만들 수 있는 라이브러리

(링크: https://www.npmjs.com/package/html2canvas)

 

찾다보니 html2pdf란 친구(?)도 있는데 9년전 업데이트

 

그리고 낮은 다운로드수로 일단 사용해보지는 않았습니다. 혹시 필요하신 분들을 위해

(링크: https://www.npmjs.com/package/html2pdf)

 

최근에 사용중인 라이브러리인데 페이지가 여러장인 경우, 스타일을 잘(?) 처리하고 싶은 경우 사용하시면 좋을거 같습니다

(링크: https://www.npmjs.com/package/@react-pdf/renderer)

(상대적으로 무거운 라이브러리다보니 dynamic import를 통해서 번들 크기를 나누시면 좋습니다)

 

먼저 출력할 화면을 만들어줍니다

app.jsx

import React from 'react'
import makePdf from 'make_pdf'
import './app.css'

function App() {
    const onClick = async (e) => {
        e.preventDefault()
        await makePdf.viewWithPdf()
    }

    return (
        <div className='div_container'>
            <div className='div_paper'>
                <div>
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Eos officiis at iure sapiente maxime provident possimus dolorum eveniet illo nisi ullam, animi sunt nobis, error consequatur quos facere. Perspiciatis, harum.
                </div>
            </div>
            <button onClick={onClick}>pdf로 보기</button>
        </div>
    )
}

export default App

간단한 Lorem으로 출력할 부분을 만들어주고

 

pdf로 보기 버튼을 만들어줬습니다

 

스타일을 대충(?) 잡아줍니다

 

app.css

body{
    margin: 0;
}

.div_container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: 150vh;
    width: 100%;
    background-color: grey;
}

.div_container > button {
    position: fixed;
    bottom: 30px;
    right: 30px;
    border: none;
    border-radius: 8px;
    padding: 20px;
    box-shadow: 0 2px 2px #e4e4e4;
    background-color: white;
    cursor: pointer;
    transition: 1s;
}

.div_container > button:hover {
    background-color: black;
    color: white;
}

.div_paper {
    height: 842px;
    width: 595px;
    margin: 30px;
    background-color: white;
    text-align: center;
}

.div_paper > div {
    margin: 30px;
}

paper는 a4용지 사이즈에 맞게 사이즈를 맞춰봤습니다

(해상도는 제일 낮은거로..)

 

make_pdf.js

import html2canvas from "html2canvas";
import jsPDF from "jspdf";

const makePdf = {
    viewWithPdf: async () => {
        // html to imageFile
        const imageFile = await makePdf._converToImg()

        // imageFile to Pdf
        const pdf = makePdf._converToPdf(imageFile)

        // makePdf.sendToServer(pdf)
    },
    _converToImg: async () => {
        // html to imageFile
        const paper = document.querySelector(".div_container > .div_paper");

        const canvas = await html2canvas(paper);
        const imageFile = canvas.toDataURL("image/png", 1.0);

        return imageFile
    },
    _converToPdf: (imageFile) => {
        // imageFile to pdf

        const doc = new jsPDF("p", "mm", "a4");

        const pageWidth = doc.internal.pageSize.getWidth();
        const pageHeight = doc.internal.pageSize.getHeight();

        doc.addImage(imageFile, "JPEG", 0, 0, pageWidth, pageHeight);

        // doc.save("test.pdf")

        window.open(doc.output("bloburl"))

        const pdf = new File([doc.output("blob")], "test.pdf", {
            type: "application/pdf",
        });

        return pdf
    },
    // _sendToServer: async (pdf) => {
    //     const formData = new FormData();
    //     formData.append("file", pdf);
    //     formData.append("type", "pdf");
    //     formData.append("name", "test");

    //     const res = await axios.post("/pdf/upload_file", formData, {
    //         headers: {
    //             "Content-Type": "multipart/form-data",
    //         },
    //     });

    //     if (res.data.code === 1) {
    //         window.open(`${util.mode()}${res.data.link}`);
    //     }
    //     console.log({ res });

    //     setTimeout(() => {
    //         makePdf._isLoading = false;
    //     }, 2000);
    // }
}

export default makePdf

 

먼저 coverToImage부분을 보면 paper element를 cavas로 변환 후 image file로 만듭니다

 

이후 coverToPdf부분을 보면 doc의 사이즈를 정해주고, 캔버스의 너비와 높이를 줍니다

(안주면 align이 안맞는 문제가 발생할 수 있습니다)

 

doc.save("파일 이름")을 사용하면 해당이름의 pdf파일이 저장되고

 

window.open(doc.output("bloburl"))을 사용하면 pdf파일 미리보기가 만들어지고

 

서버에 보낼때는 File형태로 만들어서 서버에 보내주면 됩니다

(혹시 필요할 분들을 위해 주석부분에 간단한 예제코드를 만들어봤습니다)

 

뭔가 조금더 js스럽게(?) 바꿔보면  

const makePdf = () => {
    const converToImg = async () => {
        // html to imageFile
        const paper = document.querySelector(".div_container > .div_paper");

        const canvas = await html2canvas(paper);
        const imageFile = canvas.toDataURL("image/png", 1.0);

        return imageFile
    }

    const converToPdf = (imageFile) => {
        // imageFile to pdf

        const doc = new jsPDF("p", "mm", "a4");

        const pageWidth = doc.internal.pageSize.getWidth();
        const pageHeight = doc.internal.pageSize.getHeight();

        doc.addImage(imageFile, "JPEG", 0, 0, pageWidth, pageHeight);

        // doc.save("test.pdf")

        window.open(doc.output("bloburl"))

        const pdf = new File([doc.output("blob")], "test.pdf", {
            type: "application/pdf",
        });

        return pdf
    }
    
    return {
        viewWithPdf: async () => {
            // html to imageFile
            const imageFile = await converToImg()
    
            // imageFile to Pdf
            const pdf = converToPdf(imageFile)
        }
    }
}

------------------------------------------------------------------
//콜하는 부분
const pdf = makePdf()

const onClick = async (e) => {
    e.preventDefault()
    await pdf.viewWithPdf()
}

이렇게 처리하면 될거 같습니다

 

결과화면을 보면

< 완성된 pdf미리보기 >

(전체 소스 코드 : https://github.com/devmemory/react_make_html_to_pdf)

 

추가로 해상도 문제로 인해 글자가 깔끔하지 않은 경우

 

html2canvas에서 scale을 더 주면 됩니다

const canvas = await html2canvas(paper, { scale: 1.5 });

용량 문제가 있는경우

 

compress옵션을 이용하시면 됩니다

// 마지막 인자가 compressPDF
const doc = new jsPDF("p", "mm", "a4", true);

// 마지막 인자가 compression, undefined로 선언한 부분을 공백('')으로 넣으면 여러 페이지 적용시 문제 발생
doc.addImage(e, "JPEG", 0, 0, pageWidth, pageHeight, undefined, 'FAST');

 

최근에 체력적인 문제로 포스트를 못했는데

 

작업한건 많은데 정리를 안했더니 뭔가 아쉽기도 하고

 

나중에 필요할 때 빠른 리마인딩을 하기 위해

 

다시 초심잡고 하나둘씩 포스트 해나가려고 합니다

반응형
Comments