在我们关于 Chrome Experiment A Journey Through Middle-earth 开发的 第一篇文章 中,我们专注于移动设备的 WebGL 开发。在本文中,我们讨论了在创建 HTML5 前端的其余部分时遇到的挑战、问题和解决方案。
让我们首先从屏幕尺寸和设备功能的角度谈谈如何调整此实验以在台式计算机和移动设备上工作。
整个项目基于一种非常“电影化”的风格,我们在设计上希望将体验保持在面向风景的固定框架内,以保持电影的魔力。由于该项目的很大一部分由交互式迷你“游戏”组成,因此让它们溢出框架也是没有意义的。
我们可以以着陆页为例,说明我们如何针对不同尺寸调整设计。

老鹰刚刚把我们丢到了登陆页面。
该网站具有三种不同的模式:台式机、平板电脑和移动设备。不仅仅是为了处理布局,还因为我们需要处理运行时加载的资产并添加各种性能优化。对于具有比台式电脑和笔记本电脑更高的分辨率但性能比手机差的设备,定义最终规则集并非易事。
我们正在使用用户代理数据来检测移动设备,并使用视口大小测试来针对其中的平板电脑(645 像素或更高)。实际上,每种不同的模式都可以渲染所有分辨率,因为布局基于媒体查询或 JavaScript 的相对/百分比定位。
由于这种情况下的设计不是基于网格或规则,并且在不同部分之间非常独特,因此它实际上取决于特定元素和场景来使用哪些断点或样式。不止一次发生过,我们已经用漂亮的 sass-mixins 和媒体查询设置了完美的布局,然后我们需要添加基于鼠标位置或动态对象的效果,最后用 JavaScript 重写了所有内容。
我们还在 head 标签中添加了一个具有当前模式的类,以便我们可以在我们的样式中使用该信息,就像在这个例子中(在 SCSS 中):
.loc-霍比特人标志 {
// 这里的默认值。
。桌面 & {
// 仅适用于桌面模式。
}
.tablet &、.mobile & {
// 移动设备和平板电脑的资产可能不同。
@media screen and (max-height: 760px), (max-width: 760px) {
// 断点特定的样式。
}
@media screen and (max-height: 570px), (max-width: 400px) {
// 断点特定的样式。
}
}
}
我们支持低至约 360x320 的所有尺寸,这在制作沉浸式网络体验时非常具有挑战性。在桌面上,我们在显示滚动条之前有一个最小尺寸,因为我们希望您尽可能在更大的视口中体验该网站。在移动设备上,我们决定允许横向和纵向模式一直到交互式体验,我们要求您将设备转为横向模式。反对这一点的论点是,它在肖像中不像在风景中那样身临其境。但是该站点的扩展性很好,因此我们保留了它。
DeviceOrientation 事件内容布局由断点和 CSS 控制,但我们还需要在 JavaScript 中处理事件以暂停游戏循环并保持正确的状态。事实证明,您不能依赖 window.orientation 中的值,因为它不是标准的并且因设备而异。相反,倾听事件,但寻找window.innerWidth并window.innerHeight确定方向。
重要的是要注意布局不应与输入类型、设备方向、传感器等特征检测混为一谈。这些特征可以存在于所有这些模式中,并且应该跨越所有模式。同时支持鼠标和触摸就是一个例子。视网膜对质量的补偿,但最重要的是性能是另一回事,有时质量越低越好。例如,画布是视网膜显示器上 WebGL 体验中分辨率的一半,否则它必须渲染四倍的像素数
通过在 Chrome DevTools 中模拟设备, 您可以轻松地在浏览器中尝试所有尺寸。在移动、平板电脑和桌面版本之间切换时,您必须重新加载站点以使用正确的依赖项和设置。
在开发过程中,我们经常在 DevTools 中使用模拟器工具,特别是在 Chrome Canary 中,它具有新的改进功能和大量预设。这是快速验证设计的好方法。我们仍然需要定期在真实设备上进行测试。原因之一是该网站正在适应全屏。在大多数情况下,带有垂直滚动的页面在滚动时会隐藏浏览器 UI(iOS7 上的 Safari 目前存在这方面的问题),但我们必须适应与此无关的所有内容。我们还在模拟器中使用了预设并更改了屏幕大小设置以模拟可用空间的损失。在真实设备上进行测试对于监控内存消耗和性能也很重要
在登陆页面之后,我们登陆中土世界的地图。您是否注意到 URL 发生了变化?该站点是一个单页应用程序,它使用History API来处理路由。
站点的每个部分都是它自己的对象,继承了诸如 DOM 元素、转换、资产加载、处置等功能的样板。当您浏览站点的不同部分时,会启动部分,将元素添加到和删除当前部分的 DOM 和资产已加载。
由于用户可以随时点击浏览器的后退按钮或通过菜单导航,因此创建的所有内容都需要在某个时候处理掉。超时和动画需要停止和丢弃,否则它们会导致不需要的行为、错误和内存泄漏。这并不总是一件容易的事,尤其是当最后期限临近并且您需要尽快将所有东西都做好时。
保持冷静并添加那些事件侦听器。练习为每个对象添加一个 dispose 函数。小心留下计时器和补间。如果补间,使用等效的TweenMax.killTweensOf(foo)或保存引用并阻止它们触发回调。移除运行时添加的 DOM 元素。定期使用分析工具来关注内存消耗和泄漏。
为了展示中土世界的美丽设置和人物,我们构建了一个图像和文本组件的模块化系统,您可以水平拖动或滑动。我们没有在此处启用滚动条,因为我们希望在不同的范围内具有不同的速度,例如在图像序列中,您将横向停止运动,直到剪辑播放完毕。
滚动条设置对您网站行为的期望。当网站劫持此控件时,可能会导致糟糕的用户体验。

