JavaScript基于用户照片姓名生成海报


Posted in Javascript onMay 29, 2020

前言

最近在为公司的一个比赛制作专题页,碰到一个使用参赛者上传的照片生成专属海报的需求,实现过程中用到了一些以前没用过的 api,也踩了一些坑,于是将其记录下来。

需求描述

  • 用户点击按钮进行照片上传
  • 照片上传完成后,将照片进行裁剪,并和海报背景、姓名等组合得到海报
  • 将生成的海报上传

效果大概如下:

海报背景:

JavaScript基于用户照片姓名生成海报

成品:

JavaScript基于用户照片姓名生成海报

实现过程

1、初始化 canvas

canvas#poster-canvas(width='960' height='1280')
function initCanvas() {
canvasCtx = document.getElementById("poster-canvas").getContext('2d');
}

2、绘制海报背景

海报背景为预先提供的一张照片,将其设置到一个隐藏的 img 标签里面,并且预留一个 canvas 元素用于绘制海报:

img.poster-background(src='/assets/xxx/poster-background.jpeg')

页面加载完成后,将海报背景绘制到 canvas 内:

$('img.poster-background').on('load', function () {
 var backgroundImg = $('img.poster-background')[0];
 canvasCtx.drawImage(backgroundImg, 0, 0, 960, 1280);
 renderName();
});

海报背景绘制完成之后,需要将用户姓名绘制到特定位置。由于用户姓名长度不一,因此需要进行计算确定字体大小:

function renderName() {
 var name = $('input[name="chName"]').val();
 var fontSize;
 if (name.length < 3) {
  fontSize = 100;
 } else {
  fontSize = parseInt(320 / name.length);
 }
 canvasCtx.font = "bold " + fontSize + "px Courier New";
 canvasCtx.fillStyle = "#de071b";
 canvasCtx.fillText(name, 20, 1066);
}

3、上传照片

使用 file 类型的 input 元素,因为页面上表现为点击按钮,因此使用经典的将 input 元素透明化并覆盖按钮的方法:

a.upload-btn 
 input#photo(type='file' name='photo' accept='image/jpeg, image/png')
 | 上传自己的照片生成专属海报
.upload-btn input {
 position: absolute;
 left: 0;
 top: 0;
 opacity: 0;
 width: 100%;
 height: 68px;
 cursor: pointer;
}

然后监听 input 元素的 change 事件,然后使用 FormData API 构造表单数据,使用 ajax 进行异步上传,照片上传完成之后。得到一个地址,将这个地址设置到页面上预留的一个 img 标签里面:

$('#photo').on('change', function (e) {
 var file = e.target.files[0];
 var type = file.type;
 if (type !== 'image/jpeg' && type !== 'image/png') {
  window.toastr.error('请上传 jpg 或 png 格式的图片');
 } else {
  var formData = new FormData();
  formData.append('avatar', file);
  $.ajax({
   type: 'POST',
   url: '/upload_url',
   data: formData,
   contentType: false,
   processData: false,
   success: function(result) {
    var avatarUrl = result.data.url;
    $('img.avatar').attr('src', avatarUrl);
   },
   error: function(err) {
    
   }
  });
 }
});

4、绘制照片

海报中放置照片的区域为正方形,但是用户上传的照片却不一定,因此需要对照片进行裁剪,裁剪的原则为取照片中间部分。然后将裁剪参数传进 canvas 的 drawImage 方法,进行绘制:

$('img.avatar').on('load', function () {
 var avatarImg = $('img.avatar')[0];
 var originWidth = avatarImg.width;
 var originHeight = avatarImg.height;
 var newWidth, cutStartX, cutStartY;

 if (originWidth < originHeight) {
  newWidth = originWidth;
  cutStartX = 0;
  cutStartY = (originHeight - originWidth) / 2;
 } else if (originWidth > originHeight) {
  newWidth = originHeight;
  cutStartX = (originWidth - originHeight) / 2;
  cutStartY = 0;
 } else {
  newWidth = originWidth;
  cutStartX = 0;
  cutStartY = 0;
 }
 
 canvasCtx.drawImage(avatarImg, cutStartX, cutStartY, newWidth, newWidth, 0, 0, 960, 960);

 uploadPoster();
   
});

前面绘制海报背景和这里绘制照片,调用的是同一个方法,只不过后者多传进去了裁剪参数。但是需要注意的是,裁剪参数是在绘制位置之前传进去的,而不是简单的补在后面:

canvasCtx.drawImage(backgroundImg, 0, 0, 960, 1280);

canvasCtx.drawImage(avatarImg, cutStartX, cutStartY, newWidth, newWidth, 0, 0, 960, 960);

5、上传海报

依然使用 FormData API,因此需要先用 canvas 构造一个 Blob 对象。新版本的 Chrome 和 Firefox 支持 canvas 的 toBlob 方法,可以直接使用:

document.getElementById("poster-canvas").toBlob(function (blob) {});

其它浏览器里,可以先用 toDataURL方法得到 base64 格式的图片数据,再转为 Blob:

