성장과정(dev)/Frontend(feat. Vue, Next.js)

[html to pdf] vue에서 html을 pdf로 다운로드 하기 (심플버전), pdf image not showing 문제해결

lowellSunny 2021. 2. 23. 19:45

오늘은 목표 html to pdf in vuejs

--> pdf 다운로드 버튼 클릭 시 html에 출력되어 있는 내용을 그대로 pdf에서 출력하기

 

총 3단계의 시행착오를 걸쳐 완성했다. 3단계 전부 있으니 급하다면 마지막코드로..

 

"vue html to pdf" 라고 구글에 검색하면 가장 먼저 나오고 사용량이 가장 많은 vue-html2pdf plugin을 사용하려다가 몇시간의 시도 끝에 실패했다. demo의 component 구조가 약간 복잡하다. 뭐 쉽다면 쉽지만 단시간에 단순구조로 pdf로 다운로드 할 수 있도록 하는 것이 목표였다.

 

원래 vue-html2pdf도 html2canvas와 jsPDF 기반으로 만들어져있어 vue-html2pdf 말고 본래의 html2pdf을 사용하기로 결정했다.

※ 참고답변

stackoverflow.com/questions/60204249/impossible-to-convert-html-code-to-pdf-with-vue-html2pdf

 

 plugin site

https://www.npmjs.com/package/html2pdf.js/v/0.9.1

 

 demo site

https://codesandbox.io/s/xlnzq7y28q

 

 

 

본격적으로 시작 ! 사실 위에 demo와 똑같이 적용했다. 옵션만 살짝 바꿨을 뿐.

여기서 포인트는 window.scrollTo(0,0); 이다.

사실 같은 예제를 가지고 어제부터 헤맸는데 이유가 scroll 때문이었다. pdf download button이 아래쪽에 있으니 스크롤이 맨 아래에 가있는 상태에서 download를 하고, pdf의 이미지캡처가 해당스크롤을 기준으로 출력하므로 앞에 몇장의 page가 빈 상태로 나오는 문제가 있었다. 그래서 계속 다른 라이브러리를 찾다가 돌고돌아 문제를 찾았다. 해당 pdf plugin은 화면을 이미지로 변환 후에 다시 pdf로 변환하기 때문에 페이지가 여러개로 나타나는 경우에는 scroll이 맨 위에 올라가있는 상태에서 makePDF 로직을 수행해야한다.

=== 추가수정 ===

window.scrollTo를 굳이 할 필요가 없다. html2canvas안에 scrollY 옵션이 있다. 해당 옵션을 0으로 맞춰주면 pdf는 알아서 세로 scroll위치 0부터 pdf를 예쁘게 출력한다.

 

적용 1단계) in vue component javascript

<script>
	import html2pdf from 'html2pdf.js'
	
	export default {
    name: 'submission-detail',
    methods: {
      exportToPDF () {

          //window.scrollTo(0, 0);
          html2pdf(this.$refs.pdfarea, {
              margin: 0,
              filename: 'document.pdf',
              image: {type:"jpg", quality: 0.95},
              html2canvas: { scrollY: 0,scale:1, dpi: 300, letterRendering: true,
              		ignoreElements : function( element  ) {	//pdf에 출력하지 않아야할 dom이 있다면 해당 옵션 사용
                        if( element.id == "pdf-button-area" ) {
                            return true;
                        }
                    }
              },
              jsPDF: {orientation: 'portrait', unit: 'mm', format: 'a4', compressPDF: true}
          })
      },
      
      ...
      
    }
  }
</script>

in vue component html

<template>

	<div>
      <div ref="pdfarea">

            //기존 html code (화면에 출력되어야하는 pdf html tags)

      </div>


      <div class="col-md-8 text-left" style="float:none;margin:auto;" v-show="!loading">
          <b-button class="mb-5 float-right" @click="exportToPDF">PDF 다운로드</b-button>
      </div>
	</div>
</template>

 