瑟兰迪尔大厅 时间线
开发开始时,我们不知道每个位置的模块内容。我们所知道的是,我们想要一种在水平时间轴上显示不同类型媒体和信息的模板化方式,这将使我们能够自由地进行六种不同的位置演示,而无需重新构建所有内容六次。为了管理这一点,我们创建了一个时间线控制器,它根据设置和模块的行为来处理其模块的平移。
我们添加支持的不同模块是图像序列、静止图像、视差场景、焦点移动场景和文本。视差场景模块有一个不透明的背景,带有自定义层数,可以监听视口进度以获取确切位置。焦点转移场景是视差桶的一种变体,此外,我们为每一层使用两个图像,淡入淡出来模拟焦点变化。我们尝试使用模糊过滤器,但它仍然很昂贵,所以我们将等待 CSS 着色器来解决这个问题。文本模块中的内容是使用 TweenMax 插件Draggable启用的。您还可以使用滚轮或两指滑动来垂直滚动。注意throw-props-plugin当您滑动和释放时,它会增加投掷式的物理效果。模块还可以具有作为一组组件添加的不同行为。它们都有自己的目标选择器和设置。翻译以移动元素、缩放以缩放、用于信息叠加的热点、用于视觉测试的调试指标、开始标题叠加、耀斑层等等。这些将被附加到 DOM 或控制模块内的目标元素。有了这个,我们可以只使用一个配置文件来创建不同的位置,该文件定义了要加载和设置不同类型的模块和组件的资产。
从性能和下载大小方面来看,最具挑战性的模块是图像序列。有很多关于这个主题的阅读. 在手机和平板电脑上,我们将其替换为静止图像。如果我们想要在移动设备上获得良好的质量,那么解码和存储在内存中的数据就太多了。我们尝试了多种替代解决方案;首先使用背景图像和精灵表,但是当 GPU 需要在精灵表之间交换时,它会导致内存问题和滞后。然后我们尝试交换 img 元素,但也太慢了。从 spritesheet 到画布绘制帧是性能最高的,因此我们开始对其进行优化。为了节省每帧的计算时间,要写入画布的图像数据通过临时画布进行预处理,并使用 putImageData() 保存到数组中,解码并准备好使用。然后可以对原始 spritesheet 进行垃圾收集,并且我们仅在内存中存储所需的最少数据量。也许存储未解码的图像实际上更少,但是我们在以这种方式擦洗序列时获得了更好的性能。帧非常小,只有 640x400,但在擦洗过程中它们只会可见。当你停下来时,一个高分辨率的图像会加载并迅速淡入。
var canvas = document.createElement('canvas');
canvas.width = 图像宽度;
canvas.height = imageHeight;
var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);
var tilesX = imageWidth / tileWidth;
var tileY = imageHeight / tileHeight;
var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;
变量 i, j, canvasPasteTemp, imgData,
var currentIndex = 0;
var startIndex = 索引 * 16;
对于 (i = 0; i < tileY; i++) {
对于 (j = 0; j < tileX; j++) {
// 将每个图块的图像数据存储在数组中。
canvasPasteTemp = canvasPaste.cloneNode(false);
imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);
列表[ startIndex + currentIndex ] = imgData;
当前索引++;
}
}
精灵表是用Imagemagick生成的。这是GitHub 上的一个简单示例,展示了如何创建文件夹内所有图像的 spritesheet。
要将模块放置在时间线上,时间线的隐藏表示,显示在屏幕外,跟踪“播放头”和时间线的宽度。这可以只用代码来完成,但在开发和调试时使用可视化表示效果很好。实际运行时,它只是在调整大小时更新以设置尺寸。有些模块填充视口,有些模块有自己的比例,因此在所有分辨率下缩放和定位所有内容有点棘手,因此所有内容都可见且不会被裁剪太多。每个模块都有两个进度指示器,一个用于屏幕上的可见位置,一个用于模块本身的持续时间。在进行视差运动时,通常很难计算对象的开始位置和结束位置,以便与视野中的预期位置同步。
每个模块顶部都有一个微妙的黑色层,可以调整其不透明度,因此当它处于中心位置时它是完全透明的。这有助于您一次专注于一个模块,从而增强体验。
从正常运行的原型到无卡顿的发布版本意味着从猜测到知道浏览器中发生了什么。这就是 Chrome DevTools 是你最好的朋友的地方。我们花了很多时间优化网站。强制硬件加速当然是获得流畅动画的最重要工具之一。但也可以在 Chrome DevTools 中寻找彩色列和红色矩形。有很多关于这些主题的好文章,你应该阅读它们。删除跳帧的回报是立竿见影的,但当它们再次返回时的挫败感也是如此。他们会的。这是一个需要迭代的持续过程。
观看图层面板(仅在 Canary 中)和 Chrome DevTools 中的“绘制矩形”。例如,如果子元素需要每帧更新并被绘制,您应该调查是否可以更快地重新排列图层以尽可能减少需要绘制的区域。
我喜欢使用 Greensock 的 TweenMax 来补间属性、变换和 CSS。在容器中思考,在添加新层时可视化您的结构。请记住,现有的转换可以被新的转换覆盖。如果您仅对 2D 值进行补间,则在 CSS 类中强制硬件加速的 translateZ(0) 将替换为 2D 矩阵。要在这些情况下保持图层处于加速模式,请在补间中使用属性“force3D:true”来制作 3D 矩阵而不是 2D 矩阵。当您结合 CSS 和 JavaScript 补间来设置样式时,很容易忘记。
不要在不需要的地方强制硬件加速。当您想要对许多容器进行硬件加速时,GPU 内存可能会很快填满并导致不需要的结果,尤其是在内存有更多限制的 iOS 上。加载较小的资产并使用 css 扩展它们并在移动模式下禁用一些效果取得了巨大的改进。
内存泄漏是我们需要提高技能的另一个领域。在不同的 WebGL 体验之间导航时,会创建许多对象、材质、纹理和几何图形。如果当您离开并删除该部分时这些还没有准备好进行垃圾收集,它们可能会导致设备在内存不足时崩溃。

