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 相关文章推荐
[HTML/CSS/Javascript]WWTJS
Sep 25 Javascript
jquery中dom操作和事件的实例学习 仿yahoo邮箱登录框的提示效果
Nov 30 Javascript
javascript 禁用IE工具栏,导航栏等等实现代码
Apr 01 Javascript
jquery实现input输入框实时输入触发事件代码
Jan 28 Javascript
JQuery插件ajaxfileupload.js异步上传文件实例
May 19 Javascript
jquery实现仿新浪微博评论滚动效果
Aug 06 Javascript
Angular.js回顾ng-app和ng-model使用技巧
Apr 26 Javascript
浅谈jQuery hover(over, out)事件函数
Dec 03 Javascript
bootstrap daterangepicker双日历时间段选择控件详解
Jun 15 Javascript
bootstrap时间插件daterangepicker使用详解
Oct 19 Javascript
JavaScript模块管理的简单实现方式详解
Jun 15 Javascript
微信小程序 flexbox layout快速实现基本布局的解决方案
Mar 24 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
将OICQ数据转成MYSQL数据
2006/10/09 PHP
攻克CakePHP系列二 表单数据显示
2008/10/22 PHP
解析thinkphp中的M()与D()方法的区别
2013/06/22 PHP
PHP记录搜索引擎蜘蛛访问网站足迹的方法
2015/04/15 PHP
PHP用mb_string函数库处理与windows相关中文字符及Win环境下开启PHP Mb_String方法
2015/11/11 PHP
深入解析PHP的Laravel框架中的event事件操作
2016/03/21 PHP
使用JQuery进行跨域请求
2010/01/25 Javascript
jQuery.event兼容各浏览器的event详细解析
2013/12/18 Javascript
函数式 JavaScript(一)简介
2014/07/07 Javascript
Jquery 效果使用详解
2015/11/23 Javascript
jQuery使用animate实现ul列表项相互飘动效果示例
2016/09/16 Javascript
js实现仿购物车加减效果
2017/03/01 Javascript
js放到head中失效的原因与解决方法
2017/03/07 Javascript
jQuery实现动画、消失、显现、渐出、渐入效果示例
2018/09/06 jQuery
浅谈关于iview表单验证的问题
2018/09/29 Javascript
Vue实现一个无限加载列表功能
2018/11/13 Javascript
layUI使用layer.open,在content打开数据表格,获取值并返回的方法
2019/09/26 Javascript
vue引入静态js文件的方法
2020/06/20 Javascript
[04:42]2015国际邀请赛CDEC战队晋级之路
2015/08/13 DOTA
[02:00]DOTA2英雄COSPLAY闹市街头巡游助威2015国际邀请赛
2015/08/02 DOTA
通过Python来使用七牛云存储的方法详解
2015/08/07 Python
python 内置模块详解
2019/01/01 Python
python+os根据文件名自动生成文本
2019/03/21 Python
django 类视图的使用方法详解
2019/07/24 Python
Python 中如何实现参数化测试的方法示例
2019/12/10 Python
使用OpenCV获取图片连通域数量,并用不同颜色标记函
2020/06/04 Python
python安装读取grib库总结(推荐)
2020/06/24 Python
俄罗斯最大的在线手表商店:Bestwatch.ru
2020/01/11 全球购物
介绍一下游标
2012/01/10 面试题
什么是静态路由,其特点是什么?什么是动态路由,其特点是什么?
2013/07/26 面试题
大学生求职中的自我评价
2013/10/01 职场文书
自行车租赁公司创业计划书
2014/01/28 职场文书
国旗下的演讲稿
2014/05/08 职场文书
2014年最新个人对照检查材料范文
2014/09/25 职场文书
欠款起诉书范文
2015/05/19 职场文书
使用CSS定位HTML元素的实现方法
2022/07/07 HTML / CSS