var blob = dataURLtoBlob(document.getElementById("poster-canvas").toDataURL());

function dataURLtoBlob(dataurl) {
 if (dataurl.indexOf('base64') < 0) {
  dataurl = 'data:image/jpeg;base64,' + dataurl;
 }
 var arr = dataurl.split(',');
 var mime = arr[0].match(/:(.*?);/)[1];
 var bstr = atob(arr[1]);
 var n = bstr.length;
 var u8arr = new Uint8Array(n);
 while (n --) {
  u8arr[n] = bstr.charCodeAt(n);
 }
 return new Blob([u8arr], {type: mime});
}

然后进行上传,步骤和前面上传照片一致:

var formData = new FormData();
formData.append('poster', blob);
$.ajax({
 type: 'POST',
 url: '/upload_poster_url',
 data: formdata,
 contentType: false,
 processData: false,
 success: function(result) {
  
 },
 error: function(err) {
  
 }
});

至此,整个流程完结。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
做网页的一些技巧
Feb 01 Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
Mar 04 Javascript
js实现滑动触屏事件监听的方法
May 05 Javascript
浅谈JSON.stringify()和JOSN.parse()方法的不同
Aug 29 Javascript
JavaScript页面实时显示当前时间实例代码
Oct 23 Javascript
微信小程序开发之录音机 音频播放 动画实例 (真机可用)
Dec 08 Javascript
详解vue跨组件通信的几种方法
Jun 15 Javascript
node.js中grunt和gulp的区别详解
Jul 17 Javascript
webpack+vue+express(hot)热启动调试简单配置方法
Sep 19 Javascript
详解vue项目中实现图片裁剪功能
Jun 07 Javascript
Vue项目打包压缩的实现(让页面更快响应)
Mar 10 Javascript
Vue3.0中Ref与Reactive的区别示例详析
Jul 07 Vue.js
微信小程序实现上拉加载功能示例【加载更多数据/触底加载/点击加载更多数据】
May 29 #Javascript
JavaScript设计模式之策略模式实现原理详解
May 29 #Javascript
JavaScript隐式类型转换代码实例
May 29 #Javascript
vue实现编辑器键盘抬起时内容跟随光标距顶位置向上滚动效果
May 28 #Javascript
node+vue实现文件上传功能
May 28 #Javascript
vue中实现图片压缩 file文件的方法
May 28 #Javascript
详解Vue 数据更新了但页面没有更新的 7 种情况汇总及延伸总结
May 28 #Javascript
You might like
深入解析PHP垃圾回收机制对内存泄露的处理
2013/06/14 PHP
php5.3 注意事项说明
2013/07/01 PHP
2个比较经典的PHP加密解密函数分享
2014/07/01 PHP
PHP启动windows应用程序、执行bat批处理、执行cmd命令的方法(exec、system函数详解)
2014/10/20 PHP
PHP堆栈调试操作简单示例
2018/06/15 PHP
php实现的生成排列算法示例
2019/07/25 PHP
Js sort排序使用方法
2011/10/17 Javascript
jQuery.event兼容各浏览器的event详细解析
2013/12/18 Javascript
javascript判断office版本示例
2014/04/11 Javascript
jQuery实现鼠标经过图片变亮其他变暗效果
2015/05/08 Javascript
BootStrap中Tab页签切换实例代码
2016/05/30 Javascript
微信小程序 ecshop地址三级联动实现实例代码
2017/02/28 Javascript
Bootstrap 模态框多次显示后台提交多次BUG的解决方法
2017/12/26 Javascript
react项目实践之webpack-dev-serve
2018/09/14 Javascript
node和vue实现商城用户地址模块
2018/12/05 Javascript
JS代码实现页面切换效果
2021/01/10 Javascript
Python多线程编程(四):使用Lock互斥锁
2015/04/05 Python
Python中functools模块的常用函数解析
2016/06/30 Python
django定期执行任务(实例讲解)
2017/11/03 Python
python执行精确的小数计算方法
2019/01/21 Python
python验证码图片处理(二值化)
2019/11/01 Python
tensorflow使用range_input_producer多线程读取数据实例
2020/01/20 Python
使用Python实现将多表分批次从数据库导出到Excel
2020/05/15 Python
django admin管理工具自定义时间区间筛选器DateRangeFilter介绍
2020/05/19 Python
Regatta官网:英国最受欢迎的户外服装和鞋类品牌
2019/05/01 全球购物
学生处主任岗位职责
2013/12/01 职场文书
自我评价是什么
2014/01/04 职场文书
采购主管岗位职责
2014/02/01 职场文书
销售主管岗位职责
2014/02/08 职场文书
法人委托书范本
2014/04/04 职场文书
租房协议书怎么写
2014/04/10 职场文书
护士优质服务演讲稿
2014/08/26 职场文书
教师个人发展总结
2015/02/11 职场文书
Python中使用ipython的详细教程
2021/06/22 Python
总结一下关于在Java8中使用stream流踩过的一些坑
2021/06/24 Java/Android
实现一个简单得数据响应系统
2021/11/11 Javascript