欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

原生js实现打印部分页面

程序员文章站 2022-03-04 15:53:57
...

打印部分页面

实现思路

    最近遇到需要页面表格打印的场景,用了市面上各插件后感觉不太理想。要么样
式出现问题,要么只能自己定义样式,于是想着自己写个专门的打印库。
    通过window.getComputedStyle计算出元素真实的样式,然后构建样式表,
生成新的iframe然后打印。因为浏览器自带的window.print()只能打印整个页面,
所以必须把要打印的内容和样式都塞进iframe实现。
github链接: [link](https://github.com/mindhacker0/web-print).

步骤和代码摘要

    首先要解析需要打印部分的dom,生成dom树并计算元素的样式,然后构造iframe框,把样式和dom分开渲染出来。然而还有些问题,就是元素的属性没考虑,比如说input的type,还有伪元素,表单的处理,canvas转为图片的处理,将在下次更新解决。

import styleEnum from "./styleEnum";//那些样式需要计算
function GUUID(prefix){//产生GUUID(一个随机字符串),作为类名
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4";
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
    s[8] = s[13] = s[18] = s[23] = "-";
    var uuid = s.join("");
    return prefix+uuid;
}
const standards = {//文档类型
    strict: 'strict',
    loose: 'loose',
    html5: 'html5'
};
class DomNode{//dom 节点类
    constructor(element){
        if(!(element instanceof HTMLElement)) return new Error(element+"is not a HTMLElement");
        this.style = this.getStaticStyle(element);
        this.className = GUUID("print-");
        this.nodeType = element.nodeType;
        this.nodeName = element.nodeName.toLowerCase();
        this.childNodes = [];
    }
    getStaticStyle(element){//计算元素的样式,存储起来以生成样式列表。
       const allStyles =  window.getComputedStyle(element);
       const style = new Map();
       for(var i of styleEnum){
            if(typeof allStyles[i]!=="undefined" && allStyles[i]!==''){
                style.set(i.replace(/[A-Z]/g,function(value){ return "-"+value.toLocaleLowerCase()}),allStyles[i]);
            }
       }
       return style;
    }
}
class textNode{//文本节点
    constructor(element){
        this.content = element.textContent;
    }
}
class Print{
    constructor(configs){
        const {el,title} = configs;
        this.printDom = el instanceof HTMLElement?el:document.querySelector(el);
        this.domTree = null;
        this.title = title||"打印标题";
        this.frame = null;
        this.standard = standards.html5;
        this.styleSheets = new Map();
        this.makeCloneDomWithStyle();
        this.docType = this.getDocType();
        this.head = this.getHead();
        this.body = this.getBody();
        this.makeFrame();
    }
    makeCloneDomWithStyle(){//拷贝所有需要克隆的元素的样式生并成dom树
        const vm =this;
        function copyELements(parent){//遍历dom树,解析它们的当前样式。
            if(parent instanceof Element){//元素节点
                const dom = new DomNode(parent);
                vm.styleSheets.set(dom.className,dom.style);
                let children = parent.childNodes;
                if(typeof children === "object" && children.length){
                    for(var i = 0;i<children.length;i++){
                        dom.childNodes.push(copyELements(children[i]));
                    }
                }
                return dom;
            }else if(parent instanceof Text){//文本节点
               return new textNode(parent);
            }
            //console.log(parent.__proto__,parent);
            //包含注释和其他的字符被过滤掉,如果需要后面可以处理
            return parent;
        }
        this.domTree = copyELements(this.printDom);
    }
    makeFrame(){//构造iframe,使我们构造的页面放进去调用window.print()打印。
        let iframe = document.createElement('iframe');
        iframe.style.border = '0px';
        iframe.style.position = 'absolute';
        iframe.style.width = '0px';
        iframe.style.height = '0px';
        iframe.style.right = '0px';
        iframe.style.top = '0px';
        iframe.setAttribute('id', "printjs-iframe");
        iframe.setAttribute('src',new Date().getTime());
        document.body.append(iframe);
        iframe.onload = ()=>{
            iframe.contentDocument.write(`${this.docType}<html>${this.head}<body></body></html>`);
            iframe.contentDocument.body.append(this.body);
            iframe.contentWindow.print();
            setTimeout(()=>{
                document.body.removeChild(iframe);
            });
        }
        this.frame = iframe;
    }
    getDocType(){//构造文档声明,如何解析文档
        if (this.standard === standards.html5) {
            return '<!DOCTYPE html>';
        }
        var transitional = this.standard === standards.loose ? ' Transitional' : '';
        var dtd = this.standard === standards.loose ? 'loose' : 'strict';
        return `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01${transitional}//EN" "http://www.w3.org/TR/html4/${dtd}.dtd">`;
    }
    getHead() {//构造头部,样式也将构造存放在这儿
        let links = '';
        let style = '';
        // 复制所有link标签
        [].forEach.call(document.querySelectorAll('link'), function(item) {
          if(item.href.indexOf('.css') >= 0){
            links += `<link type="text/css" rel="stylesheet" href="${item.href}" >`;
          }
        });
        this.styleSheets.forEach((val,key)=>{//解析收集的样式
            style+=`.${key}{`;
            val.forEach((value,name)=>{
                style+=`${name}:${value};`
            });
            style+=`}`;
        });
        return `<head><title>${this.title}</title>${links}<style type="text/css">${style}</style></head>`;
    }
    getBody(){//构造body,遍历之前创建的Dom树并添加样式,存放在fragment中
        let wrapper = document.createDocumentFragment();
        function renderElement(vNode,root){
            if(vNode instanceof DomNode){//还原元素节点
                let elem = document.createElement(vNode.nodeName);
                elem.classList.add(vNode.className);
                const children = vNode.childNodes;
                if(children.length){
                    for(var i = 0;i<children.length;i++){
                        renderElement(children[i],elem);
                    }
                }
                root.appendChild(elem);
            }else if(vNode instanceof textNode){//还原文本节点
                root.appendChild(document.createTextNode(vNode.content));
            }
        }
        renderElement(this.domTree,wrapper);
        return wrapper;
    }
    
}
export default Print;