在去年的时候也实现过合成海报的功能,不过当时时间仓促,实现的比较简单。

就一个旋转功能,图片也不能拖动放大,也不能裁剪。

去年的实现可以参考《移动图片操作--上传》和《移动图片操作--预览旋转合成

这次有时间就实现一个功能稍微多点的海报。

一、概要

第一屏

第二屏 第三屏

总共有三屏,第一屏是选择图片,第二屏是合成图片,第三屏是显示结果图,可保存分享朋友圈。

页面内容不是很多,分析起来也比较简单。

1)每一屏的左右边距相同,上边距各不相同。

2)屏幕内的元素,大部分是居中,有些特殊边距的可用绝对定位,例如第一屏中父亲图与标语图,两张图有重叠部分。

3)第2和3屏中的按钮布局可以用Flex中的两端对齐。

4)4种按钮,可将背景制作成Sprite 图,方便重用。1种弹出框,1种Loading。

5)有3种动画,放大、脉冲以及旋转360°。

6)这次实现的难点是拖动、裁剪和旋转,需要经过逻辑计算高宽、坐标等。

二、涉及的知识点

1)Sprite图

移动端的Sprite图在前面一篇《一张H5游戏页引起的思考》曾重点介绍过。

在移动端的话,位置就是用百分比来计算。从上面的总览图中可以看到多种按钮背景,有几个就是字不一样,可以重复使用。

2)PrimusUI

PrimusUI是前面一段时间整理的一个微型UI库,为了提升开发效率,提取公用模块而制作的。

有多个模块可以使用,此次就用了三个模块normalize、layout与loading。

具体内容可以参考前面一段时间写的一篇介绍文《小身材大用途,用PrimusUI驾驭你的页面》。

3)High DPI Canvas

引入High DPI Canvas,是为了解决在高清屏的设备中,绘制在 canvas 中的图形(包括文字)都会出现模糊的问题。

在demo代码中有一张hidpi.html页面,就是在比较引入此插件后表现的区别,下图是在iphone6中展现的样子。

可以看到原生的比较模糊,而引入了插件后就变的清晰了。原理就是让Canvas中的1个像素等于屏幕中的1个物理像素,关于屏幕的概念可以参考《移动开发屏幕适配分析

下面是一段插件中的代码,就是计算devicePixelRatio(设备像素比)与webkitBackingStorePixelRatio(Canvas缓冲区的像素比),做个除法。

然后将Canvas的width和height根据这个比来放大,而CSS中的width和height再缩小回原来的,以此达到1像素的对应。

backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1; ratio = (window.devicePixelRatio || 1) / backingStore; if (ratio > 1) {
this.style.height = this.height + 'px';
this.style.width = this.width + 'px';
this.width *= ratio;
this.height *= ratio;
}

4)touch.js

touch.js是个开源的手势库,第二屏中的拖动和捏(双指放大缩小)就是通过这个库实现的。

touch.on(touchPad, 'drag', function(ev) {
//拖动逻辑
});
touch.on(touchPad, 'pinch', function(ev) {
//捏的逻辑
});

5)FileReader

使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象或者Blob对象来指定所要处理的文件或数据。

在执行上传插件的“change”中,就是通过此对象获取图片的data:URL。

var file = $(this)[0].files[0];
var reader = new FileReader();
reader.readAsDataURL(file); // 将文件以Data URL形式进行读入页面
reader.onload = function() {
var base64 = this.result;
};

三、实现

1)音频控制

为了营造父亲节气氛,特地选了首接地气的歌曲配合页面。

播放器就使用了HTML5标签“audio”。

<audio id="audio" src="music.mp3" type="audio/mpeg"></audio>

这里注意下,IOS是禁止自动播放音频的,解决办法就是不要自动播放,或者就是第一次点击页面触发播放。

剩下的就是音频标签绑定播放和停止,在触发的时候添加旋转或脉冲动画。

