想给头像加国旗?何须@微信官方,@我就可以啦

6,776 阅读6分钟

前言

前几天大家都在朋友圈@微信官方,求赐一面国旗,没有得到微信官方的小盆友们,有没有很失落呀~心血来潮的我,决定用js来写一个给头像加国旗的功能,然后我就送了自己一面国旗,事实证明,想要的东西还是只能靠自己的双手去创造。哈哈哈,这鸡汤洒的触不及防~,回归正题,下面就一起来看看我是怎么实现的吧~~

实现过程

理清思路

其实给头像加国旗的实现原理很简单,就是拿到你的图片资源(就是你的头像),用canvas绘制该图片,然后在图片的右下角再绘制一个小国旗,这样基础的功能就实现了。下面我们来一步步实现给头像加国旗的功能。源码地址

应评论区需求,我这里贴一个代码处理后的头像的效果图(图像是有一个白色圆角边框的,可能会与博客的白色背景重叠,审美不太好,凑合看哈~).

我把我开发该功能的整个思考流程梳理如下:

  1. 创建input标签,定义onchange事件,读取图片资源,拿到头像的base64编码;
  2. 创建canvas标签,网上找一个国旗的图片,同样拿到该图片的base64编码,依次使用canvas的drawImage api把头像和国旗绘制在特定位置。
  3. 直角的头像和国旗太生硬,于是写了个方法给头像和国旗加了一个白色的圆角边框。
  4. 写死画布的高度会导致图片被拉伸,所以我希望固定画布的宽度,画布的高度根据图片资深的宽高比,和已知的宽度,把按比例计算出来的高度设置为画布的高度。因为国旗的绘制区域是相对于画布的右下角的位置,所以需要在画布高度确定之后(也就是头像绘制完成之后)再进行绘制。
  5. 把canvas绘制好的图片下载,编写下载图片的逻辑。

开始编码

html代码:

<div>
  <div><button class="input-container">
      上传头像
      <input accept="image/gif,image/jpeg,image/png"
      class="file-input" id="fileInput" type="file" />
    </button>
  <button class="save-btn"  onclick="downLoad()">保存图片</button>
  </div>
  <canvas id="canvas" class="canvas"></canvas>
</div>

js代码:

