别再@官方啦,普天同庆加国旗
国庆就要来了!今年是新中国成立70周年,大家的热情都很高涨。这不,我今天一翻朋友圈,被齐刷刷地带国旗的头像给刷屏了!
不过还有些朋友不明就里,还在不停地@微信官方,希望能自动给头像加上一面国旗。然而等了半天,还是毫无反应……
当然,程序员朋友,也开始了一顿操作。
python 用 python-opencv 库,直接调。
go 用 image 库,直接调。
那么前端呢? 咱们前端不用库,原生的 js + canvas 简单快捷。
一、思路分析
大致有两种办法可以实现。
- 使用 将图片画在 canvas 上面,然后通过 api 获取 图片的像素值,进行叠加,然后再写入 canvas ,最后导出图片。好处是:可以对透明的图像进行处理,自定义程度高。缺点:控制起来复杂度高,计算量大。
- 直接将两张图片分两次画在 canvas 上面,然后导出图片 。 好处:简单快捷。 缺点:透明图像不能处理,可控性小。
这里我采用第二种办法,简单点。。
二、技术点
1、 获取画布:
const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d");
2、将图片画入canvas
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
3、导出图片
canvas.toDataURL("image/png");
4、有了这些方法,我们的图片从哪里来?
通过 <input type="file" name="" id="flag" value="" /> 能拿到文件 file ,我们只需要将 file 转化为 图片的 url链接即可。
这里有两种办法:1、使用 FileReader
读取出 base64图片,是一个异步操作。 2、使用 URL.createObjectURL 读取出 blob 的协议链接,是一个同步操作。这里我们采用第二种办法
三、正式开始
1、结构和样式
<style type="text/css"> * { padding: 0; margin: 0; font-size: 14px; } #box { 400px; height: 400px; border: 1px solid red; margin: 20px auto; position: relative; } #operate { text-align: center; ; } #operate p { margin-bottom: 6px; } input[type=file] { display: none; } label { display: inline-block; cursor: pointer; background: #38f; color: #fff; 102px; height: 38px; line-height: 38px; border-radius: 4px; } </style>
<div id="box"> <canvas id="canvas" width="400" height="400"></canvas> </div> <div id="operate"> <p><label><input type="file" name="" id="bg" value="" />选择头像</label></p> <p><label><input type="file" name="" id="flag" value="" />选择上层</label></p> <p><label id="create">直接生成</label></p> </div> <a href="" download="logo.png" title="点击下载" id="down"> <img src="" id="result"> </a> </body>
2、js代码如下
const canvas = document.getElementById("canvas"); //获取canvas const ctx = canvas.getContext("2d"); //获取画布 const baseW = 400; //头像的最大宽高 const flagW = 100; //旗的最大宽高 let bgConfig; //画 头像的参数 let flagConfig; //画 旗的参数 document.getElementById("bg").onchange = async function() { const file = this.files[0]; try { const img = await getImageObj(file); const rate = compress(img, baseW); bgConfig = [img, 0, 0, img.width, img.height, 0, 0, rate.w, rate.h]; drawn(); } catch (e) { console.error(e); } }; document.getElementById("flag").onchange = async function() { const file = this.files[0]; try { const img = await getImageObj(file); const rate = compress(img, flagW); flagConfig = [img, 0, 0, img.width, img.height, 0, 0, rate.w, rate.h]; drawn(); } catch (e) { console.error(e); } }; //画 function drawn() { ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); if (bgConfig) { ctx.drawImage(...bgConfig); } if (flagConfig) { ctx.drawImage(...flagConfig); } } //图片压缩,获取等比缩放后的结果 function compress(img, base) { let w = img.width; let h = img.height; if (img.width > img.height) { if (img.width > base) { //要将宽度缩放 w = base; h = (w / img.width) * img.height; // 新的 宽比 高 = 旧的宽比高 h / w = img.heigth/img.width ; } } else { if (img.height > base) { h = base; w = (h / img.height) * img.width; } } return { w, h }; } //通过 file 获取到图片对象 function getImageObj(file) { const url = getObjectURL(file); const img = new Image(); img.src = url; return new Promise((resolve, reject) => { img.onload = function() { resolve(img); } img.onerror = function(e) { reject(e); } }); } //取得该文件的url function getObjectURL(file) { var url = null; if (window.createObjectURL != undefined) { url = window.createObjectURL(file); } else if (window.URL != undefined) { url = window.URL.createObjectURL(file); } else if (window.webkitURL != undefined) { url = window.webkitURL.createObjectURL(file); } return url; } //直接生成 document.getElementById("create").onclick = function(){ exportImg({ x:0, y:0, w:canvas.clientWidth, h:canvas.clientHeight, }); } //导出头像的范围 function startClip(area) { var canvas = document.createElement("canvas"); canvas.width = area.w; canvas.height = area.h; var data = ctx.getImageData(area.x, area.y, area.w, area.h); var context = canvas.getContext("2d"); context.putImageData(data, 0, 0); return canvas.toDataURL("image/png"); } //导出头像,点击可以下载 function exportImg(clipArea){ var url = startClip(clipArea); document.getElementById("result").src = url; document.getElementById("down").href = url; }
其中: bgConfig 存了 头像的 logo 图 ctx.drawImage 的参数。 flagConfig 存了 旗子 参数。
结果如下:
很显然,这并不是我们要的效果,需要控制旗子的位置,也简单。
调整 flagConfig 组数里面的 5 和 6 的位置元素 值,即 x 、y ,例如:
flagConfig[5] = 100; flagConfig[6] = 200;
为了方便修改。增加两个输入框:
在js 里面增加如下代码:
document.getElementById("posX").oninput = function() { let val = Number(this.value) || 0; flagConfig[5] = val; drawn(); } document.getElementById("posY").oninput = function() { let val = Number(this.value) || 0; flagConfig[6] = val; drawn(); }
大功告成:
四、问题
以为这样就完了吗?还早,,如果传一张不规则的头像,如下:
如果遇见这样不规则的图片,最后生成的图片,会出现一片白边。
不过,这可难不到程序员,截掉就可以啦。
后面截取 canvas的代码就不一 贴出来了,最后附近加入完整版的吧!
直接用鼠标截取想要的部分:
完整版源码:
链接:https://pan.baidu.com/s/1c4Rj6zmILpJW-x5yjGtSzg
提取码:ol0y