前端新手用JS自己编写一个身份证水印添加应用

Web | Html5 | Javascript

效果

使用步骤

先选择本地图片,点击加载图片,调整好需要的参数后点击 添加水印 ,如果不满意可点击 清空画布 重新加载图片,重新选择参数,调整到满意为止,然后点击保存按钮一键保存到本地。

如果保存按钮点击之后无法下载,可能是图片过大了(2M以上),但右键另存为还是可以用的。

缘起

最近在小众软件上看到这样一个工具 “水水的证件” ,号称保护隐私,为身份证加水印,防止身份证被盗用。但是只提供了一个simpletool的在线网页供用户使用,一开始还把加水印放在服务端实现,为了保护隐私把自己的身份证图片发到一个没有任何保障的网站上去?后面开发者意识到这个问题,于是把加水印服务放到了前端,但仍然是个在线网页。这意味着网页仍然可以通过某个隐蔽的Javascript脚本将你加载进网页的图片发到不知道哪里去!会考虑到给自己的证件图片加水印的人这点安全意识还是要有的,反正我是不敢他们的服务,实在难以判断它的安全性。于是开发者在号称陷入如何让用户认为 水水的证件 是安全的困境后粗暴地把代码开源,让用户自己去做code review。开源总是安全的吧?错!如何证明在线跑的代码就是开源出来的代码又是值得怀疑的点,并且既然是前端加水印,代码应该都是在F12里可见的html5文本才对,有何开源的意义?既然如此,我就直接用Chrome自带的开发者工具扒一扒它网页上的源码。

使用开发者工具查看在线网页的源码

可以看到,引用了三个站外脚本,这个main前缀的有近六万行,想来是什么诸如框架模板一类的东西,但这么大的体积里想藏点东西可太容易了,另外两个站外脚本里面更不知道是用来干什么的,那个share.baidu.com的外链更是引入了百度分享的脚本,令人心慌慌。页面上的“任何证件信息不会上传到网站,请放心使用”更是毫无说服力,正所谓「我们保证不会泄漏用户资料」在互联网上的意思基本上就是「我们保证会拿你的资料去卖钱」,颇有此地无银之感,实在难以安心使用。

虽然这样的服务让人感觉不可靠,但是却提醒了我,在需要使用身份证扫码件进行实名认证时非常有必要打上水印。

那既然信不过别人的产品,就自己写一个咯。况且心中隐约觉得,使用html5的canvas应该可以很容易实现在前端本地加水印的效果。虽然作为一个前端萌新,对html的了解仅仅是知道每个标签的作用,以及许久前在FCC(FreeCodeCamp)上刷的一点点javascript,css语法,但是做项目是熟悉语言的最快方式,于是我边查手册边写边百度,花了一个晚上实现了这个玩意,现在将过程分享出来,好让大家可以省心,放心地使用本地的html文件给自己的有需要的图片加上类似的水印,我将解释每一行代码的作用,大家可以下载我写的版本,也可以参考我写这玩意的过程写出自己的web应用。

需求

做一件事首先要明确目标,做一个应用要先明白需求。我需要的是在单个html本地网页打开图片并打上密集的有旋转角度的水印,并且绝不引入外部链接,原则上不使用框架,使用原生JavaScript

资料源

不会就要查,语言什么的都是工具,用的时候查说明书就好咯。以下是我做这玩意过程中查询资料的地方。

过程

创建html文件

第一步肯定要先创建一个网页嘛,零基础的朋友看看,懂的自己跳过。

创建一个文本文件,后缀改成.html,使用文本编辑器打开。(我使用的是notepad++)

下面是我的开发界面,左边随时用来查询,右下方编辑代码,右上方浏览器打开编辑的html文件,每写一点就crtl+s保存然后F5刷新页面看看效果,随时更正错误

创建一个html结构

创建canvas画布并初步水印

旋转密铺水印

使用ctx.rotate()旋转坐标系,使用嵌套for循环密铺,铺完的再把坐标系转回去,避免多次打印水印时越转越多

rotate接受的角度参数是弧度制的,使用角度值的参数输入就需要先计算转换

angle = 30*Math.PI/180;
ctx.rotate(angle);
var x,y;
for(x=0;x<=320;x+=(100)) {
for(y=0;y<=240;y+=(20)) {
    ctx.fillText("hello",x,y);
}}
ctx.rotate(-angle);

UI界面与更多参数控制

初步熟悉完canvas,心里有底,接下来将水印的功能完善,并添加相应控件,根据需求动态调整参数

清空之前body与script标签中的demo内容(懒得介绍一步步改进代码的过程了)

在<body>中添加标签获得一个面板,在按钮中使用onclick事件调用后面的JavaScript里的函数

用value属性定义的初始值,id用来给js代码的访问定位

下载按钮的初始链接使用了小书签(bookmarklet),以在空链时提示,而后面加载图片的默认链接则没有使用,因为没有必要

<div align="center">
  <input type="file" id="srcPic" accept="image/png" />
  <p>水印文本
  <input type="text" id="waterText" value="输入文本 他用无效">
  </p>
  <div>
  <p>旋转角度
  <input type="range" id="waterAngle" value ="20" min="0" max="360" >
  </p>
  <p>透明程度
  <input type="range" id="waterAlpha" value ="0.3" min="0" max="1" step="0.1">
  </p>
  </div>
  <p>
  尺寸颜色
  <input type="number" id="fontSize" value ="20">
  <input type="color" id="waterColor" value ="#FFC125">
  </p>
