手写JS打印(导出pdf)插件

2,170 阅读2分钟

起因

Vue项目中,有一个打印(导出pdf)需求,于是网上搜寻了几个插件包括vue插件js插件,要么太重不够轻量级,要么功能不够完善,最终没有一款插件符合要求。
经过搜寻插件和资料的过程,发现打印原理并不难,于是尝试自己写一个插件,效果非常棒,而且非常轻量级。

效果图

下面是实际项目中的部分打印截图:

123.jpg

使用

使用过程中,只需要传入要打印的dom元素即可,vue中使用ref,需要注意的是要打印的元素css样式不依赖于其他元素,必须是可以独立的展示的,比如,下面实例中resume-container元素不能依赖box,否则会造成打印时样式部分丢失的情况。
示例如下:

<template>
  <div class="box">
    <div class="resume-container" ref="resume">
      
    </div>
  </div>
</template>

<script>
// 引入打印插件
import printHtml from "@/utils/print.js"

export default {
  methods: {
    // 创建打印方法
    print() {
      // 传入要打印的dom
      printHtml(this.$refs.resume, () => {
        location.reload();
      })
    },
  },
}
</script>

<style scoped rel="stylesheet/scss" lang="scss">
/* 设置打印格式 */
@page {
  margin: 0; /* this affects the margin in the printer settings */
  size: A4;
}
/* 设置打印样式 */
@media print {
  .resume-container {
    font-size: 1.473vh !important;
  }
}
.box {
  width: 100vw;
}
/* 要打印的dom元素为最外层元素,样式不依赖于其他元素 */
.resume-container {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  font-size: 1.35vh;
  font-weight: 300;
  letter-spacing: 0.1vw;
  -webkit-print-color-adjust:exact;
  -moz-print-color-adjust:exact;
  -ms-print-color-adjust:exact;
}
</style>

源代码

const VueHtmlToPaper = function(dom, cb) {
  if (!(this instanceof VueHtmlToPaper)) return new VueHtmlToPaper(dom, cb);
  if (!cb) cb = function() { return true; };
  if ((typeof dom) === "string") {
    this.dom = document.querySelector(dom);
  } else {
    this.isDOM(dom)
    this.dom = this.isDOM(dom) ? dom : dom.$el;
  }

  const styles = this.getStyle();
  const content = this.getHtml();
  console.log(styles, content)
  const defaultName = '_blank',
      defaultSpecs = ['fullscreen=yes','titlebar=yes', 'scrollbars=yes'],
      defaultReplace = true;
  const url = '';
  const win = window.open(url, defaultName, defaultSpecs, defaultReplace);

  win.document.write(`
    <html>
      <head>
        <title>${document.title}</title>
        ${styles}
      </head>
      <body>
        ${content}
      </body>
    </html>
  `);

  setTimeout(() => {
    win.document.close();
    win.focus();
    win.print();
    win.close();
    cb();
  }, 1000);

  return true;
}

VueHtmlToPaper.prototype = {
  calenderFlag: false,
  getStyle: function () {
    var str = "",
      styles = document.querySelectorAll('style, link');
    if (document.getElementById('calender-task-flag')) {
      this.calenderFlag = true;
    }
    if (this.calenderFlag) {
      const c_height = window.innerHeight;
      const c_width = window.innerWidth;
      const boxes = document.getElementsByClassName('resize-box');
      for (let i = 0; i < boxes.length; i++) {
        const box = boxes[i];
        const width = this.getAttr(box, 'width');
        const height = this.getAttr(box, 'height');
        const top = this.getAttr(box, 'top');
        const left = this.getAttr(box, 'left');
        //const widthScale = 1;
        const widthScale = 0.8;

        //parent.style.width = this.pxStrToNumber(width) / widthScale / c_width * 100 + '%';
        box.style.width = this.pxStrToNumber(width) / widthScale / c_width * 100 + '%';
        //parent.style.height = this.pxStrToNumber(height) / c_height * 100 + '%';
        box.style.height = this.pxStrToNumber(height) / c_height * 100 + 'vh';

        box.style.left = this.pxStrToNumber(left) / widthScale / c_width * 100+ '%';
        box.style.top = this.pxStrToNumber(top) / c_height * 100 + 'vh';
        box.style.border = '0.5px solid #909399';
        box.style.color = 'white';
        this.prefixStyle('transform');
        document.getElementById('printMe');
        //container.style[transform] = 'scale(0.5) translate(-50%, -50%)';
        // container.style.height = '100vh';
        // container.style.overflow = 'hidden';
      }
    }
    for (var i = 0; i < styles.length; i++) {
      str += styles[i].outerHTML;
    }

    str += "<style>" + '.no-print' + "{display:none;}</style>";
    str += "<style> body { margin: 0; padding: 0 } </style>";

    return str;
  },
  getHtml: function () {
    var inputs = document.querySelectorAll('input');
    var textareas = document.querySelectorAll('textarea');
    var selects = document.querySelectorAll('select');

    for (var k = 0; k < inputs.length; k++) {
      if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
        if (inputs[k].checked == true) {
          inputs[k].setAttribute('checked', "checked")
        } else {
          inputs[k].removeAttribute('checked')
        }
      } else if (inputs[k].type == "text") {
        inputs[k].setAttribute('value', inputs[k].value)
      } else {
        inputs[k].setAttribute('value', inputs[k].value)
      }
    }

    for (var k2 = 0; k2 < textareas.length; k2++) {
      if (textareas[k2].type == 'textarea') {
        textareas[k2].innerHTML = textareas[k2].value
      }
    }

    for (var k3 = 0; k3 < selects.length; k3++) {
      if (selects[k3].type == 'select-one') {
        var child = selects[k3].children;
        for (var i in child) {
          if (child[i].tagName == 'OPTION') {
            if (child[i].selected == true) {
              child[i].setAttribute('selected', "selected")
            } else {
              child[i].removeAttribute('selected')
            }
          }
        }
      }
    }
    return this.dom.outerHTML;
  },
  isDOM: (typeof HTMLElement === 'object') ?
    function (obj) {
      return obj instanceof HTMLElement;
    } :
    function (obj) {
      return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
    },
  getAttr: function(element, attr) {
    return window.getComputedStyle ? window.getComputedStyle(element, )[attr] : element.currentStyle[attr];
  },
  pxStrToNumber: function(pxStr) {
    return Number(pxStr.slice(0, pxStr.length - 2));
  },

  vender: (function() {
    let elementStyle = document.createElement('div').style;
    let transformNames = {
      webkit: 'webkitTransform',
      Moz: 'MozTransform',
      O: 'OTransform',
      ms: 'msTransform',
      standard: 'transform',
    };

    for(let key of Object.keys(transformNames)) {
      if (elementStyle[transformNames[key]] !== undefined) return key;
    }

    return false;
  })(),
  prefixStyle: function(style) {
    if (this.vender === false) return false;
    if (this.vender === 'standard') return style;
    return this.vender + style.charAt(0).toUpperCase() + style.substr(1);
  }
}


export default VueHtmlToPaper;

最后

此插件虽然是在Vue中开发,但是是纯js实现,适用于任何前端环境,希望给大家提供一个思路。