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 相关文章推荐
jquery 表单取值常用代码
Dec 22 Javascript
基于jquery实现拆分姓名的方法(纯JS版)
May 08 Javascript
jquery隐藏标签和显示标签的实例
Nov 11 Javascript
简介JavaScript中toUpperCase()方法的使用
Jun 06 Javascript
jQuery实现分章节锚点“回到顶部”动画特效代码
Oct 23 Javascript
Active控件问题小结(附解决办法)
Jun 09 Javascript
jQuery学习心得总结(必看篇)
Jun 10 Javascript
真正好用的js验证上传文件大小的简单方法
Oct 27 Javascript
微信小程序 页面滑动事件的实例详解
Oct 12 Javascript
React native ListView 增加顶部下拉刷新和底下点击刷新示例
Apr 27 Javascript
React 全自动数据表格组件——BodeGrid的实现思路
Jun 12 Javascript
微信小程序 wx:for遍历循环使用实例解析
Sep 09 Javascript
微信小程序实现上拉加载功能示例【加载更多数据/触底加载/点击加载更多数据】
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
晋城吧对DiscuzX进行的前端优化要点
2010/09/05 PHP
PHP多线程类及用法实例
2014/12/03 PHP
Laravel执行migrate命令提示:No such file or directory的解决方法
2016/03/16 PHP
基于PHP生成简单的验证码
2016/06/01 PHP
Laravel5.* 打印出执行的sql语句的方法
2017/07/24 PHP
php 实现银联商务H5支付的示例代码
2019/10/12 PHP
javaScript 判断字符串是否为数字的简单方法
2009/07/25 Javascript
jquery分析文本里url或邮件地址为真实链接的方法
2015/06/20 Javascript
JavaScript SHA512&amp;SHA256加密算法详解
2015/08/11 Javascript
JS实现左右拖动改变内容显示区域大小的方法
2015/10/13 Javascript
基于BootStrap Metronic开发框架经验小结【五】Bootstrap File Input文件上传插件的用法详解
2016/05/12 Javascript
简单封装js的dom查询实例代码
2016/07/08 Javascript
javascript的几种写法总结
2016/09/30 Javascript
js正则表达式注册页面表单验证
2016/10/11 Javascript
Bootstrap基本插件学习笔记之Alert警告框(20)
2016/12/08 Javascript
jQuery实现的动态文字变化输出效果示例【附演示与demo源码下载】
2017/03/24 jQuery
Node.js发送HTTP客户端请求并显示响应结果的方法示例
2017/04/12 Javascript
荐书|您有一份JavaScript书单待签收
2017/07/21 Javascript
Vue中的v-for循环key属性注意事项小结
2018/08/12 Javascript
微信小程序时间选择插件使用详解
2018/12/28 Javascript
详解Vue中的MVVM原理和实现方法
2020/07/15 Javascript
小程序点餐界面添加购物车左右摆动动画
2020/09/23 Javascript
[47:03]完美世界DOTA2联赛PWL S3 access vs LBZS 第一场 12.20
2020/12/23 DOTA
Web服务器框架 Tornado简介
2014/07/16 Python
python开发之基于thread线程搜索本地文件的方法
2015/11/11 Python
对变量赋值的理解--Pyton中让两个值互换的实现方法
2017/11/29 Python
Python3内置模块pprint让打印比print更美观详解
2019/06/02 Python
python下对hsv颜色空间进行量化操作
2020/06/04 Python
Python爬虫之Selenium库的使用方法
2021/01/03 Python
房屋委托书范本
2014/04/04 职场文书
2014年社区计生工作总结
2014/11/18 职场文书
2016年中秋节晚会领导致辞
2015/11/26 职场文书
如何用RabbitMQ和Swoole实现一个异步任务系统
2021/05/29 PHP
elementui的el-popover修改样式不生效的解决
2021/06/30 Javascript
MyBatis自定义SQL拦截器示例详解
2021/10/24 Java/Android
Python使用pandas导入csv文件内容的示例代码
2022/12/24 Python