</div>
<div align="center">
  <button type="botton" id="btn-onload" onclick="Onload()" class="btn">加载图片</button>
  <button type="button" id="btn-print" onclick="printWater()" class="btn"> 添加水印 </button>
  <button type="button" id="btn-clear" onclick="clearPrint()" class="btn"> 清空画布 </button>
  <a id="download" href="javascript:(function(){alert('图片未完成');})();" download="waterMarked"><button class="btn">保存图片</button></a>
</div>
<div align="center">
  <canvas id="waterMark" width="320" height="240"></canvas>
</div>

再在script标签中加入代码获取这些元素的访问

//获取元素访问
var waterImg=document.getElementById("waterMark");
var waterText = document.getElementById("waterText");
var fontSize = document.getElementById("fontSize");
var waterAngle=document.getElementById("waterAngle");
var waterAlpha=document.getElementById("waterAlpha");
var waterColor=document.getElementById("waterColor");
var srcPic=document.getElementById("srcPic");
var download=document.getElementById("download");
//获取画布访问
var ctx = waterImg.getContext("2d");

这样就能在后面的代码中获取到页面元素中的参数,如value,height,width

继续在script标签中加入代码,写一个printWater()实现那个 添加水印 的按钮的功能,这时,水印的具体参数都可以通过元素的属性获得到了,密铺的范围要比画布大,因为要确保360°的旋转都能有水印完整覆盖,1.4这个数字是根号二来的,偷懒直接取了图片是正方形的情况来算,如果图片比例比较奇怪的话还是不一定覆盖到?

值得一提的是`${fontSize.value}px Arial`这样的字符串格式化,使用反引号代替引号包裹字符串然后插入${变量}

function printWater() {	
	
	//获取水印属性
	ctx.fillStyle = waterColor.value; //颜色
	ctx.globalAlpha = waterAlpha.value; //透明度
	ctx.font = `${fontSize.value}px Arial`; //字体
	
	angle = waterAngle.value*Math.PI/180; //旋转角度
	
	//获取画布大小
	cvsHeight = waterImg.height*1.4;
	cvsWidth = waterImg.width*1.4;
	xGap = fontSize.value*waterText.value.length
	yGap = fontSize.value*2
	
	//密铺水印
	ctx.rotate(angle);
	var x,y;
	for(x=-cvsWidth;x<=cvsWidth;x+=(xGap)) {
	for(y=-cvsHeight;y<=cvsHeight;y+=(yGap)) {
		ctx.fillText(waterText.value,x,y);
	}}
	ctx.rotate(-angle);
}

清空画布

function clearPrint() {
	ctx.clearRect(0, 0, waterImg.width, waterImg.height);
}

载入本地图片

载入本地图片的实现有个关键点,只有把本地文件通过FileReader()函数以base64读入后才能载入,将图片装载到一个Image()对象中再使用canvas的drawImage()加载到画布上,加载到画布上之前要先将画布尺寸调到与图像一致

function Onload() {
	//将图片转为base64码进行加载
        var imgFile = new FileReader();  
        imgFile.readAsDataURL(srcPic.files[0]);  
        imgFile.onload = function () {  
        var img = new Image()
	img.src = this.result;
	img.onload = function(){
		waterImg.width=img.width;
		waterImg.height=img.height;
		ctx.drawImage(img,0,0);
	}
}}

保存水印后图片

在printWater()函数和[clearPrint()]的末尾追加一行以更新保存链接

save();

在script里再添加一个save()函数的代码

将画布上的内容转为一个base64码的url链接作为保存链接

function save() {
	var finalResult=waterImg.toDataURL('waterMarked/png');
	download.href=finalResult;
}

美化

编辑head标签中的内容,利用meta指定了utf-8编码,使用style标签插入css样式简单修改了部分元素外观,(依然很朴素),大家可以根据自己的审美利用css把界面进一步美化。因为不使用外部脚本什么的,所以没法用bootstrap之类我喜欢的响应式框架,懒得自己写样式了。

<head>
<title>WaterMark证件水印</title>
<meta name="水印" http-equiv="Content-Type" content="WaterMark; charset=utf-8" />
<style type="text/css">
#waterMark {
	border: 6px solid;
	border-color: #686de0;
	box-shadow: 10px 10px 5px #95afc0;
}
#fontSize {
	width: 50px;
}
#srcPic {
	border: 2px solid;
	border-color: #686de0;
}
.btn{
	border-radius: 25px;
}
</style>
</head>

加强功能(渐变、阴影)

设置canvas的fillStyle时为了加强水印的强度,可以使用渐变的颜色,阴影,甚至带纹理样式的水印。我尝试了前两种,代码如下。

var grd=ctx.createLinearGradient(0,0,waterImg.width,waterImg.height);
grd.addColorStop(0,"blue");
grd.addColorStop("0.3","green");
grd.addColorStop("0.5","yellow");
grd.addColorStop("0.6","red");
grd.addColorStop("0.8","magenta");
grd.addColorStop(1,"blue");
ctx.fillStyle = grd;
ctx.shadowBlur=20;
ctx.shadowColor="black";

仅作尝试,并未正式加入完整代码中,水印效果如下


渐变+阴影

结束

暂时就做成这样吧,去掉注释空行不到一百行的html代码完成目标。有空可能会再完善强化水印的功能和自动调整字体大小什么的,现在么,懒得写了。若有需求,浏览完本文的读者可以自己完善一下。

完整代码在文章开头

谢谢浏览。

《前端新手用JS自己编写一个身份证水印添加应用》上有1条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注