在上一篇的基础上,实现了一下另外一种方式。

上一篇地址:https://www.cnblogs.com/ljwsyt/p/9525290.html

首先,该方式也是有几种方法。

1.在上一篇的基础上,将生成的html转化成canvas,然后就可以直接对canvas进行打印和保存。

需要注意的是,canvas打印的时候是一片空白的,需要先转化为图片然后打印。而生成canvas之后可以直接右键保存了,也可以增加按钮进行保存,保存的时候也是先转化为base64图片然后再进行保存。

方法:使用html2canvas插件进行转化,只需引入就可以直接运行,

html2canvas(document.querySelector("#toPrint")).then(canvas => {
document.body.appendChild(canvas)
});

其源码应该也是根据元素的位置绘制的canvas。

2.直接绘制canvas。

html代码:增加了两个按钮

 <div>
<div id="printArea">
<!--startprint-->
<canvas id="toPrint">
</canvas>
<!--endprint-->
</div>
<div id="bottom_btns">
<button onclick="printNotifier()" class="layui-btn">打印</button>
<button onclick="saveNotifier1()" class="layui-btn">保存</button>
</div>
</div>

css代码:

 #toPrint {
position:absolute;
left: 50%;
top: 50%;
} #bottom_btns {
position: absolute;
bottom: 10px;
left: 50%;
/* 按钮宽度加缩进 */
margin-left: -70px;
}

