ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [html to pdf] vue에서 html을 pdf로 다운로드 하기 (심플버전), pdf image not showing 문제해결
    개발자의 공부는 은퇴까지 필수다/vue 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

Designed by Tistory.