$audio.on("play", function() {
isAudioLoaded = true;
$music.addClass('music-rotate').removeClass('music-pulse');
}).on("pause", function() {
$music.removeClass('music-rotate').addClass('music-pulse');
});

2)上传图片

上传就是绑定file标签的“change”事件,除了前面说到的用FileReader获取图片的data:URL外,还将原图做了一次压缩。

压缩其实就是将图片放到Canvas中,然后用Canvas输出“jpeg图片”,并且质量是“0.7”,可以将一张800多KB的png图片压缩到50多KB。

还发现一个现象,如果用Canvas输出“png”的data:URL,会比原图还要大。

  

在“reader.onload”事件中除了压缩图片,还会保存此图的真实际宽度和高度,下面的旋转会用到尺寸,还保存了一条旋转信息的缓存。

var img = new Image();
img.onload = function() {
var src = poster.filterImage(img, this.width, this.height); //将图片进行压缩,减少页面大小
$frameImg.data('width', this.width); //实际宽度
$frameImg.data('height', this.height); //实际高度 var realImg = new Image();
realImg.onload = function() {
$frameImg.attr('src', realImg.src); //第三次载入Base64数据
};
realImg.src = src;
rotates[0] = {
src: src,
width: this.width,
height: this.height,
image: realImg
}; //用于旋转的缓存
};
img.src = base64;

3)拖拽、放大、缩小

此功能是需要与上面的touch.js手势库结合。

拖拽使用了CSS3的“translate3d”属性,而放大缩小使用了CSS3的“scale”属性。

function formatTransform(offx, offy, scale) {
var translate = 'translate3d(' + (offx + 'px,') + (offy + 'px,') + '0)';
scale = 'scale(' + scale + ')';
//var rotate = 'rotate('+deg+'deg)';
return translate + ' ' + scale;
}

原先旋转也想用CSS3的“rotate”属性实现,不过后面实现后,裁剪图片变得非常棘手,不能下手,最后是否决了这个实现方式。

4)旋转

为了解决裁剪的问题,每次旋转都会生成一张新的图片,并将这个图片信息缓存起来。

由于是新的图片,所以就可以直接按照原先的方式来裁剪了,也不用考虑旋转角度的问题。

旋转的逻辑放在“filterImage”中,当时在编写旋转的时候,碰到旋转后的图形变形的问题,后面用图片的实际宽高就解决了变形。

之所以变形是因为宽高用了CSS计算后的值,下图中的两个尺寸就是计算后的值。

旋转的代码就两行,rotate中“deg”就是旋转角度,这里是90。

ctx.rotate(deg * Math.PI / 180);
ctx.drawImage(image, 0, -canvas.width);

下图介绍了操作过程:

为了提升性能,每个方向的图片信息都会被缓存起来。

rotates[direction] = {src:src, width:this.width, height:this.height, image:realImg};//缓存

5)裁剪

比较复杂的一部分,计算图片相对于画框的left和top边距。

而right和bottom与以往的定义不同,这里是高度与宽度分别和top与left相加后的值。

再根据不同逻辑,分别计算画框与图片的X、Y、width和height的值。

最后计算实际图片的宽度与CSS计算后的图片宽度比,将这个值与图片的X、Y、width和height相乘,得出最终值。

这里注意下,在iphone5S中,如果图片的实际高度 < 计算后的高度,就会出现不显示。具体的逻辑在“intersect”方法中。

下图是某一种情况下的各个坐标值:

intersect: function($frame, $img) {
var imgX = 0,imgY = 0,imgW = 0,imgH = 0;
var frmX = 0,frmY = 0;
var imgOffset, frmOffset, left, right, top, bottom; imgOffset = $img.offset(); //图片的偏移对象
frmOffset = $frame.offset(); //画框的偏移对象
left = imgOffset.left - frmOffset.left - 3; //图片到边框左边的距离 去除3px的边框
right = left + imgOffset.width; //画框模型是border-box,所以图片宽度需要减去边框的宽度 就是574
top = imgOffset.top - frmOffset.top - 3; //图片到边框上边的距离
bottom = top + imgOffset.height; //图片在画框内
if (!(right <= 0 || left >= frmOffset.width || bottom <= 0 || top >= frmOffset.height)) {
if (left < 0) {
imgX = -left;
frmX = 0;
imgW = (right < frmOffset.width) ? right : frmOffset.width;
} else {
imgX = 0;
frmX = left;
imgW = (right < frmOffset.width ? right : frmOffset.width) - left;
} if (top < 0) {
imgY = -top;
frmY = 0;
imgH = (bottom < frmOffset.height) ? bottom : frmOffset.height;
} else {
imgY = 0;
frmY = top;
imgH = ((bottom < frmOffset.height) ? bottom : frmOffset.height) - top; }
} var ratio = $img.data('width') / $img.width(); //图片真实宽度 与 图片CSS宽度
//图片的实际高度不能低于计算后的高度 否则iphone 5S中就不显示
var imageHeight = imgH * ratio;
if (+$img.data('height') < imageHeight) {
imageHeight = $img.data('height');
}
return {
frame: {x: frmX,y: frmY,w: (imgW + 6),h: (imgH + 6)}, //此处画框是574,而画布是580
image: {x: imgX * ratio,y: imgY * ratio,w: imgW * ratio,h: imageHeight}
};
}

6)合成

合成其实就是将两张Canvas合并到一起。下面代码中的“drawImage”是自定义的一个方法,最终还是会调用Canvas的“drawImage”。

poster.drawImage(ctx, rotates[direction].image, poster.intersect($frame, $frameImg));
poster.drawImage(ctx, $word, poster.intersect($frame, $word));

Canvas的“drawImage”方法有多种参数组合。第三组有9个参数, 一开始还不是理解这几个参数的含义,后面去查了一下。

sx、sy对应的是图片的x、y坐标,而dx、dy对应的是画布的x、y坐标。

demo下载:

https://github.com/pwstrick/father

参考资料:

hidpi canvas

touch.js

一次关于canvas hidpi的2个bug解决过程

High DPI Canvas

High DPI Canvas中文版

结合HTML5的纯js图片上传

how-to-send-formdata-objects-with-ajax-requests-in-jquery

convert-base64-png-data-to-javascript-file-objects

那些年,我们曾一起走过

canvas 在高清屏下绘制图片变模糊的解决方法