js代码:移动端兼容也很OK

 myApp.controller('notifierController2', function ($rootScope, $scope, services, $sce, $stateParams, $state) {
$scope.services = services; //查询录取通知书内容
services["getApplyStatus"] = function (param) {
return $rootScope.serverAction('/apply/queryDegreeApplyInfo', param, "GET");
}; $scope.mobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent);
if(1 == $rootScope._USERINFO.role || 2 == $rootScope._USERINFO.role) {
if($scope.mobile) {
$rootScope._ALLMENU = [{
children: [{
res_name: "查看审核状态",
res_url: "#status_mobile",
res_id: "#status_mobile"
},{
res_name: "查看修改申请资料",
res_url: "#registerMsg#review",
res_id: "#registerMsg#review"
},{
res_name: "打印录取通知书",
res_url: "#notifier",
res_id: "#notifier"
}]
}];
//$rootScope.mobile_regstatus = true;
} else {
$rootScope._ALLMENU = [{
children: [{
res_name: "查看审核状态",
res_url: "#status",
res_id: "#status"
},{
res_name: "查看修改申请资料",
res_url: "#registerMsg#review",
res_id: "#registerMsg#review"
},{
res_name: "打印录取通知书",
res_url: "#notifier",
res_id: "#notifier"
}]
}];
}
}
$rootScope.curentSel = "#notifier";
$rootScope.setContent = function(url) {
if($scope.mobile) {
$('#main-layout').removeClass('hide-side');
if(-1 < url.indexOf("#registerMsg")) {
window.open(encodeURI(encodeURI('/pages/index_mobile.html#/registers#review?id=' + $rootScope._USERINFO.id)));
window.location.href = "/pages/index_mobile.html#/home";
return;
} else {
$rootScope.curentSel = url;
}
} else {
if(-1 < url.indexOf("#registerMsg")) {
window.open(encodeURI(encodeURI('/pages/index.html#/registers#review?id=' + $rootScope._USERINFO.id)));
window.location.href = "/pages/index.html#/home";
return;
} else {
$rootScope.curentSel = url;
}
}
} //模板
$scope.printObj = {
notifierObj:{
"url": "/res/img/notifications.png",
"height": "631",
"width": "942"
},
paramList:[{
"objName":"黄大明",
"left":"133",
"top":"191",
"size": "28"
},{
"objName":"SXXX小学",
"left":"460",
"top":"272",
"size": "28"
},{
"objName":"2018",
"left":"195",
"top":"312",
"size": "28"
},{
"objName":"8",
"left":"325",
"top":"312",
"size": "28"
},{
"objName":"31",
"left":"405",
"top":"312",
"size": "28"
}]
} services.getApplyStatus('token').success(function(res) {
if ('OK' == res.result) {
if(res.msg) {
userName = res.msg.studentName;
$scope.printObj.paramList[0].objName = res.msg.studentName;
$scope.printObj.paramList[1].objName = res.msg.applySchoolName; //屏幕自适应
suitScreen($scope);
//画图
drawNotifier($scope);
//组装页面
//assembleHtml($scope);
//打印
//printNotifier();
}
} else {
layer.alert(res.msg);
}
}); }); var userName; function saveNotifier1() {
//一样需要先转化为图片后保存
var type = 'png';//格式可以自定义
var imgData = $("#toPrint")[0].toDataURL(type);
// 加工image data,替换mime type
imgData = imgData.replace(_fixType(type),'image/octet-stream');
//可以直接用以下下载,但是下载的文件名没有后缀
//window.location.href=image; // it will save locally
//文件名可以自定义
var filename = '录取通知书_' + userName + '.' + type;
saveFile(imgData,filename);
} function _fixType(type) {
//imgData是一串string,base64
type = type.toLowerCase().replace(/jpg/i, 'jpeg');
var r = type.match(/png|jpeg|bmp|gif/)[0];
return 'image/' + r;
} function saveFile(data, filename) {
//命名空间
var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
save_link.href = data;
save_link.download = filename; //window.location = save_link;//此方法可下载但是文件名无效
//下载
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
save_link.dispatchEvent(event);
} function printNotifier() {
try{
print.portrait = false;//横向打印 ,去掉页眉页脚
}catch(e){
//alert("不支持此方法");
} //canvas无法直接打印,需先转换成img
$(convertCanvasToImage($("#toPrint")[0])).jqprint();
} function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
} function suitScreen($scope) {
//下方留30放按钮
var effectiveHeight = findParam("#printArea", "height") - 30;
var effectiveWidth = findParam("#printArea","width");
if($scope.printObj.notifierObj.width/effectiveWidth > $scope.printObj.notifierObj.height/effectiveHeight) {
//取最接近的一个属性进行自适应,并适当调小一些
var suitTimes = $scope.printObj.notifierObj.width/effectiveWidth*1.2;
} else {
var suitTimes = $scope.printObj.notifierObj.height/effectiveHeight*1.2;
}
$scope.printObj.notifierObj.width = $scope.printObj.notifierObj.width/suitTimes;
$scope.printObj.notifierObj.height = $scope.printObj.notifierObj.height/suitTimes;
for(i=0;i<$scope.printObj.paramList.length;i++) {
$scope.printObj.paramList[i].size = $scope.printObj.paramList[i].size/suitTimes;
$scope.printObj.paramList[i].left = $scope.printObj.paramList[i].left/suitTimes;
$scope.printObj.paramList[i].top = $scope.printObj.paramList[i].top/suitTimes;
}
} function drawNotifier($scope) {
//canvas需要先定位好,否则画好再动就清除了
$("#toPrint").css("margin-left", (0-$scope.printObj.notifierObj.width)/2+"px");
//上移30放按钮
$("#toPrint").css("margin-top", (0-$scope.printObj.notifierObj.height-60)/2+"px");
var canvas = document.getElementById("toPrint");
canvas.width = $scope.printObj.notifierObj.width;
canvas.height = $scope.printObj.notifierObj.height;
var ctx = canvas.getContext("2d");
var img=new Image();
img.src = $scope.printObj.notifierObj.url;
img.onload=function() {
//需要onload方法接收,否则画不出
ctx.drawImage(img, 0, 0, $scope.printObj.notifierObj.width, $scope.printObj.notifierObj.height);
//写文字,且要在画好图片之后写,否则会被图片覆盖
$.each($scope.printObj.paramList, function(index, e) {
//canvas的字体不会有12px的兼容性问题
ctx.font = "bold "+e.size+"px KaiTi";
//canvas写字以字体的左下角为基准,因而要再加一个字体大小的高度
ctx.fillText(e.objName,e.left, e.top+e.size);
});
} } function assembleHtml($scope) {
var htmlStr = "<img src='" + $scope.printObj.notifierObj.url+"' style='width:"+$scope.printObj.notifierObj.width+"px;height:"+
$scope.printObj.notifierObj.height+"px'>";
for(i=0;i<$scope.printObj.paramList.length;i++) {
var nowObj = $scope.printObj.paramList[i];
if(nowObj.size < 12) {
htmlStr += "<div style='font-size:"+nowObj.size+"px;top:"+nowObj.top+"px;left:"+nowObj.left+
//谷歌浏览器字体小于12px时会不再变小,使用-webkit-transform兼容,并设置已左上角作为变换原点
"px;-webkit-transform:scale("+nowObj.size/12+","+nowObj.size/12+");transform-origin:0 0'>"+nowObj.objName+"</div>";
} else {
htmlStr += "<div style='font-size:"+nowObj.size+"px;top:"+nowObj.top+"px;left:"+nowObj.left+
"px'>"+nowObj.objName+"</div>";
}
}
$("#toPrint").css("margin-left", (0-$scope.printObj.notifierObj.width)/2+"px");
//上移30放按钮
$("#toPrint").css("margin-top", (0-$scope.printObj.notifierObj.height-60)/2+"px");
$("#toPrint").css("height", $scope.printObj.notifierObj.height+"px");
$("#toPrint").css("width", $scope.printObj.notifierObj.width+"px");
$("#toPrint").append(htmlStr);
} //获取有效区域
function findParam(targetObj, attribute) {
//取数字
if($(targetObj).css(attribute) && $(targetObj).css(attribute).replace(/[^0-9]/ig,"") != '0') {
return $(targetObj).css(attribute).replace(/[^0-9]/ig,"");
} else {
//递归
return findParam($(targetObj).parent(), attribute);
}
}

