我第一次想参加 Dev Derby 是在 2012 年 5 月的挑战,规则是需要使用 WebSockets。当时我认为可以使用 NodeJS 和 SocketIO。但是时间不断流逝,我最终没有想到任何酷炫的应用创意。
从那以后,我一直只是关注着每月的挑战,直到 2012 年 12 月的挑战:离线应用!
我再次非常兴奋地想要为 Dev Derby 做点什么,特别是因为我认为 应用缓存 和 本地存储 API 简直太棒了,是在考虑离线应用时需要使用的主要 API。
无论如何,在思考了 30 分钟离线应用的强大之处后,我决定是时候为挑战想点什么了。
作为一名丈夫和专业人士,很难抽出时间来构建这种挑战所需的酷炫东西。因此,我想知道我是否可以重用几个月前已经构建的一个演示,并添加必要的元素使其能够离线使用。查看我之前做过的演示,最适合挑战的是一个漫画生成器,我非常有创意地将其命名为 Comic Gen。
Comic Gen(源代码)能够在屏幕上添加和操作角色,并允许您将结果导出为 PNG 文件。
Ragaboom 框架
Canvas 是我注意到的第一个 HTML5 API。能够绘制任何东西、导入图像、编写文本并与它们进行交互非常酷。可能性似乎无限。
我开始使用 Canvas 进行我的第一个实验,没过多久我就意识到我可以构建一个框架来简化操作。那时我创建了 Ragaboom 框架。
当时,这是我用 JavaScript 编写的最先进的代码。今天再看它,我意识到它有多复杂、难用,以及我还能对其进行多少改进……好吧,我仍然计划这样做,但现在的责任和优先事项阻止了我这样做。
因此,我必须以某种方式测试 Ragaboom。然后我创建了一个名为 CATcher 的简单游戏。游戏的目标是在时间用完之前用篮子接住掉落的猫咪。玩家每获得 100 分即可获得 15 秒的奖励时间。
我的妻子帮我画了图,她说她很喜欢这个游戏。是的,没错……她还说我很帅……
CATcher 和 Ragaboom 对我来说是两项巨大的成就。我将这款游戏提交到一些 HTML5 演示网站以供发布,令人惊讶的是,许多人联系我讨论 JavaScript 高级编程和 HTML5。我最喜欢的是,有些人对改进框架有非常好的想法!
为什么是漫画生成器
在构建了 Ragaboom 之后,我开始考虑一个新的挑战,我可以在其中使用 Canvas 并改进框架。当时(现在仍然如此)关于 HTML5 的未来有很多讨论,人们开始争论 HTML5 是否有可能取代 Flash。
这正是我正在寻找的那种挑战:尝试用 Canvas 重现 Flash 中已经存在的东西。我见过几个使用 Flash 的漫画生成器。其中一些非常简单,而另一些则构建得非常好。
那一刻我决定我要做什么。我需要有人为我绘制角色。然后,我的朋友 Ana Zugaib,一位非常有才华的艺术家为我做了这些!她的作品简直太棒了!
计划演示
好的,我有了主题,我有了理由,现在我需要计划要创建的内容。构建漫画生成器时无需考虑太多。我需要的只是一个工具箱和一个放置角色的屏幕。
我希望用户能够选择不同的屏幕尺寸,并且他们应该可以选择将其作品导出为图像或类似的东西。
屏幕上的对象应该可以进行操作、移动、调整大小、翻转和从屏幕上移除。此时,我的脑海中突然冒出一个想法:我到底要怎么做这一切?!!
如何完成所有这些
哦,好吧,如果我想要挑战,我就得到了一个挑战。幸运的是,我需要的一些功能已经在 Ragaboom 框架中实现了,或者已经在 DOM API 中提供了。
所以我必须专注于他们没有提供给我的东西
- 如何水平翻转图像;
- 如何在不丢失当前内容的情况下调整屏幕大小;
- 如何将内容导出为 PNG 文件;
初始化对象
首先,我需要创建一个 Canvas 对象及其上下文,并在其上创建一个大的白色矩形。这将是我的漫画屏幕。
var c = $('#c')[0];
var ctx = c.getContext('2d');
var scene = new RB.Scene(c);
var w = c.width;
var h = c.height;
...
scene.add( scene.rect(w, h, 'white') );
scene.update();
scene.add
方法(来自我的框架)将一个新对象添加到屏幕上,如上所示,其中一个白色矩形被添加到屏幕上。接下来,屏幕会更新以显示迄今为止绘制的所有对象。在内部,框架会在应该绘制到屏幕上的对象中创建一个数组,存储其属性,如 x 和 y 位置以及它们的类型(矩形、圆形、图像等)。
scene.update 方法迭代该数组,在 Canvas 区域重新绘制每个对象。
对于工具栏,我创建了两个数组。第一个保存每个工具栏图像的 URL,第二个存储应放置在屏幕上的实际图像的 URL。工具栏图像数组的元素对应于实际图像数组。因此,第一个数组中的海盗图标与第二个数组中的实际海盗图像处于相同的位置,依此类推。
然后我必须迭代数组以构建按钮,并将它们中的每一个链接到相应的图像。之后,迭代的结果被附加到一个 DOM 元素。
//toolbar icons array
var miniUrls = ["sm01_mini.png", "sm02_mini.png", "sm03_mini.png", "sushi_mini.png", "z01_mini.png", "z02_mini.png", "z03_mini.png", "z04_mini.png", "balao.png", "toon01_mini.png", "toon02_mini.png", "toon03_mini.png", "toon04_mini.png", "toon05_mini.png", "toon06_mini.png"];
//actual images array
var toonUrls = ["sm01.png", "sm02.png", "sm03.png", "sushi.png", "z01.png", "z02.png", "z03.png", "z04.png", "balao.png", "toon01.png", "toon02.png", "toon03.png", "toon04.png", "toon05.png", "toon06.png"];
//building the toolbar
cg.buildMinis = function() {
var buffer = '';
var imgString = "<img src='toons/IMG_URL' class='rc mini'></img>";
var link = "<a href="javascript:cg.createImage('toons/IMG_URL')">";
for(var i=0; i < miniUrls.length; i++) {
buffer += link.replace(/IMG_URL/, toonUrls[i]);
buffer += imgString.replace(/IMG_URL/, miniUrls[i]) + '</a>';
}
lib.append(buffer);
$('#menuContainer').append( $('#instructs').clone() );
}
将对象添加到屏幕
当您通过 JavaScript 加载图像时,务必记住浏览器将异步下载该图像。这意味着您无法确定图像是否已加载并准备就绪。
要解决此问题,其中一种技术是使用 Image 对象的 onload 方法
var img = new Image();
img.onload = function() {
alert('the image download was completed!');
};
img.src = "my_image.png";
Ragaboom 框架使用了相同的技巧。这就是为什么图像方法的第二个参数是一个回调函数,当图像准备好使用时将触发该函数。
cg.createImage = function(url) {
scene.image(url, function(obj) {
obj.draggable = true;
obj.setXY(30, 30);
obj.onmousedown = function(e) {
currentObj = obj;
scene.zIndex(obj, 1);
scene.update();
}
scene.add(obj);
currentObj = obj;
scene.update();
pop.play();
});
}
在上面的示例中,图像存储在 objects 数组中,转换为可拖动状态并放置在坐标 x=30, y=30
处。然后将一个 mousedown
事件附加到对象,将其设置为当前对象。最后,画布会更新。
缩放
为了增加对象的大小,我只是在对象的宽度和高度上都添加了一小部分像素。在尝试减小对象大小时也执行了同样的操作,通过减去一部分像素。我只需要处理宽度和高度小于零的情况以防止出现错误。
为了提供流畅且统一的缩放,我决定应用当前宽度和高度的 5%,而不是使用固定数量的像素。
var w = obj.w * 0.05;
var h = obj.h * 0.05;
完整的“放大”函数如下所示
cg.zoomIn = function(obj) {
var w = obj.w * 0.05;
var h = obj.h * 0.05;
obj.w += w;
obj.h += h;
obj.x -= (w/2);
obj.y -= (h/2);
scene.update();
}
导出 PNG 文件
Canvas 对象有一个名为 toDataURL
的方法,它根据指定为参数的格式返回画布作为图像的 URL 表示形式。使用此方法,我创建了一个变量来存储图像 URL 表示形式并打开了一个新的浏览器窗口。
然后我创建了一个 Image
对象,使用 URL 的值设置 src
属性并将其附加到新窗口的文档中。
用户必须右键单击图像并自行“另存为”。我知道这不是最好的解决方案,但当时我能想到的只有这个。
var data = c.toDataURL('png');
var win = window.open();
var b = win.document.body;
var img = new Image();
img.src = data;
b.appendChild(img);
重新调整屏幕大小
对于屏幕大小调整,实际上没有任何不便。毕竟,Canvas 对象具有 width 和 height 属性。因此,我所要做的就是设置这些值,屏幕就会重新调整大小,对吧?你想多了……当您设置画布对象的 width 或 height 属性时,其上下文会以某种方式丢失。
为了解决此问题,我必须在重新调整画布对象大小后更新上下文的每个对象。那一刻我意识到了使用框架的优势。因为它保留了每个对象及其属性的信息,并且它完成了将每个图像重新绘制回画布上下文的全部脏活。
c.width = w;
c.height = h;
scene.update(); // thanks, confusing framework
最后考虑
我一直喜欢编程的前端部分。出于某种原因,我花了很长时间才意识到并接受这一点。我发现 JavaScript 是一种非常强大的语言,能够实现无数令人惊叹的功能。
几年前,我认为我永远无法构建像现在这样的一些东西。我很高兴我错了!
如果您喜欢编码,请花一些时间学习新事物。网络就在您身边。我的意思是,看看 MDN!您拥有成为优秀开发人员所需的一切。
您还在等什么才能成为一名优秀的开发者?
关于 Willian Carvalho
我是一位充满激情的开发者、丈夫、左撇子吉他手。在过去的 33 年里(大约),我一直在尽可能地微笑和欢笑。我是开放网络的忠实粉丝,并且痴迷于 Javascript 开发。我喜欢创造新事物、API、框架以及与 JS 相关的所有可能有用且有趣的东西!
2 条评论