退出具有失败处置功能的部分。

好多了!
要找到泄漏,它是 DevTools 中非常简单的工作流程,记录时间线并捕获堆快照。如果有可以过滤掉的特定对象(例如 3D 几何图形或特定库)会更容易。在上面的示例中,事实证明 3D 场景仍然存在,并且存储几何的数组也没有被清除。如果您发现很难找到对象所在的位置,则有一个很好的功能可以让您查看此功能,称为保留路径。只需在堆快照中单击要检查的对象,即可在下面的面板中获得信息。在定位引用时,使用具有较小对象的良好结构会有所帮助。

在 EffectComposer 中引用了场景。
一般来说,在操作 DOM 之前三思而后行是有益的。当您这样做时,请考虑效率。如果可以,请不要在游戏循环中操纵 DOM。将引用存储在变量中以供重用。如果您需要搜索元素,请通过存储对战略容器的引用并在最近的祖先元素中搜索来使用最短路径。
如果您遇到布局错误,请延迟阅读新添加元素的尺寸或删除/添加类时。或者确保Layout 被触发。有时浏览器批量更改样式,下一次布局触发后不会更新。有时这确实是一个大问题,但它的存在是有原因的,所以试着了解它在幕后是如何工作的,你会收获很多。
如果可用,您可以选择通过全屏 API 在菜单中将站点置于全屏模式。但在设备上,浏览器也决定将其置于全屏模式。iOS 上的 Safari 之前有一个 hack 可以让你控制它,但它不再可用,所以在制作非滚动页面时,你必须准备好你的设计才能在没有它的情况下工作。我们可能会在未来的更新中对此进行更新,因为它破坏了很多网络应用程序。