几个需要注意的点:

(1)由于需要留出30像素高的底部放按钮,所有在计算绘制区域的有效高度时应减去30;

(2)绘制顺序:先调整好画布的高宽和位置-->绘制图片-->绘制文字。否则绘制后再调画布会清空,而且先绘制图片再绘制文字时文字覆盖图片而不是反过来;

(3)绘制图片和文字要在图片的onload事件中进行,否则图片还未加载完成就绘制的话会是一片空白区域;

(4)canvas的字体大小不必考虑12px的兼容性问题;

(5)fillText和strokeText,前者是绘制实心文字,后者是空心文字;

(6)画布在未设置宽和高的情况下,会有默认100多的高宽,没有研究源码,但是调试的时候发现的,所有我们取有效区域的时候,就不能直接用toPrint这个canvas进行取了,而要根据其父元素进行取;

(7)createElementNS,下载时用到的,创建带有指定命名空间的元素节点,和createElement类似;

(8)在定义好字体后绘制之前,可以
cxt.fillStyle = "blue";
进行设置颜色

(9)最后就是canvas转图片的方法了,

   var image = new Image();
image.src = canvas.toDataURL("image/png");

其中

canvas.toDataURL("image/png")就可以用来进行图片转base64.首先绘制canvas,画图片进去,然后就可以生成了。

附另外一种图片转base64的方法

使用FileReader

      var reader = new FileReader();
var AllowImgFileSize = 2100000; //上传图片最大值(单位字节)( 2 M = 2097152 B )超过2M上传失败
var file = $("#img1")[0].files[0];
var imgUrlBase64;
if (file) {
//将文件以Data URL形式读入页面
imgUrlBase64 = reader.readAsDataURL(file);
reader.onload = function (e) {
//var ImgFileSize = reader.result.substring(reader.result.indexOf(",") + 1).length;//截取base64码部分(可选可不选,需要与后台沟通)
if (AllowImgFileSize != 0 && AllowImgFileSize < reader.result.length) {
alert( '上传失败,请上传不大于2M的图片!');
return;
}else{
//执行上传操作
//alert(reader.result);
var tempPhoto;
for(var i=0;i<$scope.registerMsg.userPhotoInfos.length;i++) {
//其他允许多张,否则只允许一张
if($scope.img_url_code == $scope.registerMsg.userPhotoInfos[i].photoType
//&& 12 != $scope.registerMsg.userPhotoInfos[i].photoType
) {
tempPhoto = $scope.registerMsg.userPhotoInfos[i];
$scope.registerMsg.userPhotoInfos.splice(i, 1);
break;
}
}
if(tempPhoto) {
/*if(tempPhoto.photoName) {
tempPhoto.photoName = $("#img1")[0].files[0].name;
} else if(!$scope.review) {
tempPhoto['photoName'] = $("#img1")[0].files[0].name;
}*/
tempPhoto.photoUrl = "";
tempPhoto.base64 = reader.result;
$scope.registerMsg.userPhotoInfos.push(tempPhoto);
} else {
$scope.registerMsg.userPhotoInfos.push({
"id": '', //记录的id(更新接口需要带上)
"extendProperty": null,
"photoPath": "",
"photoUrl": "", //照片的预览路径
"userId": $scope.userId, //对应的user的id
"createTime": 0,
"photoType": $scope.img_url_code,
"updateTime": 0,
//"photoName": $("#img1")[0].files[0].name,
"base64": reader.result //图片的base64编码
})
} ......
}
}
}

手机端会有些模糊,原因是canvas在绘制后,进行手机端兼容的情况下会缩放

<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=0">

因而,又对上一篇的方案1进行了修改,增加手动打印和保存,在保存时先html转canvas,再canvas转图片进行保存。

