之前遇到一个业务问题,就是先将信息生成一个简单的二维码,然后打印出包括二维码在内的部分页面内容(这个二维码旁边有二维码信息,让人识别,二维码交给扫码枪识别)。
1.二维码的生成
二维码生成器网上一找一大堆,可以直接看一下API然后调用就好了。
网上可以直接下载jquery.qrcode.min.js的插件,下载完就好了
使用:
//声明一个QRCode的实例,传入两个参数,第一个参数是装载这个二维码的元素可以直接是一个div
//例如<div id="qrcode"></div>
//第二个参数是一个对象,声明生成的二维码的大小
var qrcode = new QRCode(document.getElementById("qrcode"), {
width : 100,
height : 100
});
function makeQRCode (paraValue) {
var qrcodeText = encodeURIComponent(paraValue);//进行编码
//下面这几行可以自行选择对信息进行处理,不是必须的
var qrCodeArr = qrcodeText.split('_');//分割编码
var text = qrCodeArr[0].substring(0,6)+'<br/>'+ qrCodeArr[1].substring(0,6) + '<br/>' + qrCodeArr[2].substring(0,6);//值获取每段编码的前六位
$('.qrtxt').html(text);//将上面分割后的字符信息放到打印的二维码盒子旁边
qrcode.makeCode(qrcodeText);//生成二维码,必须
}
const valueText = "123456_$666345_欧文我们";
makeQRCode(valueText);//调用打印二维码,一般点击按钮触发
再生成二维码之前最好的做法是对编码的信息进行处理 encodeURIComponent(paraValue),因为信息中可能包含一些中文或其他qrcode插件不能处理的字符(当时我遇到了这个问题,没有注意到,排查浪费了很多时间)。encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。其中的某些字符将被十六进制的转义序列进行替换。
说明:该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。
提示:请注意 encodeURIComponent() 函数 与 encodeURI() 函数的区别之处,前者假定它的参数是 URI 的一部分(比如协议、主机名、路径或查询字符串)。因此 encodeURIComponent() 函数将转义用于分隔 URI 各个部分的标点符号。
2.打印页面中的一部分(比如刚刚生成的二维码及附带信息)
二维码生成搞定了,像这种二维码我们一般有这种尺寸打印机,打印小尺寸的二维码贴在产品上。目前就需要打印出来也买那种的这一部分信息。
一般来说我们打印整个页面只需要调用window.print()即可弹出浏览器的打印窗口,但是如果只打印部分就需要单独处理。搜寻解决办法的时候发现有人说这种方法:
function doPrint() {
bdhtml=window.document.body.innerHTML;
sprnstr="<!--startprint-->"; //开始打印标识字符串有17个字符
eprnstr="<!--endprint-->"; //结束打印标识字符串
prnhtml=bdhtml.substr(bdhtml.indexOf(sprnstr)+17); //从开始打印标识之后的内容
prnhtml=prnhtml.substring(0,prnhtml.indexOf(eprnstr)); //截取开始标识和结束标识之间的内容
window.document.body.innerHTML=prnhtml; //把需要打印的指定内容赋给body.innerHTML
window.print(); //调用浏览器的打印功能打印指定区域
window.document.body.innerHTML=bdhtml; // 最后还原页面
}
这种方法打印完成之后你会发现页面确实还原了,思想是先把要打印的部分克隆出来,再把整个页面的内容保存起来,接着把要打印的内容塞进body中,这是body中就只有打印的内容了可以直接调用window.print()方法打印,最后再把之前保留的整个页面的内容再还给body。
但是这样做是有问题的,就是你发现的事件都不能触发了,就如果动态添加的元素需要使用事件委托的方式来解决事件触发一样。所以不推荐这种方式。
最终解决方案:在当前页面中内嵌一个iframe框架来放置打印的内容,打印的时候调用iframe里面的打印方法就可以了,不会影响到当前的文档流。
先上代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!--......页面中的各种各样的其他内容 -->
<!--这段代码是我们要指定打印的页面中的一部分内容,在上面第一部qrcode也是这个内容-->
<div id="qrCodeBox" class="qrCodeBox">
<div id="qrcode" class="qrCode"></div>
<p class="qrtxt">ZL06<br/>ZJB60-110<br/>TF-L1</p>
</div>
<!-- 用于打印目的开启的另一个隐藏窗口,这样打印完页面不会失效 -->
<iframe style="position:fixed;left:0;top:0;z-index:-1;" id="printf" src="" width="0" height="0" frameborder="0"></iframe>
<script>
function printPartContent(){
//获取要打印的内容,整个盒子
var printHtml = document.getElementById('qrCodeBox').innerHTML;
//获取到我们嵌入body中的iframe框架
var frame = document.getElementById('printf');
//往内嵌的iframe文档中放入要打印的部分的样式,这里也可以直接是'<style type="text">...</style>'的样式形式,也可以直接引入一个样式文件
//当然这里可以使用frame.contentDocument.head.innerHTML=....。或者创建使用document.createElement方法创建link元素,再...head.apendChild(link)将样式添加到head中
frame.contentDocument.write('<link rel="stylesheet" type="text/css" href="css/print.css">');
//往内嵌的iframe文档中放入要打印的内容部分
frame.contentDocument.write(printHtml);
//关闭iframe文档窗口,这里手动关闭了之前调用write方法默认调用open()方法创建的文档,后面如果,再使用.write()方法则会再次调用open()方法创建新文档,并且会覆盖之前的内容
frame.contentDocument.close();
//调用iframe的打印窗口弹出浏览器打印窗口,定时器用于给iframe足够的渲染内容的时间
setTimeout(function(){
frame.contentWindow.print();
},100);
}
</script>
</body>
</html>
原理:使用一个iframe框架内嵌到页面中,并且不可见,不会对页面中原有的内容造成任何影响。然后将要打印的内容部分拷贝通过iframe.contentDocument.write()方法写入到iframe当中,包括打印的样式也可以写入进去。这就相当于你另外开了一个页面,页面当中含有你要打印的内容部分,这个内容在iframe中式独立存在的,任何这个页面中的操作不会影响到当前文档流。所以最后调用iframe.contentWindow.print()即可调出浏览器打印窗口。其中每个知识点都已经详细的写入到上面代码的注释当中了。
3.iframe基础知识回顾
代码铺设完成可以避免打印后原页面失效的问题,现在来补充一点关于iframe的基础知识:
通常我们使用iframe直接直接在页面嵌套iframe标签指定src就可以了
iframe常用属性:
1.frameborder:是否显示边框,1(yes),0(no)
2.height:框架作为一个普通元素的高度,建议在使用css设置。
3.width:框架作为一个普通元素的宽度,建议使用css设置。
4.name:框架的名称,window.frames[name]时专用的属性。
5.scrolling:框架的是否滚动。yes,no,auto。
6.src:内框架的地址,可以使页面地址,也可以是图片的地址。
7.srcdoc , 用来替代原来HTML body里面的内容。但是IE不支持, 不过也没什么卵用
8.sandbox: 对iframe进行一些列限制,IE10+支持
获取iframe里的内容
主要的两个API就是contentWindow,和contentDocument
iframe.contentWindow, 获取iframe的window对象
iframe.contentDocument, 获取iframe的document对象
这两个API只是DOM节点提供的方式(即getELement系列对象)
var iframe = document.getElementById("iframe");
var iwindow = iframe.contentWindow;
var idoc = iwindow.document;
console.log("window",iwindow);//获取iframe的window对象
console.log("document",idoc); //获取iframe的document
console.log("html",idoc.documentElement);//获取iframe的html
console.log("head",idoc.head); //获取head
console.log("body",idoc.body); //获取body
window.parent 获取上一级的window对象,如果还是iframe则是该iframe的window对象
window.top 获取最顶级容器的window对象,即,就是你打开页面的文档
window.self 返回自身window的引用。可以理解 window===window.self
4.document.write()方法浅析
另外有一点需要做一点说明的就是我们在往iframe里里面添加打印内容的时候看到了document.write()方法,有的同学可能会疑惑,document.write()不是会把文档内容清空吗?下面就来盘一下她吧
<!DOCTYPE html>
<html>
<head>
<meta charset=" utf-8">
<title>Document</title>
</head>
<body>
<div>.........我是原来的内容</div>
<script type="text/javascript">
window.onload=function(){
document.write("66666");
document.write("7777");
document.write("8888");//也可以直接document.write("666","777","888");
}
</script>
</body>
</html>
你会发现结果中只有6666677778888而之前的body中div元素中的内容全都被覆盖了,可以看出document.write()先将原来的文档内容清空了,在往文档中添加write()中的内容。
介绍一下造成的原因:
window.onload事件是在文档内容完全加载完毕再去执行事件处理函数,这个时候文档流已经关闭了,这时执行document.write()函数会自动调用document.open()函数创建一个新的文档流,并写入新的内容,再通过浏览器展现,这样就覆盖了原来的内容。但是问题又来了下面这种情况又不会被覆盖:
<!DOCTYPE html>
<html>
<head>
<meta charset=" utf-8">
<title>Document</title>
</head>
<body>
<div>.........我是原来的内容</div>
<script type="text/javascript">
document.write("66666");
document.write("7777");
document.write("8888");//也可以直接document.write("666","777","888");
</script>
</body>
</html>
上面这种情况文档中原来的内容就不会被清空,这是因为当前文档流是有浏览器所创建,并且document.write()函数身处其中,也就是执行此函数的时候当前文档流并没有被关闭,这个时候不会调用document.open()创建新的文档流,所以也就不会覆盖文档中的原来内容。
讨论一下open()和close:
<!DOCTYPE html>
<html>
<head>
<meta charset=" utf-8">
<title>Document</title>
</head>
<body>
<div>.........我是原来的内容</div>
<script type="text/javascript">
document.write("66666");
document.close();
document.write("7777");
document.write("8888");//也可以直接document.write("666","777","888");
</script>
</body>
</html>
上面使用document.close()已经关闭文档流了,为什么还是不能覆盖原来的内容,很遗憾,当前文档流是由浏览器创建,无权手动关闭,document.close()只能关闭有document.open()函数创建的文档流(包括使用document.write隐式调用);
<!DOCTYPE html>
<html>
<head>
<meta charset=" utf-8">
<title>Document</title>
<script type="text/javascript">
function create(){
var newWindow=window.open("","Document","_blank");
newWindow.document.write("Hello JavaScript");
newWindow.document.close();
newWindow.document.write("覆盖后的输出");
}
window.onload=function(){
var obt=document.getElementById("bt");
obt.onclick=function(){
create();
}
}
</script>
</head>
<body>
<div id="print">Hello JavaScript</div>
<input type="button" id="bt" value="查看效果"/>
</body>
</html>
上面由document.open()创建的文档流就可以由document.close()关闭,那个第二个document.write()输出的内容会覆盖第一个输出的内容。