html 화면 PDF 저장 (jspdf / html2canvas.js)

양찌·2022년 6월 6일
0

Vue

목록 보기
1/4
post-thumbnail

신입 프로젝트를 하는 중에 Custom Support Report를 pdf로 저장하는 구현이 있었다. 이 문제 해결 과정을 기록해본다.

html 화면을 pdf로 저장하는 것을 생각보다 그렇게 어렵지는 않았다. 많은 블로그를 참고하여 내용을 취합해보면 모두 비슷한 방법으로 pdf 파일 저장 로직을 구현하여서, 나도 그 방법으로 해보니 잘 되었다.

다만, 상용화할 홈페이지에서 사용할 때는 더 세부적인 코드를 구현할 필요가 있을 것 같다. 여러 페이지가 나오는 경우 상하좌우 여백을 준다든지, 머리글/꼬리글에 고정된 디자인을 넣는 등의 커스텀 디자인 시에는 세부적인 구현을 추가할 필요가 있을 것 같다.



내가 구현한 내용은 다음과 같다.

  • 지정한 영역을 이미지로 저장(html2canvas) 후, PDF파일로 저장(jspdf)
  • textarea 태그가 있을 경우
    - 태그 안에 글자가 엔터 없이 inline 형태로 나오는 오류 해결



npm package 설치

html 화면을 PDF 파일로 저장하기 위해서는 우선 필요한 npm 패키지를 설치해야 한다.

  • jspdf

    npm i jspdf

  • html2canvas

    npm i html2canvas



코드 작성

나는 Vue.js 라이브러리를 사용하였다.
Vue.js 든 React 든지 javascript 기반 라이브러리임으로
사용법은 거의 비슷하다고 볼 수 있겠다.

<template>
  <nav>..</nav>
  <div>..</div>
  <button v-on:click="generatePdf(this.year, this.month)"></button>
  <div id="pdfDiv">
     // ... PDF로 변환할 부분
  </div>
</template>

<script>
import { jsPDF } from "jspdf";
import html2canvas from "html2canvas";
 //...생략
data() {
  return {
    year: '2022',
    month: '05',
  }
},
 methods: {
    generatePdf(year, month) {
      
      // 프린트 하고자 하는 dom 영역을 가져옴
      const pdfDiv = document.getElementById("pdfDiv");
	
      html2canvas(pdfDiv).then(function (canvas) {
        
        // 캔버스를 이미지로 변환
        let imgData = canvas.toDataURL("image/png");
        const imgWidth = 190;
        const pageHeight = imgWidth * 1.414;
        const imgHeight = (canvas.height * imgWidth) / canvas.width;
        let heightLeft = imgHeight;
        const margin = 10;
        const doc = new jsPDF("p", "mm");
        let position = 0;

        // 첫 페이지 출력
        doc.addImage(imgData, "PNG", margin, position, imgWidth, imgHeight);
        heightLeft -= pageHeight;

        // 한 페이지 이상일 경우 루프 돌면서 출력
        while (heightLeft >= 20) {
          position = heightLeft - imgHeight - 28;
          doc.addPage();
          doc.addImage(imgData, "PNG", margin, position, imgWidth, imgHeight);
          heightLeft -= pageHeight;
        }

        // 파일 저장
        // save 안에 들어가는 인자는 저장시 파일명이 됨
        doc.save(`CSS Report_${year}${month}월.pdf`);
      });
    },
}
</script>



textarea 태그 사용 시 문제

나의 코드에서 div#pdfDiv 안에는
textarea 태그가 있었다.

textarea 태그 안에 여러 줄의 내용이 있을 경우 jspdf와 html2canvas pdf 파일 생성 시 엔터 없이 한 줄로 내용이 나오는 문제가 있었다.

web 화면

생성된 pdf 파일 화면

  • 문제가 되는 화면




참고한 html2canvas 깃허브에서 나와 같은 문제를 해결하고자 여러 사람들이 의견을 나누는데 다음과 같은 해결책이 있었다.

해결

textarea 안에 형제 엘리먼트 div를 생성하고, 그 안에 textarea 내용을 넣는 것이다. 그리고 문제가 되는 textarea는 display를 none으로 하는 것이다.

아까 generatePdf 메소드 안에 코드를 추가한다.

  methods: {
    generatePdf(year, month) {

      const pdfDiv = document.getElementById("pdfDiv");
	
      // pdf 파일로 생성하고하 하는 영역에
      // textarea 태그가 있는 경우
      pdfDiv.querySelectorAll("textarea").forEach((textArea) => {
        // textarea 형제 태그를 생성하여 
        // 그 안에 내용이 들어가게 한다
        const div = document.createElement("div");
        // remove 할 때를 대비하여 class도 추가
        div.classList.add("textAreaDiv");
        div.innerText = textArea.value;
        div.style.padding = "20px";
        div.style.backgroundColor = "#ffffff";
        textArea.style.display = "none";
        textArea.parentElement.append(div);
      });

      html2canvas(pdfDiv).then(function (canvas) {
		// 여기 내용은 위 "코드 작성" 코드와 동일
      });
	  
      // 위 코드에서 pdf 파일 저장이 완료된 후
      // 생성된 div.textAreaDiv 태그들을 remove해준다
      // 🎈 클릭 버튼을 다시 눌러을 때 반복해서 태그가 생기지 않도록 하기 위함
      pdfDiv.querySelectorAll("textarea").forEach((textArea) => {
        textArea.style.display = "block";
        const textAreaDiv = document.querySelector(".textAreaDiv");
        textAreaDiv.remove();
      });
    },
  },

주의

주의해야 할 점은 textarea 형제 태그로 생성한 div를 pdf 파일이 만들어지고 난 후, remove()를 해주어야 한다. 그래야 pdf 다운로드 버튼 클릭시 계속적으로 동일한 내용의 div가 재생성 되지 않기 때문이다.

(위 "해결" 코드에서 🎈 이 부분 밑에 코드 구현)









Reference

profile
신입 개발자 입니다! 혹시 제 글에서 수정이 필요하거나, 개선될 부분이 있으면 자유롭게 댓글 달아주세요😊

0개의 댓글