实验的动画说明。
在整个网站中,我们有许多不同类型的资源,我们使用图像(PNG 和 JPEG)、SVG(内嵌和背景)、精灵表 (PNG)、自定义图标字体和 Adobe Edge 动画。我们将 PNG 用于元素不能基于矢量的资产和动画(spritesheets),否则我们会尝试尽可能多地使用 SVG。
矢量格式意味着没有质量损失,即使我们对其进行缩放。1 个文件用于所有设备。
图标字体在可扩展性方面与 SVG 具有相同的优势,并且可以代替 SVG 用于像图标这样的小元素,我们只需要能够在其上更改颜色(悬停、活动等)。图标也很容易重复使用,您只需要设置元素的 CSS“内容”属性。
在某些情况下,使用代码为 SVG 元素制作动画可能非常耗时,尤其是在设计过程中需要对动画进行大量更改时。为了改进设计师和开发人员之间的工作流程,我们使用 Adobe Edge 制作一些动画(游戏前的说明)。动画工作流程非常接近 Flash,这对团队有帮助,但也有一些缺点,尤其是在我们的资源加载过程中集成 Edge 动画,因为它带有自己的加载器和实现逻辑。
我仍然觉得在我们有一个完美的工作流程来处理网络上的资产和手工动画之前,我们还有很长的路要走。我们期待看到像 Edge 这样的工具将如何发展。随意在评论中添加对其他动画工具和工作流程的建议。
现在,当项目的所有部分都发布并且我们查看最终结果时,我必须说我们对现代移动浏览器的状态印象深刻。当我们开始这个项目时,我们对实现它的无缝、集成和高性能的期望要低得多。这对我们来说是一次很棒的学习经历,所有花费在迭代和测试上的时间(很多)提高了我们对现代浏览器如何工作的理解。如果我们想缩短这些类型项目的生产时间,从猜测到知道,这就是我们需要做的。