由于jquery的版本问题出现了一些兼容性,高一点版本的代码中已经没有$.browser对象了,所有与jqprint出现了不兼容,解决方法是再拼接进去。

代码:

 (function(jQuery){ 

 if(jQuery.browser) return; 

 jQuery.browser = {};
jQuery.browser.mozilla = false;
jQuery.browser.webkit = false;
jQuery.browser.opera = false;
jQuery.browser.msie = false; var nAgt = navigator.userAgent;
jQuery.browser.name = navigator.appName;
jQuery.browser.fullVersion = ''+parseFloat(navigator.appVersion);
jQuery.browser.majorVersion = parseInt(navigator.appVersion,10);
var nameOffset,verOffset,ix; // In Opera, the true version is after "Opera" or after "Version"
if ((verOffset=nAgt.indexOf("Opera"))!=-1) {
jQuery.browser.opera = true;
jQuery.browser.name = "Opera";
jQuery.browser.fullVersion = nAgt.substring(verOffset+6);
if ((verOffset=nAgt.indexOf("Version"))!=-1)
jQuery.browser.fullVersion = nAgt.substring(verOffset+8);
}
// In MSIE, the true version is after "MSIE" in userAgent
else if ((verOffset=nAgt.indexOf("MSIE"))!=-1) {
jQuery.browser.msie = true;
jQuery.browser.name = "Microsoft Internet Explorer";
jQuery.browser.fullVersion = nAgt.substring(verOffset+5);
}
// In Chrome, the true version is after "Chrome"
else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) {
jQuery.browser.webkit = true;
jQuery.browser.name = "Chrome";
jQuery.browser.fullVersion = nAgt.substring(verOffset+7);
}
// In Safari, the true version is after "Safari" or after "Version"
else if ((verOffset=nAgt.indexOf("Safari"))!=-1) {
jQuery.browser.webkit = true;
jQuery.browser.name = "Safari";
jQuery.browser.fullVersion = nAgt.substring(verOffset+7);
if ((verOffset=nAgt.indexOf("Version"))!=-1)
jQuery.browser.fullVersion = nAgt.substring(verOffset+8);
}
// In Firefox, the true version is after "Firefox"
else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) {
jQuery.browser.mozilla = true;
jQuery.browser.name = "Firefox";
jQuery.browser.fullVersion = nAgt.substring(verOffset+8);
}
// In most other browsers, "name/version" is at the end of userAgent
else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) <
(verOffset=nAgt.lastIndexOf('/')) )
{
jQuery.browser.name = nAgt.substring(nameOffset,verOffset);
jQuery.browser.fullVersion = nAgt.substring(verOffset+1);
if (jQuery.browser.name.toLowerCase()==jQuery.browser.name.toUpperCase()) {
jQuery.browser.name = navigator.appName;
}
}
// trim the fullVersion string at semicolon/space if present
if ((ix=jQuery.browser.fullVersion.indexOf(";"))!=-1)
jQuery.browser.fullVersion=jQuery.browser.fullVersion.substring(0,ix);
if ((ix=jQuery.browser.fullVersion.indexOf(" "))!=-1)
jQuery.browser.fullVersion=jQuery.browser.fullVersion.substring(0,ix); jQuery.browser.majorVersion = parseInt(''+jQuery.browser.fullVersion,10);
if (isNaN(jQuery.browser.majorVersion)) {
jQuery.browser.fullVersion = ''+parseFloat(navigator.appVersion);
jQuery.browser.majorVersion = parseInt(navigator.appVersion,10);
}
jQuery.browser.version = jQuery.browser.majorVersion;
})(jQuery);

整改项目的代码在

https://github.com/MRlijiawei/enroll

其他还有图片转化与保存及自定义文件名的方法,大家也可以作为参照。