// 这一过程是定义一些可配置的参数,以便根据需要做修改
const options = {
  avatarImgX: 0,  // 头像开始剪切的x坐标
  avatarImgY: 0,  // 头像开始剪切的y坐标
  avatarX: 10,    // 画布上头像开始绘制的x坐标
  avatarY: 10,    // 画布上头像开始绘制的y坐标
  avatarWidth: 200,   // 画布上要绘制的头像的宽度
  avatarHeight: 200,  // 画布上要绘制的头像的宽度
  radius: 5,          // 头像的圆角半径
  
  guoqiRadius: 3,     // 国旗的圆角半径
  guoqiX: 0,          // 国旗开始剪切的x坐标
  guoqiY: 0,          // 国旗开始剪切的y坐标
  guoqiWidth: 50,     // 画布上要绘制的国旗的宽度
  guoqiHeight: 33,    // 画布上要绘制的国旗的宽度
  // 存放的是国旗的base64编码,源码地址点开可查看完整的
  guoqi: ""           
}
// 定义一些常量
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const inputUpload = document.getElementById('fileInput');
// 定义input的onchange事件,通过fileReader拿到图片的base64编码
window.onload = function() {
  inputUpload.onchange = function uploadAvatar() {
    const file = this.files[0];
    if(file) {
      // 读取图片资源
      const fileReader = new FileReader();
      fileReader.readAsDataURL(file);
      fileReader.onload = function () {
       // 为了保证图片不失真,画布的高度是根据上传头像的宽高比
       // 和options中定义好的绘制宽度计算出来的,
       // 所以需要把国旗的绘制放在头像绘制完成后的回调函数中执行。
       function cb() {
         const param = {
           sx: options.guoqiX,
           sy: options.guoqiY,
           x: canvas.width - options.guoqiWidth - options.radius * 2,
           y: canvas.height - options.guoqiHeight - options.radius * 2,
           width: options.guoqiWidth,
           height: options.guoqiHeight
         }
         // 调用绘制图片的工具函数,传入所需参数
        drawAvatar(options.guoqi,cb, param.sx,param.sy,param.x,param.y,
        param.width,param.height, options.guoqiRadius)
      } 
      // 调用绘制图片的工具函数,传入所需参数
      drawAvatar(this.result,cb,options.avatarImgX,options.avatarImgY,
      options.avatarX,options.avatarY,options.avatarWidth,
      options.avatarHeight); 
  }
 }  
};
}
// 绘制图片
function drawAvatar(imgSrc,cb, ...args) {
  let img = new Image();
  img.src = imgSrc;
  // 很多情况下的图片绘制的逻辑都会写在onload里面
  // 是因为通常的图片资源都是网络请求获取的,
  // 我们会要保证在资源加载完成后才能正确完成绘制。
  // 我这里是直接使用的图片的base64编码,
  // 绘制图片的逻辑也可不写在onload函数里
  img.onload = function () {
    args.splice(2, 0, img.width,img.height);
    // 绘制的是头像,则计算并设置画布的高度,清空input的值
    if(args[6] === options.avatarWidth) {
       args[7] = parseInt(args[6] / (img.width / img.height));
      canvas.width = args[6] + 20;
      canvas.height = args[7] + 20;
      inputUpload.value = '';
       }
    const r = args[8] || options.radius, x = args[4],
    y = args[5], w = args[6], h=args[7];
    ctx.drawImage(img, ...args);
    // 绘制图像的圆角的函数
    drawRadius(x, y, w, h, r);
    // 绘制国旗
    typeof cb === 'function' && cb();
  }
  
}
// 绘制图片圆角
function drawRadius(x,y,w,h,r) {
  ctx.beginPath();
  ctx.strokeStyle='white';
  ctx.fillStyle ='white'
  ctx.lineWidth= r;
  console.log(ctx.lineWidth);
  ctx.moveTo(x+r, y);
  ctx.arcTo(x+w, y, x+w, y+h, r);
  ctx.arcTo(x+w, y+h, x, y+h, r);
  ctx.arcTo(x, y+h, x, y, r);
  ctx.arcTo(x, y, x+w, y, r);
  ctx.stroke();
  ctx.closePath();
}
// 图片下载,下载的原理就是创建a标签,指向图片的下载地址,
// 模拟触发a标签的点击完成下载
function downLoad(url){
    let oA = document.createElement("a");
    oA.download = 'avatar';
    // 通过canvas.toDataURL方法生成图片的下载地址 
    oA.href = saveAsPNG();
    document.body.appendChild(oA);
    oA.click();
    oA.remove(); // 下载之后把创建的元素删除
}
// 保存成png格式的图片,你这里可以选择其他格式的图片保存
function saveAsPNG() {
    return canvas.toDataURL("image/png");
}

css代码:

 .save-btn, .input-container{
  border: none;
  box-shadow: none;
  width: 150px;
  padding: 10px;
  margin-bottom: 20px;
  margin-right: 20px;
  text-align: center;
  color: #fff;
  background-color: blue;
  border-radius: 5px;
  cursor: pointer;
}
.save-btn {
  background-color: #67c23a;
}
.input-container {
  background-color: #409eff;
  position: relative;
  .file-input {
    cursor: pointer;
    position: absolute;
    display: inline-block;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    opacity: 0;
  }
}

总结

到这儿,给头像添加国旗的功能就完成了,不知道你们有没有学会呢?代码写到这里其实还有很多待完善的地方,例如国旗的素材,我选的这个国旗图片很规矩,没有太多的设计感,还可以利用canvas的其他api,让生成的图片更有趣,更好看,更自然。我今天的分享就到这儿了,有什么疑惑或者建议可评论区留言给我奥~~,最后,祝我们的祖国生日快乐~,国泰民安,繁荣富强,这盛世,如你所愿!

扩展阅读