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

上一篇地址: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. 使用IIS调试ASP.NET网站程序

    在实际的开发当中,相信很多的开发者在开发调试ASP.NET网站时候都是直接通过Visual Studio工具的编译运行来调试的. 一般情况下,这种调试方式也不会有多少问题,但有时候我们会发现这样的一个 ...

  2. mysql 关联查询技巧

    废话不多说,直接进入正题 #数据准备 班级表class: CREATE TABLE `class` ( `class_no` ) unsigned zerofill NOT NULL AUTO_INC ...

  3. 解决MyEclipse中install new software问题

    eclipse中点击help可以直接找到install new software选项进行安装插件,但是在Myeclipse中help没有这个选项,如下提供几种解决方法 Windows-preferen ...

  4. 【Java】Properties文件的解析

    public abstract class ReadProperties { public ReadProperties() {} /** * 回调函数,由调用者处理 * @param key * @ ...

  5. javascript浅拷贝深拷贝详解

    一.浅拷贝 浅拷贝在现实中最常见的表现在赋值上面,例如 <!DOCTYPE html> <html lang="en"> <head> < ...

  6. es6 set

    ES6 提供了新的数据结构 Set.它类似于数组,但是成员的值都是唯一的,没有重复的值. Set 本身是一个构造函数,用来生成 Set 数据结构. const setset = new Set([1, ...

  7. 如何通过setTimeout理解JS运行机制详解

    setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行.它返回一个整数,表示定时器timer的编号,可以用来取消该定时器. 例子 ? 1 2 3 4 5 console.log(1 ...

  8. 下载使用前端开发工具sublime,并汉化

    官网:www.sublimetext.com 汉化流程:安装package control 1.打开“https://packagecontrol.io/installation”,先下载“packa ...

  9. ABP问题速查表

    如果你领导要让你一夜之间掌握ABP,并且用ABP撸一个项目出来,你很可能很快速的过了一遍ABP文档就马上动手干活了.那么这篇文章就很适合你. 这篇文章列出了很多ABP新手问的问题和解答.注:有些同学问 ...

  10. Nginx 图片服务器

    文件服务器:后台如果是集群,每次请求都会到不同的服务器,所以每台服务器的图片文件等都要做同步处理,才能保证每次用户不管访问到哪台服务器都能获取一样的资源.这种做法开销会很大,专门使用 nginx 作为 ...