js开发打印证书功能(二)的更多相关文章

  1. js开发打印证书功能

    最近突然被加了要打印证书的功能的需求.其实打印功能很简单,直接调用window.print()就可以打印,只是这是最基本的打印,会打印当前页面的所有元素,而我们要的是局部打印,实现方法: 1.设置好开 ...

  2. 网站开发进阶(十二)JS实现打印功能(包括打印预览、打印设置等)

    JS实现打印功能(包括打印预览.打印设置等) 绪 最近在进行项目开发时,需要实现后台管理端打印功能,遂在网上一阵搜索,搜到了很多相关的文章.其中绝大部分文章都是使用的Lodop5.0(Web打印和套打 ...

  3. EasyNVR网页H5无插件播放摄像机视频功能二次开发之直播通道接口保活示例代码

    背景需求 随着雪亮工程.明厨亮灶.手机看店.智慧幼儿园监控等行业开始将传统的安防摄像头进行互联网.微信直播,我们知道摄像头直播的春天了.将安防摄像头或NVR上的视频流转成互联网直播常用的RTMP.HT ...

  4. JS开发HTML5游戏《神奇的六边形》(二)

    近期出现一款魔性的消除类HTML5游戏<神奇的六边形>,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏. (点击图片可进入游戏体验) 因内容 ...

  5. Koa与Node.js开发实战(2)——使用Koa中间件获取响应时间(视频演示)

    学习架构: 在实战项目中,经常需要记录下服务器的响应时间,也就是从服务器接收到HTTP请求,到最终返回给客户端之间所耗时长.在Koa应用中,利用中间件机制可以很方便的实现这一功能.代码如下所示: 01 ...

  6. Node.js学习笔记——Node.js开发Web后台服务

    一.简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.j ...

  7. .NET开发邮件发送功能的全面教程(含邮件组件源码)

    今天,给大家分享的是如何在.NET平台中开发“邮件发送”功能.在网上搜的到的各种资料一般都介绍的比较简单,那今天我想比较细的整理介绍下: 1)         邮件基础理论知识 2)         ...

  8. MVC5 网站开发之七 用户功能 3用户资料的修改和删除

    这次主要实现管理后台界面用户资料的修改和删除,修改用户资料和角色是经常用到的功能,但删除用户的情况比较少,为了功能的完整性还是坐上了.主要用到两个action "Modify"和& ...

  9. MVC5 网站开发之八 栏目功能 添加、修改和删除

    本次实现栏目的浏览.添加.修改和删除. 栏目一共有三种类型. 常规栏目-可以添加子栏目,也可以添加内容模型.当不选择内容模型时,不能添加内容. 单页栏目-栏目只有一个页面,可以设置视图. 链接栏目-栏 ...

随机推荐

  1. svn迁移后本地地址变更及externals无效的问题

    1.软件: visual SVN Server 2.具体方法: 在打开本地原来SVN check  out的根目录,点右键,tortoiseSVN --> relocate 弹出的对话框中修改s ...

  2. 定时任务Task

    使用注解@EnableScheduling开启定时任务,会自动扫描 定义@Component作为组件被容器扫描 对于EnableScheduling是注解在启动类上,很多开关配置都会再启动类中进行设置 ...

  3. java_GPS数据处理

    题目内容: NMEA-0183协议是为了在不同的GPS(全球定位系统)导航设备中建立统一的BTCM(海事无线电技术委员会)标准,由美国国家海洋电子协会(NMEA-The National Marine ...

  4. Docker 系列六(Docker Swarm 项目).

    一.前言 随着互联网快速发展,以及微服务架构的流行,服务器的压力越来越大.上一篇介绍的 Docker Compose 项目,可以将多个容器捏合在一起,实现容器间的通信,比如 Web 项目对 DB.Ca ...

  5. Bootstrap 按钮颜色

    上述按钮CSS规则 .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-w ...

  6. 为什么用bower 安装bootstrap而不用npm来安装?

    NPM(node package manager),通常称为node包管理器.顾名思义,它的主要功能就是管理node包,包括:安装.卸载.更新.查看.搜索.发布等. npm的背后,是基于couchdb ...

  7. js 字符串转数组

    var obj = "123456".replace(/(.)(?=[^$])/g, "$1,").split(",");    conso ...

  8. 小tips:JS严格模式(use strict)下不能使用arguments.callee的替代方案

    在函数内部,有两个特殊的对象:arguments 和 this.其中, arguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 a ...

  9. BZOJ4559: [JLoi2016]成绩比较(dp 拉格朗日插值)

    题意 题目链接 Sol 想不到想不到.. 首先在不考虑每个人的真是成绩的情况下,设\(f[i][j]\)表示考虑了前\(i\)个人,有\(j\)个人被碾压的方案数 转移方程:\[f[i][j] = \ ...

  10. 如何用ABP框架快速完成项目(7) - 用ABP一个人快速完成项目(3) - 通过微服务模式而不是盖楼式来避免难度升级和奥卡姆剃刀原理

    这节文章十分重要!十分重要!十分重要!   很多同学在使用ABP的过程中遇到很多问题, 花费了很多时间和精力都还无法解决, 就是卡在这节文章这里.   Talk is cheap, just show ...