案例研究:Google I/O 2013 实验
为了在会议注册开始前建立开发者对 Google I/O 2013 网站的兴趣,我们开发了一系列移动优先的实验和游戏,重点关注触摸交互、生成音频和发现的乐趣。 受到代码潜力和游戏力量的启发,当您点击新的 I/O 标志时,这种互动体验从 I 和 O 的简单声音开始。
有机运动
我们决定以一种在 HTML5 交互中不常见的不稳定有机效果来实现 I 和 O 动画。 拨入选项以使其变得有趣和被动需要一点时间。
弹性物理代码示例
为了实现这种效果,我们对代表两个形状边缘的一系列点进行了简单的物理模拟。 当任一形状被轻敲时,所有的点都会从轻敲的位置加速出去。 在被拉回之前,它们会伸展开来。
在实例化时,每个点都会获得一个随机的加速度量和回弹“弹力”,因此它们的动画效果不会一致,如您在以下代码中所见:
this.paperO_['vectors'] = []; // Add an array of vector points and properties to the object. for (var i = 0; i < this.paperO_['segments'].length; i++) { var point = this.paperO_['segments'][i]['point']['clone'](); point = point['subtract'](this.oCenter); point['velocity'] = 0; point['acceleration'] = Math.random() * 5 + 10; point['bounce'] = Math.random() * 0.1 + 1.05; this.paperO_['vectors'].push(point); }
然后,当被点击时,它们会使用以下代码从点击位置向外加速:
for (var i = 0; i < path['vectors'].length; i++) { var point = path['vectors'][i]; var vector; var distance; if (path === this.paperO_) { vector = point['add'](this.oCenter); vector = vector['subtract'](clickPoint); distance = Math.max(0, this.oRad - vector['length']); } else { vector = point['add'](this.iCenter); vector = vector['subtract'](clickPoint); distance = Math.max(0, this.iWidth - vector['length']); } point['length'] += Math.max(distance, 20); point['velocity'] += speed; }
最后,每个粒子在每一帧都减速,并在代码中使用这种方法慢慢恢复平衡:
for (var i = 0; i < path['segments'].length; i++) { var point = path['vectors'][i]; var tempPoint = new paper['Point'](this.iX, this.iY); if (path === this.paperO_) { point['velocity'] = ((this.oRad - point['length']) / point['acceleration'] + point['velocity']) / point['bounce']; } else { point['velocity'] = ((tempPoint['getDistance'](this.iCenter) - point['length']) / point['acceleration'] + point['velocity']) / point['bounce']; } point['length'] = Math.max(0, point['length'] + point['velocity']); }
有机运动演示
这是供您使用的 I/O 家庭模式。 我们还在此实现中公开了许多其他选项。 如果您打开“显示点”,您将看到物理模拟和力作用的各个点。 玩得开心。
重新换肤
一旦我们对家庭模式的动作感到满意,我们就想为两种复古模式使用相同的效果:Eightbit 和 Ascii。
为了完成这种重新蒙皮,我们使用了家庭模式下的同一画布,并使用像素数据来生成这两种效果中的每一种。 这种方法让人想起 OpenGL 片段着色器,其中检查和操作场景的每个像素。 让我们更深入地研究一下。
画布“着色器”代码示例
可以使用 getImageData 方法读取 Canvas 上的像素。 返回的数组包含每个像素的 4 个值,代表每个像素的 RGBA 值。 这些像素以巨大的阵列状结构串在一起。 例如,一个 2×2 的画布在其 imageData 数组中有 4 个像素和 16 个条目。
我们的画布是全屏的,所以如果我们假设屏幕是 1024×768(就像在 iPad 上),那么数组有 3,145,728 个条目。 因为这是一个动画,所以整个数组每秒更新 60 次。 现代 javascript 引擎可以足够快地处理循环和对如此多的数据采取行动,以保持帧率一致。 (提示:不要尝试将这些数据记录到开发者控制台,因为它会减慢浏览器的爬行速度或完全崩溃。)
以下是我们的八位模式如何读取家庭模式画布并放大像素以产生更块状的效果:
var pixelData = pctx.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height); // tctx is the Target Context for the output Canvas element tctx.clearRect(0, 0, targetCanvas.width + 1, targetCanvas.height + 1); var size = ~~(this.width_ * 0.0625); if (this.height_ * 6 < this.width_) { size /= 8; } var increment = Math.min(Math.round(size * 80) / 4, 980); for (i = 0; i < pixelData.data.length; i += increment) { if (pixelData.data[i + 3] !== 0) { var r = pixelData.data[i]; var g = pixelData.data[i + 1]; var b = pixelData.data[i + 2]; var pixel = Math.ceil(i / 4); var x = pixel % this.width_; var y = Math.floor(pixel / this.width_); var color = 'rgba(' + r + ', ' + g + ', ' + b + ', 1)'; tctx.fillStyle = color; /** * The ~~ operator is a micro-optimization to round a number down * without using Math.floor. Math.floor has to look up the prototype * tree on every invocation, but ~~ is a direct bitwise operation. */ tctx.fillRect(x - ~~(size / 2), y - ~~(size / 2), size, size); } }
八位着色器演示
下面,我们剥离了 Eightbit 覆盖层,然后看到下面的原始动画。 “杀屏”选项将向您展示我们通过错误采样源像素而偶然发现的奇怪效果。 当将八位模式调整为不太可能的纵横比时,我们最终将其用作“响应式”复活节彩蛋。 事故快乐!
画布合成
通过组合多个渲染步骤和蒙版可以完成的工作非常令人惊奇。 我们构建了一个 2D 元 球,它要求每个球都有自己的径向渐变,并且这些渐变在球重叠的地方混合在一起。 (您可以在下面的演示中看到这一点。)
为此,我们使用了两个独立的画布。 第一个画布计算并绘制元球形状。 第二个画布在每个球位置绘制径向渐变。 然后形状掩盖了渐变,我们渲染了最终的输出。
合成代码示例
这是使一切发生的代码:
// Loop through every ball and draw it and its gradient. for (var i = 0; i < this.ballCount_; i++) { var target = this.world_.particles[i]; // Set the size of the ball radial gradients. this.gradSize_ = target.radius * 4; this.gctx_.translate(target.pos.x - this.gradSize_, target.pos.y - this.gradSize_); var radGrad = this.gctx_.createRadialGradient(this.gradSize_, this.gradSize_, 0, this.gradSize_, this.gradSize_, this.gradSize_); radGrad.addColorStop(0, target['color'] + '1)'); radGrad.addColorStop(1, target['color'] + '0)'); this.gctx_.fillStyle = radGrad; this.gctx_.fillRect(0, 0, this.gradSize_ * 4, this.gradSize_ * 4); };
然后,设置画布以进行遮罩和绘制:
// Make the ball canvas the source of the mask. this.pctx_.globalCompositeOperation = 'source-atop'; // Draw the ball canvas onto the gradient canvas to complete the mask. this.pctx_.drawImage(this.gcanvas_, 0, 0); this.ctx_.drawImage(this.paperCanvas_, 0, 0);
元球演示
下面,您可以通过拖动每个球并从“O”中拉出新球来玩元球。 关闭蒙版以查看渐变画布。
结论
我们使用的各种技术和我们实施的技术(例如 Canvas、SVG、CSS 动画、JS 动画、Web 音频等)使该项目的开发非常有趣。
甚至还有比您在这里看到的更多的探索方式。 继续点击 I/O 标志,正确的序列将解锁更多迷你实验、游戏、迷幻视觉效果,甚至可能还有一些早餐食品。 我们建议您在智能手机或平板电脑上试用以获得最佳体验。
这是一个让您入门的组合:OIIIIII。 立即尝试: google.com/io
开源
我们已经开源了我们的代码 Apache 2.0 许可证。 您可以在我们的 Github 上找到它:http://github.com/Instrument/google-io-2013
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: CSS 绘制时间和页面渲染权重
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论