用H5中的Canvas等技术制作海报的更多相关文章

  1. 关于H5中的Canvas API的探索

    Canvas API 是H5中比较炫酷的一部分内容.可以通过它动态的生成和展示图形.图表.图像以及动画.下面我将学习一下Canvas API. 最后有书籍和源码. 一.概述: 1.基本元素: 在网页上 ...

  2. 从web图片裁剪出发:了解H5中的canvas

    本篇内容不针对canvas文档对每个api进行逐个的详解! 本篇内容不针对canvas文档对每个api进行逐个的详解! 本篇内容不针对canvas文档对每个api进行逐个的详解! 重说三,好了,现在进 ...

  3. H5中标签Canvas实现图像动画

    一:主题部分 1.介绍 canvas可以实现画图功能,所以只要通过js的控制,就可以实现简单的动画效果. 这里主要是两个程序,一个小球来回上下弹跳,一个是吹气球. 2.弹跳程序 <!DOCTYP ...

  4. H5上传图片并使用canvas制作海报

    马上就要"十一"国庆节了,又恰逢公司已经三周岁了,所以市场部和产品共同策划了一个"正青春,共成长"的主题代言活动,准备在国庆节以及中秋节期间让公司员工和用户为公 ...

  5. H5的canvas绘图技术

    canvas元素是HTML5中新添加的一个元素,该元素是HTML5中的一个亮点.Canvas元素就像一块画布,通过该元素自带的API结合JavaScript代码可以绘制各种图形和图像以及动画效果. 1 ...

  6. HTML5中的canvas基本概念及绘图

    * Canvas(画布) * 基本内容 * 简单来说,HTML5提供的新元素<canvas> * Canvas在HTML页面提供画布的功能 * 在画布中绘制各种图形 * Canvas绘制的 ...

  7. 【javascript】html5中使用canvas编写头像上传截取功能

    [javascript]html5中使用canvas编写头像上传截取功能 本人对canvas很是喜欢,于是想仿照新浪微博头像上传功能(前端使用canvas) 本程序目前在谷歌浏览器和火狐浏览器测试可用 ...

  8. Unity Shader入门精要学习笔记 - 第16章 Unity中的渲染优化技术

    转自冯乐乐的 <Unity Shader 入门精要> 移动平台的特点 为了尽可能一处那些隐藏的表面,减少overdraw(即一个像素被绘制多次),PowerVR芯片(通常用于ios设备和某 ...

  9. 对于使用javaweb技术制作简单管理系统的学习

    近期在老师的引导下我们学习了利用Javaweb技术制作简单的管理系统,其中涉及到的技术很多,由于大多都是自学 对这些技术的理解还太浅显但能实现一些相关功能的雏形. (一).登录功能 在登陆功能中通过与 ...

随机推荐

  1. ASP.NET Aries 入门开发教程6:列表数据表格的格式化处理及行内编辑

    前言: 为了赶进度,周末也写文了! 前几篇讲完查询框和工具栏,这节讲表格数据相关的操作. 先看一下列表: 接下来我们有很多事情可以做. 1:格式化 - 键值的翻译 对于“启用”列,已经配置了格式化 # ...

  2. ABP文档 - 嵌入的资源文件

    文档目录 本节内容: 简介 创建嵌入的文件 暴露嵌入的文件 使用嵌入的文件 简介 一个web应用里,客户端包含javascript,css,xml等文件,这此文件被添加到一个web项目后,发布成独立的 ...

  3. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  4. 带你实现开发者头条APP(四)---首页优化(加入design包)

    title: 带你实现开发者头条APP(四)---首页优化(加入design包) tags: design,Toolbar,TabLayout,RecyclerView grammar_cjkRuby ...

  5. C#异步编程(二)

    async和await结构 序 前篇博客异步编程系列(一) 已经介绍了何谓异步编程,这篇主要介绍怎么实现异步编程,主要通过C#5.0引入的async/await来实现. BeginInvoke和End ...

  6. MAC下 mysql不能插入中文和中文乱码的问题总结

    MAC下 mysql不能插入中文和中文乱码的问题总结 前言 本文中所提到的问题解决方案,都是基于mac环境下的,但其他环境,比如windows应该也适用. 问题描述 本文解决下边两个问题: 往mysq ...

  7. 当web.config文件放置在共享目录下(UNC),启动IIS会提示有错误信息500.19,伴随有错误代码0x80070003和错误代码0x80070005的解决办法

    最近遇到一个很有意思的使用环境,操作人员将所有的网站应用内容投放到共享存储里面,并且使用微软的SMB协议将其以CIFS的方式共享出来,使用Windows Server 2008 R2的IIS将其连接起 ...

  8. JS继承之寄生类继承

    原型式继承 其原理就是借助原型,可以基于已有的对象创建新对象.节省了创建自定义类型这一步(虽然觉得这样没什么意义). 模型 function object(o){ function W(){ } W. ...

  9. 如何理解javaSript中函数的参数是按值传递

    本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...

  10. Android中使用ViewFlipper实现屏幕页面切换(关于坐标轴的问题已补充更改)

    屏幕切换指的是在同一个Activity内屏幕间的切换,ViewFlipper继承了Framelayout类,ViewAnimator类的作用是为FrameLayout里面的View切换提供动画效果.如 ...