이렇게 하고나니 pdf는 제대로 출력이 되는데 문제는 image가 안나온다. 프로젝트 내에 있는 image는 괜찮은데 url로 이미지를 가져오는 부분에서 정상적으로 가져오지를 않는다.

이 때도 html2canvas의 옵션에서 cross-origin의 이미지도 가져오도록 true로 설정해주는 옵션이 있다.

옵션명 : allowTaint

이 옵션을 true로 변경해주면 error가 발생한다. 에러 내용은 아래와 같다.

html2pdf.js?d67e:332 Uncaught (in promise) DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

Uncaught (in promise) DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

 

 

그래서 생각한 방법 ! 이미지를 blob으로 파일화 시켜서 src에 로드하기

fetch(url)
  .then(response => response.blob())
  .then(blob => {
  //	여기서 this가 가리키는건 현재 VueComponent이다.
  this.testImage = URL.createObjectURL(blob);
  })
  
  
  //위에서 blob으로 image 객체를 생성했으니 아래 태그에 이미지가 정상적으로 나타난다
  
  
  <b-img  :src="testImage"></b-img>

 

 

수정 후 전체소스코드

<template>
	<div ref="pdfarea">
    	//pdf 내용
        ...
        
        <b-img :src="testIamge"></b-img>
        ...
        
        <b-button class="mb-5 float-right" @click="exportToPDF">PDF 다운로드</b-button>
    </div>
</template>

<script>
	import html2pdf from 'html2pdf.js'
	
	export default {
    name: 'submission-detail',
    methods: {
      exportToPDF () {

          //window.scrollTo(0, 0);
          html2pdf(this.$refs.pdfarea, {
              margin: 0,
              filename: 'document.pdf',
              image: {type:"jpg", quality: 0.95},
              //	allowTaint 옵션추가
              html2canvas: { scrollY: 0,scale:1, dpi: 300, letterRendering: true, allowTaint: true },
              jsPDF: {orientation: 'portrait', unit: 'mm', format: 'a4', compressPDF: true}
          })
      },
      
      mounted() {
      
      		//data를 가져오는 api 호출
            //가져온 data 중 image url 데이터를 blob파일로 변경하여 vue data에 넣어줌
            
            
            
            let imageUrl = data.imageUrl;
            fetch( imageUrl )
              .then(response => response.blob())
              .then(blob => {
                  console.log( "객체는? " )
                  //this.images.selfie = URL.createObjectURL(blob);
                  this.testImage = URL.createObjectURL(blob);
              })
            
            
      },
      ...
      
    }
  }
</script>

 

 

수정 후 최종 전체소스코드

이번에도 더 간단한 방법을 찾았다.. image를 blob file로 만들지 않아도 되고, 로직 다 제외한 후 allowTaint: true 옵션을 html2canvas 옵션에 추가한다.

 

<template>
	<div ref="pdfarea">
    	//pdf 내용
        ...
        
        <b-img :src="이미지url"></b-img>
        ...
        
        <b-button class="mb-5 float-right" @click="exportToPDF">PDF 다운로드</b-button>
    </div>
</template>

<script>
	import html2pdf from 'html2pdf.js'
	
	export default {
    name: 'submission-detail',
    methods: {
      exportToPDF () {

          //window.scrollTo(0, 0);
          html2pdf(this.$refs.pdfarea, {
              margin: 0,
              filename: 'document.pdf',
              image: {type:"jpg", quality: 0.95},
              //	allowTaint 옵션추가
              html2canvas: {
                useCORS: true,
                scrollY: 0,
                scale:1,
                dpi: 300,
                letterRendering: true,
                allowTaint: false //useCORS를 true로 설정 시 반드시 allowTaint를 false처리 해주어야함
               },
              jsPDF: {orientation: 'portrait', unit: 'mm', format: 'a4', compressPDF: true}
          })
      },
      
      mounted() {
              
            
      },
      ...
      
    }
  }
</script>

 

 

# html to pdf empty page

# html to pdf 빈화면

# html to pdf 빈페이지

# html to pdf scroll