用H5中的Canvas等技术制作海报
zhezhongyun 2025-01-20 18:11 55 浏览
在去年的时候也实现过合成海报的功能,不过当时时间仓促,实现的比较简单。
就一个旋转功能,图片也不能拖动放大,也不能裁剪。
这次有时间就实现一个功能稍微多点的海报。
一、概要
总共有三屏,第一屏是选择图片,第二屏是合成图片,第三屏是显示结果图,可保存分享朋友圈。
页面内容不是很多,分析起来也比较简单。
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下载:
相关推荐
- Python入门学习记录之一:变量_python怎么用变量
-
写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...
- python变量命名规则——来自小白的总结
-
python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...
- Python入门学习教程:第 2 章 变量与数据类型
-
2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...
- 绘制学术论文中的“三线表”具体指导
-
在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...
- Python基础语法知识--变量和数据类型
-
学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...
- 一文搞懂 Python 中的所有标点符号
-
反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...
- Python变量类型和运算符_python中变量的含义
-
别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...
- 从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序
-
在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...
- Python中下划线 ‘_’ 的用法,你知道几种
-
Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...
- 解锁Shell编程:变量_shell $变量
-
引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...
- 一文学会Python的变量命名规则!_python的变量命名有哪些要求
-
目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...
- 更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for
-
src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...
- C++第五课:变量的命名规则_c++中变量的命名规则
-
变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....
- Rust编程-核心篇-不安全编程_rust安全性
-
Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...
- 探秘 Python 内存管理:背后的神奇机制
-
在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- opacity 属性 (32)
- transition 属性 (33)
- 1-1. 变量声明 (31)
