使用 Famo.us 创建高性能移动 UI

让 JavaScript 构建的应用拥有原生代码的用户体验

有了 Famo.us 平台,用 JavaScript 编写的移动 Web UI 就能同本机代码实现相媲美。研究本文中的各种示例,了解 Famo.us 如何帮助您轻松地在移动应用中实现高性能用户体验。

Sing Li, 顾问, Makawave

http://www.ibm.com/developerworks/i/p-sing.jpgSing 是一位活跃的作家,曾经参与编写了Beginning JavaServer PagesProfessional Apache Tomcat 5Pro JSP -- Third Edition、Early Adopter JXTA、Professional Jini、Beginning J2ME:From Novice to Professional, Third Edition以及其他许多书籍。他经常为技术杂志撰稿,而且是语音网络(Voice Over Network,VON)和对等(Peer to Peer,P2P)技术的热心推动者。



2014 年 8 月 13 日

JavaScript 开发人员社区热烈祝贺 Famo.us 开源 UI 呈现框架的公共 beta 版于 2014 年春季发布。Famo.us 承诺解决一些最新的瓶颈问题,这些问题让 JavaScript 和 Web 技术无法统治移动开发领域:缓慢的 UI 和较差的用户体验 (UX)。

Famo.us 的目标是利用移动设备上的硬件图形处理单元 (GPU),以实现最高的呈现帧速率;为了实现赏心悦目的 UX,它还添加了一个复杂的物理引擎。为移动应用创建 UI 时,JavaScript 开发人员与 Objective-C、Swift 或 Java™ 开发人员相比不再处于劣势。

本文介绍了 Famo.us 的基本概念并探讨了其设计情况。然后,我们还介绍了多个工作示例,包括一个典型的移动应用 UI,您在使用 Famo.us 进行开发时可将其用作应用程序模板(参见 下载 部分,以便获得完整的示例代码。)

Famo.us 的工作原理

我们通过快速显示连续的页面(帧)来创建动画,当然这些页面中的元素会发生变化。帧速率 是指每秒显示的帧数。较高的帧速率可以在人的视觉中实现运动的错觉,因为人眼有一种 “视觉暂留” 特性(电影也运用了相同的原理)。为了在 Web 页面上实现动画效果,每一帧中各种元素的风格属性(位置、颜色和不透明度)都要进行更改。这些属性的更新速度最终决定了 UI 的最大帧速率。为了与 Web 应用程序进行交互,60 帧/秒 (fps) 是平滑的、类似于本机应用程序 UX 的最优速率。如果无法始终实现 60 fps 的速率,则会出现令人不快的 UX 效果,如动画呈现抽筋状 (jerkiness) 和丢帧(统称为 Jank)。

rAF

现代浏览器通过 rAF 机制支持各种动画库。requestAnimationFrame() 将回调函数作为一个参数。浏览器在下一个屏幕更新前调入回调函数,通常是在 60 fps 所对应的 16.7 ms 时间窗内完成该工作。动画库实现必须在 rAF 处理期间再次调用 rAF,以便设置下一帧的回调。不同浏览器之间对 rAF 的支持也不尽一致。Chrome 和 Firefox 现在默认支持 rAF。Safari rAF 支持需要在代码中使用一个特殊的厂商前缀。Famo.us 在内部使用一个 polyfill shim 解决方案来适应这些差异。

Famo.us 的核心是一个跨浏览器的高性能 UI 布局,并且使用其自己的动画和物理引擎优化了库,这些引擎是用 JavaScript 编写的。Famo.us 已进行了优化,可在最可能短的时间/帧内完成其工作(最近的 Famo.us 基准测试表明,Famo.us 只会占用每个 16.7 ms 帧的 1 到 2 ms,同时可处理典型的动画并且没有其他任何性能开销。)然后就能用最佳的帧速率来呈现目标 UI,这个最佳的帧速率通常最低是 60 fps。Famo.us 项目已承诺在所有后续版本中会保持或提高此 fps 性能。

在操作方面,Famo.us 将典型的以 DOM 为中心的布局操作以及 2D 或 3D 动画替换为自己的高性能替代物。图 1 展示了 Famo.us 的内部工作原理。通过浏览器的 requestAnimationFrame() (rAF) 回调函数为每个帧调用 Famo.us 引擎(一个单例 [singleton]对象)。每秒 60 次与 60 fps 或 1/60s (16.7 ms)/帧相对应。在 Famo.us 中,每次回调都被称为一次引擎 运作 (tick)

图 1. Famo.us 库的运行原理
展示 Famo.us 的内部运行方式

Famo.us API

Famo.us API 为开发人员提供了其自己的一组高级可组合对象,比如 表面 (surfaces)视图 (views)widget。您的 Famo.us 代码会创建要呈现的场景图(目前在 Famo.us 中称为呈现树)、连接事件处理,并指挥或安排各种动画。

Famo.us 场景图中的每个节点都是一个呈现节点。期间会按照呈现规范 构建规范 来处理场景图,而 Famo.us 呈现组件会在内部使用它们,让目标的更新工作更高效。

呈现组件会以受支持的帧速率(目前是 60 fps)来评估场景图,并向浏览器的 DOM、Canvas 元素或 WebGL 输出所需的更新(Famo.us 的 beta 实现只将浏览器的 DOM 作为目标)。Famo.us 的设计宗旨是向任何支持的目标输出内容,甚至可以同时向多个目标输出内容(支持多模式操作),只要它们在同一个屏幕上。

表面和可呈现对象

在 Famo.us 中,您要组合各种可呈现对象 并为其制作动画。最低级的常见可呈现对象是 Surface。在浏览器的 DOM 中显示的 Surface 是一个 <div> 元素。(如果检查 Famo.us Surface,就可以看到 <div>。)Famo.us 中其他特殊类型的 Surface 则通过其他 HTML5 元素来表示:

  • Surface 是一个 <div>
  • VideoSurface 是一个 <video>
  • ImageSurface 是一个 <img>
  • InputSurface 是一个 <input>
  • CanvasSurface 是一个 <canvas>

Surface 类有一个 content 字段。您要在该字段中添加在 Surface(在底层的 <div> 中)上呈现的 HTML。

Famo.us 并不干预您如何或者在何处获得此 content,这是一个字符串,将呈现为 HTML。该字符串是任何模板、数据源或数据绑定技术的一个天然集成点。另外,您还可以操纵微型 DOM,这是通过 HTML 在被呈现的 Surface 上形成的,但布局、容纳或动画工作除外,这些工作应该通过该 Famo.us 来完成。

使用 Famo.us 进行编程时,在性能和呈现灵活性方面可能会获益匪浅,代价是用直接的立即模式操纵页面的 DOM。

立即模式与保留模式

Famo.us 本身是个保留模式 库。基本上,您可以用声明的方式描述对象场景图(树)的外观和行为。Famo.us 引擎构建一个内存型的表示,然后指出如何有效地更新 DOM(一次一帧)来显示它。但是,目前的 Web 开发人员更熟悉立即模式的 DOM 操作(就像在 jQuery 中一样),因为您的代码可以直接添加或删除元素并修改属性,从而实现 UI。使用 Famo.us 进行编程时,可在性能和呈现灵活性方面获益匪浅,代价是用直接的立即模式操纵页面的 DOM。

比较各个 3D 呈现库

Famo.us 的这种架构类似于 WebGL 3D-呈现 JavaScript 库,包括 Three.js 和 SceneJS。了解它们的相似之处和差别有助于快速跟踪对 Famo.us 的掌握情况:

  • 在两种架构中,帧呈现是通过浏览器 rAF 触发的。
  • 在 3D 呈现库中,您可在场景图中定位并放置三角形和 2D 对象(如墙壁或地板)以及更高级别的 3D 几何图形(如棱锥、球体、立方体或多边形网格)。在 Famo.us 中,您可在场景图中定位并放置 2D 表面或更高级别的对象,如视图或 widget。
  • 在 3D 呈现库中可转换一个对象,方法是在场景图中添加和链接转换节点,其中该对象是一个叶对象。在 Famo.us 中,可在场景图中添加和链接修饰符 (modifier) 节点,其中一个表面或其他可呈现对象是叶对象。
  • 在 SceneJS 中,您可使用 JSON 指定复杂的场景图,并通过图节点上的 ID 属性来修改它。Famo.us 有一个 Scene 组件,您可使用它通过 JSON 来构建复杂的场景图;可通过呈现节点上的 ID 属性来修改它。
  • 在 3D 库中,根据 rAF 回调为对象属性设置动画和补间。在 Famo.us 中,会根据 Famo.us 引擎 的每次运作 (tick) 来更新和补间表面(更常用的是可呈现对象)的属性,这些引擎运作是通过浏览器的 rAF 回调来驱动的。
  • 3D 库通常包括一个物理引擎,让动画更为真实。Famo.us 则包含一个全面的物理引擎,让动画对用户而言更直观。
  • 即使一个 3D 场景中的所有对象都由三角形组成,开发人员也很少需要处理各个三角形,因为 3D 呈现库通常会包括常用的几何形状网格,如球体和棱锥。即使 Famo.us 中的基本可呈现对象是表面,您也很少使用单个表面,因为库中包含了很多预先制作的高级 UI 视图和 widget。
  • HTML 在 3D 库中不承担任何角色,除了 Canvas 元素,因为 3D 视口要在该元素上呈现。在 Famo.us 中,HTML 是您在表面上呈现的内容,但是 HTML 不再定义 UI 页面的结构;该结构是通过 JavaScript 代码来指定的。无需再管理大量的 < <div><table> 标记来控制布局;并且所用的 CSS 只影响元素风格,不会影响布局。(HTML 仍很重要,因为目标为 DOM 时所有视觉元素都是 HTML 元素。)
  • 3D 库的呈现组件通常将 Canvas、WebGL 和 SVG 输出作为目标 — 我更喜欢 WebGL,因为它几乎可以直接访问底层的 3D-呈现 GPU 硬件。Famo.us 呈现组件的目标是 Canvas、DOM 和 WebGL。Famo.us 有一个 DOM updater,它使用领先浏览器提供的已知 GPU 优化来实现其性能目标。

使用 Famo.us

运行 Famo.us 的三种方法

启动并运行 Famo.us 的最简单方法是下载 Famo.us 启动包。解压缩 .zip 文件并单击示例网页,查看 Famo.us 是否正在运行。启动包的代码会通过一个内容交付网络来加载 Famo.us。

您可以在 Famo.us 大学 中轻松地体验一下 Famo.us。在这个在线电子学习平台上,您可以修改示例 Famo.us 代码并在旁边的输出窗口中立即查看修改效果。

如果计划在自己的服务器上运行 Famo.us 应用,可使用 Famo.us Toolbelt(基于 gruntbower)。有了 Toolbelt,通过使用基于 yeomangenerator-famous 包,可以利用最新的 Famo.us GitHub 存储库代码来创建一个开发-测试-部署环境。

下面看看实际的 Famo.us 应用如何帮助您理解各种概念。图 2 中的示例是 Famo.us 启动包中包含的默认应用程序,并且也是 generator-famous 包默认生成的(参见 运行 Famo.us 的三种方法)。该应用程序会围绕 y 轴旋转 Famo.us 徽标。

图 2. generator-famous 生成的默认应用程序
默认 Famo.us 应用程序中旋转徽标的屏幕截图

在 main.js 中,通过清单 1 中所示的代码创建惟一的 ImageSurface

清单 1. 创建 ImageSurface(呈现到 DOM <img>)
var logo = new ImageSurface({
    size: [200, 200],
    content: '/content/images/famous_logo.png',
    classes: ['backfaceVisibility']
});

设置 Famo.us 对象实例化方面的选项

Famo.us 对象包含一组默认的选项。创建对象实例时,可传递一个 options 对象(由键值对组成)作为参数。在参数中指定的值会覆盖默认值 — 在整个 Famo.us 中使用的一种标准模式。

在清单 1 中,content 选项对应于底层 <img> 标记的 src 属性。classes 选项将 CSS 类添加到 HTML 元素。backfaceVisibility CSS 类确保在徽标转动时,用户可以看到徽标的后面,该类在 app.css 中定义:

.backfaceVisibility {
  -webkit-backface-visibility: visible;
  backface-visibility: visible;
}

alignorigin

origin 属性指定在进行旋转转换操作时,元素围绕哪个锚点进行旋转。align 属性指定将子对象的原点放在父对象显示区域中的何处。两个属性的值范围是 [0,0] 到 [1,1],您可使用相同的约定(如 图 3 中所示)来指定它们。例如,属性 origin = [0, 0]align = [0, 0] 将元素放在其父元素内的左上角,其中元素的点 (0,0) 与父元素的点 (0,0) 对齐。之后都要相对于元素的坐标 (0,0) — 即元素左上角,而不是其中心来执行任何元素旋转操作。如果 alignundefined(未指定),则默认值为 origin

Context— 一个与 DOM 节点相关联的微型 DOM 管理器 — 管理一个场景图,通过节点内的所有 DOM 来表示。通常您只有一个 context 实例(除非正在处理特殊的项目,如多个透视图或平视显示器)。

Modifier 是 Famo.us 场景图中的呈现节点,可修改其下的呈现节点的一些属性(通常是对其应用转换)。可以将多个 Modifier 链接起来,在这种情况下转换矩阵是组合到一起的,并且场景图的叶对象始终是可呈现对象。

在这个例子中,centerSpinModifier 包含一个 origin 属性和一个 transform 属性。相对于包含它的父对象来指定 origin 属性 — 在这个例子中是 Famo.us context。图 3 显示了指定 Famo.us origin 的约定。

图 3. 设置 origin 属性的约定
3x3 网格中标记了位置,显示了 origin 属性设置约定

图 3 中,在 3x3 网格中排列了 9 个点。[0.0] 位于左上角,[1,1] 位于右下角。[0.5, 0.5] 位于中间。其他各点都遵从该约定,如 [1,0] 位于右上角,[0,1] 位于左下角。

centerSpinModifier 中的 transform 属性返回一个函数,此函数通过 Transform.rotateY() 绕 y 轴旋转:

  var initialTime = Date.now();
  var centerSpinModifier = new Modifier({
      origin: [0.5, 0.5],
      transform : function() {
          return Transform.rotateY(.002 * (Date.now() - initialTime));
      }
  });
mainContext.add(centerSpinModifier).add(logo);

此代码完成的这个简单场景图如图 4 所示。

图 4. 默认应用程序的场景图
展示默认应用程序的场景图。从上到下的节点分别是 Context、Rotation modifier 和 logo ImageSurface。

现在 Famo.us 引擎会评估内存中的场景图,并通过 rAF 的驱动在每一帧上高效地更新 DOM,约每秒更新 60 次。每次 centerSpinModifer 会检查自 initialTime 开始以来所经过的时间并围绕 y 轴递增式地旋转徽标。您可轻松地修改 0.002 常量,从而改变旋转的速度。

总结:

  1. 您创建 logoImageSurface
  2. 您创建 Famo.us Modifier
    • 设置徽标在 context 的中心围绕徽标自己的中心 (origin = [0.5 0.5]) 旋转
    • 使用可围绕 y 轴进行递增式旋转的 transform
  3. 您可将 Modifier 添加到场景图中,然后直接在 Modifier 的下面添加 logo ImageSurface
  4. Famo.us 引擎处理该场景图并以 rAF 速率更新 DOM,徽标则不停顿地旋转。

扩展示例

为了让默认示例更复杂一些,下一个示例会旋转 100 个徽标实例,以 10x10 方形的形式进行布局,并交替绕 x 轴和 y 轴旋转。图 5 显示了最终可用的应用程序。

图 5. 扩展后的应用程序可旋转 100 个徽标实例
扩展后的应用程序的屏幕截图,具有 100 个旋转的徽标示例

图 6 显示了必须为此示例构建的场景图。

图 6. 扩展后的应用程序的场景图
展示旋转徽标的应用程序的场景图

您可以看看这个版本是如何扩展以前示例的:

  • 场景图中现在有 100 个分支,而不是 1 个。
  • 每个分支现在都有两个 modifier。在旋转 modifier 将首个徽标移到所需的位置之前,创建并添加了另一个转换 modifier。

清单 2 中所示的代码创建了 modifier 并将其添加到每个徽标的 context 中,这些代码与第一个示例中的代码类似。其中包含了计算工作,以动画和更新每个帧。

清单 2. 扩展后的代码示例,可旋转 100 个徽标
var mainContext = Engine.createContext();
var initialTime = Date.now();
function rotY() {
        return Transform.rotateY(.002 * (Date.now() - initialTime));
    }
function rotX() {
        return Transform.rotateX(.002 * (Date.now() - initialTime));
    }

for (var i=0; i< 10; i ++)
  for (var j=0; j<10; j++) {
    var image =
        new ImageSurface({
            size: [50, 50],
            content: '/content/images/famous_logo.png'
        });
    var transMod =
       new Modifier({
              size: image.getSize.bind(image),
              transform: Transform.translate(j * 50, i * 50, 0)
            }
        );

    var rotMod =
        new Modifier({
            origin: [0.5, 0.5],
            // xor
            transform : (((i % 2) !== (j % 2)) ?  rotY  : rotX)
        });

    mainContext.add(transMod).add(rotMod).add(image);

此应用程序有两个单独的转换函数,rotY 用于围绕 y 轴旋转,rotX 用于围绕 x 轴旋转。用嵌套的 i-j 循环形式创建场景图的各个分支。两个 modifier 被添加到每个分支中,分别名为 transMod (将徽标影像移动到位)和 rotMod(围绕徽标的原点旋转徽标)。

为了交替绕 x 轴和 y 轴进行旋转,rotModtransform 属性被交替地更改:

transform : (((i % 2) !== (j % 2)) ?  rotY  : rotX)

与第一个示例中一样,您设置内存中的场景图,Famo.us 引擎则负责处理它并以 rAF 速率更新 DOM。


动画的过渡和补间

在 UI 创建方面,通常要使用一定时间的动画。一个例子是在到达滚动列表的末尾时,用户可以观察到的 “反弹” 效果。另一个例子是将牌从背面翻到正面。

Famo.us 通过 Transitionable 类支持过渡效果,这个类代表了可逐步进行过渡的属性。下面的示例显示了如何使用两个补间过渡。

运行该应用程序时,在页面中间的 Famo.us 滚动视图内会显示一个 developerWorks 文章列表。该列表在前 10 秒以恒定的速度绕 y 轴旋转。然后在下个 10 秒钟突然加速并剧烈运动。虽然这个时间有限的动画已经就位,但是您仍可滚动列表(如果使用桌面浏览器,可使用鼠标的滚轮)。图 7 显示了该应用程序的执行效果。

图 7. 通过补间过渡动画,正在滚动的文章列表绕 y 轴旋转
正在旋转的文章列表的屏幕截图

像正在滚动文章列表这样的视图是预制的高级 Famo.us 组件(此处是 ScrollContainer),通过组合这些组件可更轻松地创建 UI。下个示例详细展示了 Famo.us 视图和 widget。对于现在,理解滚动列表是有一组有序的 Famo.us Surface 组成的就足够了。清单 3 显示了列表创建代码。

清单 3. 创建一个文章滚动列表
function createArticlesList() {
        artListSVC = new ScrollContainer({
            scrollview: {direction: Utility.Direction.Y}
        });
        var lines = [];
        artListSVC.sequenceFrom(lines);

        for (var i in articles)  {
            var surf = new Surface({
                content: '<div class"a-title">' + articles[i].title + '</div>',
                size: [undefined, 50],
                properties: {
                    textAlign: 'left',
                    color: 'black'
                }
            });
            surf.addClass('article-cell');
            surf.addClass('backfaceVisibility');
            surf.artIdx = i;
            surf.pipe(eh);
            lines.push(surf);
        }
        artListSVC.container.addClass('backfaceVisibility');
        artListSVC.container.addClass('borderbox');

    }

清单 3 中创建了一个 Famo.us Surface 数组,名为 lines。创建的每个 Surface 都显示一个 developerWorks 文章的名称。还创建了名为 artListSVC 的 Famo.us ScrollContainer,并且使用其 sequenceFrom() 方法和 lines 数组来配置滚动列表。

编程实现补间过渡

artListSVC 这样的视图也是一个可呈现对象(将可呈现对象作为叶对象来管理其自己的内部场景图)。可通过一个或多个 modifier 来转换视图,并将其添加到 context 的场景图中,如前面的示例所示。将 artListSVC 添加到 context 的代码是:

var sm = new StateModifier({align:[0.5, 0.5], 
         origin: [0.5, 0.5]});
mainContext.add(sm).add(artListSVC);

StateModifier 是一个 modifier,用于维护状态(在内部是通过 Transitionable 完成的)。使用补间过渡来制作动画时,您只需指定开始和结束状态(也称为关键帧)。补间过渡利用插值方法加入中间状态值并在每个运作 (tick) 将其提供给呈现引擎。您无需在自己的代码中计算或维护这些中间状态。

清单 4 显示了进行补间过渡的编程代码。

清单 4. 使用补间过渡制作动画
Transitionable.registerMethod('tween', TweenTransition);
sm.setTransform(Transform.rotateY(Math.PI), {method: 'tween', 
        curve:'linear', duration:10000},
        function() {
              sm.setTransform(Transform.rotateY(2 * Math.PI), 
               {method: 'tween', duration: 10000,
                curve: 'spring'});
           });

缓动

进行补间期间,在您指定的补间 固定点之间通过插值方法添加属性值。默认情况下此插值是线性的,但补间引擎通常也支持缓动 特性,您可在插值期间指定一个速率更改控制曲线(例如提高四次插值或减小四次插值)。Famo.us 通过famous.transitions.Easing 提供十多个缓动曲线,您可在补间时使用这些特性。

清单 4 中的代码首先将 TweenTransition 作为 tween 方法向 Transitionable 注册。然后使用 StateModifiersetTransform() 方法添加补间后的 rotateY 转换。setTransform() 方法将一个转换作为首个参数,Transitionable 作为第二个参数,一个完成的回调函数作为第三个参数。

清单 4 中,首个有动画的过渡持续 10 秒钟,滚动列表以线性速度绕 y 轴旋转。首个补间完成后,激发回调函数,然后第二个补间使用 spring 曲线,在接下来的 10 秒钟内实现快速的激烈运动。

并非必须显式地注册 TweenTransition,因为如果没有为 Transitionable 制定 method 属性,则 Famo.us 默认使用 TweenTransition。但是,清单 4 展示了如何注册另一个过渡方法 — 如通过 Famo.us 物理引擎实现过渡。(Famo.us 物理引擎超出了本文的介绍范围。)

呈现透视图

呈现透视图(由您指定,单位为像素)将查看者的 “照相机” 与要呈现的场景之间的距离关联起来。使用 Context.setPerspective() 设置该值。较小的值可让查看者距离要呈现的对象更近一些,同时保持具有相同的视野,有点类似于照相机上的广角镜头。通过变化透视图,您可改进很多动画的观感。在这个例子中,将透视图设为 500 可实现更为生动的效果。


将 Famo.us 应用到一个典型的移动应用程序 UI

到目前为止,我们的示例都将 Famo.us 用作一个 3D 呈现库,使用 2D 表面时除外 — 纯粹的对象动画。下一个示例确认了这种组合风格是有效的并且将动画转换应用到移动 UI 的构建上。

图 8 显示了一个常见的移动应用程序布局。导航栏位于顶部,会根据 UI 的状态来激活 "back" 和 "more" 按钮(图 8 中未显示)。底部是一个选项卡栏,其中包含一组切换按钮(可使用 CSS 设置每个按钮的风格),用于选择在内容区域中显示的各个条目。内容区域位于中间,其大小取决于设备的大小。

图 8. 在 Famo.us 中构成移动应用 UI
典型移动 UI 补间的屏幕截图,带注释

未包含数据绑定

Famo.us 库是一个 UI 创建库,未包含任何数据绑定或模板技术。在移动应用程序示例中,数据源被封装在 DataSource 类中。通过该数据源,可获得文章和视频列表并在 UI 中显示它们。您可在 Famo.us 中使用任何数据访问技术。

通常,应用程序会包含一个较小的条目可滚动列表,让用户可以进一步查看列表。在这个示例应用程序中,您可在 developerWorks 文章列表和开源电影列表之间进行选择。

用户选择某个条目后,内容区域发生变化,以显示所选的条目。可能仅在内容区域中打开此条目(即页眉和页脚仍旧可见),或者它占据整个屏幕(挡住了页眉和页脚)。

试用应用程序

在您的移动浏览器中试用一下应用程序:

应用程序启动时,会显示一个文章列表,如图 9 所示。

图 9. 应用程序显示多个 developerWorks 文章
可列出多个文章的示例应用程序屏幕截图

如果触摸选项卡栏中的 Videos 按钮,则显示开源电影列表,如图 10 所示。

图 10. 应用程序显示开源视频列表
可列出多个视频的示例应用程序屏幕截图

带宽警告

在某些设备或浏览器上,选择视频后系统可能会下载整个大文件。

如果触摸列表中的某个视频,则会在内容区域中加载并播放它,如图 11 所示。

图 11. 应用程序播放电影
可播放所选视频的示例应用程序屏幕截图

在图 11 中,注意导航栏现在显示一个 back 按钮。如果触摸此按钮,则会再次显示视频列表。再次触摸 Articles 可重新显示文章列表。现在触摸某个文章名称。应用程序会加载并显示所选的文章,如图 12 所示。

图 12. 应用程序显示所选的文章
应用程序显示所选文章的屏幕截图

使用 Famo.us 视图和 widget

组合使用 Famo.us 视图和 widget 会让移动应用程序 UI 的创建变得更直接。

Famo.us 中的事件

通过使用各种事件,Famo.us 组件可以用松散耦合的方式与其他组件进行通信。通过创建 EventHandler 实例,可以发出和接收各种事件。视图通常有内置的进入和发出事件处理程序。pipe() 方法将事件推送到另一个事件处理程序,subscribe() 方法从另一个事件处理程序处获得事件。

表面(以及通常的可呈现对象)和视图可以组合到 widget 中。视图可以在托管的可呈现对象之间包含复杂的协调和交互逻辑。通过使用 Famo.us 中所支持的事件机制,视图可以接收、处理和发出事件。Widget 本身可以呈现节点,可将这些节点添加到 context 的场景图中作为叶对象。Famo.us 附带了一组随时可用的视图和 widget:

  • Scrollview 控制一组可呈现对象(沿 x 或 y 方向)的连续列表并且允许通过触摸或使用鼠标来滚动浏览该列表 — 通常是通过一系列的表面。
  • HeaderFooterLayout 管理三个可呈现对象:一个指定大小的页面和页脚,以及一个大小可变的内容区域。该视图用于布局示例的移动 UI。
  • EdgeSwapper 是一个容器,可管理多个可呈现对象的显示工作,方法是从父对象的边缘将可呈现对象滑入。示例移动 UI 使用该视图显示两个可滚动列表。
  • ScrollContainer 这个视图中包含一个 Scrollview 和一个托管的 Surface,后者用于剪辑 ScrollView 的显示内容。示例移动 UI 在 HeaderFooterLayout 的内容区域中使用 ScrollContainer 来显示文章或视频的列表。

示例移动 UI 使用这些 widget:

  • NavigationBar 是一个微型应用程序-视图,它管理标题表面以及两个可点击表面(代表导航栏的 "back" 和 "more" 按钮)的显示工作。该 widget 发出 backmore 事件。
  • TabBar 管理 widget 栏的水平或垂直布局。(默认是一个切换按钮。选择一个托管的 widget 时,会使用一个 select 事件发出其相应的 ID。
  • ToggleButton 是一个按钮,状态为 on 或 off,拥有显示两个托管的表面。

使用可用的视图和 widget,移动 UI 变成了图 13 中所示的场景图。

图 13. 移动应用程序 UI 的场景图
展示示例应用程序 UI 的场景图

虽然 context仍位于树的根部,但无法再轻松分辨出 modifier 和树的叶对象。每个组合的 Famo.us 视图都封装了组件的管理细节,提供预期的用户交互和行为 — 并且无需您再进行编码工作。

要创建移动 UI,可编写代码建立场景图;然后 Famo.us 引擎会处理它并以 rAF 速率更新 DOM:

  1. 创建文章列表 — 一个 Famo.us ScrollContainer 包含一个 Scrollview 来管理 Surface 列表(列表中的各个单元),每个文章一个单元:
    function createArticlesList() {
            artListSVC = new ScrollContainer({
                scrollview: {direction: Utility.Direction.Y}
            });
            var lines = [];
            artListSVC.sequenceFrom(lines);
    
            for (var i in articles)  {
                var surf = new Surface({
                    content: '<div class="a-title">' + articles[i].title + 
                                    '</div><div class="a-desc">' + articles[i].desc + '</div>',
                    size: [undefined, 100],
                    properties: {
                        itemType: 'article',
                        listIndex: i,
                        textAlign: 'left',
                        color: 'black'
                    }
                });
                surf.artIdx = i;
                surf.pipe(eh);
                lines.push(surf);
            }
        }
    function createWebSurface() {
            wb = new Surface(
                );
       
        }

    注意突出显示的 content 属性,它被设为用于呈现列表中某个单元的 HTML,以及相关联的 CSS 类。

    itemTypelistIndex 是两个自定义属性,用于确定在 click 事件处理程序中选择的实际数据条目。

  2. 创建视频列表。(代码此处未显示,与步骤 1 中的代码类似)。
  3. 创建一个 Surface 来显示所选中的文章:
    function createWebSurface() {
            wb = new Surface(
                );
       
        }
  4. 创建一个 Surface 来显示所选中的视频:
    function createVideoSurface() {
         vs = new VideoSurface(
             {
                 size: [undefined,undefined],
                 autoplay: true
             }
             );
    
    }
  5. 创建 NavigationBar widget 并将其添加到页眉中:
    function addHeader() {
         nb = new NavigationBar({
             size: [undefined, 75],
             content: 'dW Famo.us',
             moreContent: '',
             backContent: '',
             properties: {
                 lineHeight: '75px'
             }
         });
         layout.header.add(nb);
         eh.subscribe(nb);
         eh.on('back', function() {
             rc.setOptions({
                 inTransition: false,
                 outTransition: true
             });
             if (backTarget !== undefined)
                 rc.show(backTarget);
             setNavbarBack(false, undefined);
         });
    }
  6. 创建 EdgeSwapper 视图并将其添加到内容区域。该控制器将文章列表、视频列表、一个文章的显示或一个视频的显示交换到布局中:
    function addContent() {
       rc = new EdgeSwapper({
           overlap: false,
           outTransition: false,
           size:[undefined, undefined]
           });
       layout.content.add(rc);
    
    }
  7. 创建选项卡栏并将其添加到页脚:
    function addFooter() {
        var tb = new TabBar({
        });
        layout.footer.add(tb);
        tb.defineSection(0,{content: 'Articles', onClasses: ['tabbuton'], offClasses: ['tabbutoff']});
        tb.defineSection(1,{content: 'Videos', onClasses: ['tabbuton'], offClasses:['tabbutoff']});
        tb.select(0);
        eh.subscribe(tb);
        eh.on('select', function(but) {
           rc.setOptions({
                inTransition: false,
                outTransition: false
            });
    
          switch (but.id) {
          case 0:
            rc.show(artListSVC);
            break;
          case 1:
            rc.show(vidListSVC);
            break;
          }
          setNavbarBack(false, undefined);
        });
    
    }

    CSS tabbuton 类确定按钮 on 状态的风格,tubbutoff 确定 off 状态的风格。如果触摸了按钮 0select 事件的事件处理程序显示文章列表,如果触摸了按钮 1,则显示视频列表。
  8. 在内容区域中显示文章列表。为 click 事件添加一个事件处理程序,在 Scrollview 内通过选择操作可发出该事件:
    function init() {
        rc.show(artListSVC);
        eh.on('click', function(obj) {
                rc.setOptions(
                {
                    inTransition: true,
                    outTransition: false
                });
                var surfaceProps = obj.origin.getProperties();
                if (surfaceProps.itemType === 'article') {
                    wb.setContent('<iframe width="100%" height="100%" src="' + 
                            articles[surfaceProps.listIndex].url + '"></iframe>');
                    rc.show(wb);
                    setNavbarBack(true, artListSVC);
                }
                else
                {   // video
                    vs.setContent(videos[surfaceProps.listIndex].url);
                    rc.show(vs);
                    setNavbarBack(true, vidListSVC);
                }
    
        });
    
    }

在 IBM Bluemix 上使用 Cloudant 和 Famo.us 应用程序 UI 模板自动创建移动应用程序

可以随时将最终的示例转变成一个应用程序 UI 模板。查看 "在 Bluemix 上使用 Cloudant 自动创建 Famo.us 移动应用程序",了解如何在云中更好地完成工作,并探索使用 Cloudant 和 famo.us 以可扩展的数据驱动方式生成移动应用程序 UI。您将在 IBM DevOps Services 中编写 Famo.us 应用程序模板的代码,在 Cloudant 中自定义数据和外观,然后在 IBM Bluemix™ 上部署该应用程序。

内存中的场景图现已指定,并且所需的事件处理程序都已连接,准备让 Famo.us 引擎进行处理了。

检查此示例中所创建的 Famo.us 场景图(参见 图 13)。您可轻松地修改它,以选择和显示其他信息 — 只需更改数据源并修改风格。甚至可以通过参数自动执行此类修改。从本质上讲,您可以为一般的 ”浏览一个列表并选择显示某个条目 “类别的移动应用程序创建一个 UI 应用程序模板。可逐步构建一个完整的此类应用程序模板集合,以覆盖各类可能的应用程序领域。


结束语

Famo.us 访谈

通过 Sing Li 与 Famo.us 的 CEO 和共同创始人 Steve Newcomb 进行的一次访谈,了解 Famo.us 背后的灵感及其未来的愿景。

创建本机代码的移动应用程序很难。不仅需要在多个移动操作系统(以及修订和差异)方面要经历陡峭的学习曲线,在不同的编程语言和每种平台上的数百个系统 API 方面也是如此。添加专有的或自定义的工具带,以及各种构建和部署管道和市场,最终得到的是一个快速发展而且您必须赶上其步伐并支持的各种技术大杂烩。即使拥有可随时使用的应用程序样本代码,采用它在移动平台间解决新问题时也可能要花费数周或数月的时间才能完成编码和调试工作。

与此同时,针对移动应用程序开发的 Web 技术(尽管他们承诺是跨平台的)也远远落后于本机代码 UX 的交付 — 到目前为止情况的确如此。Famo.us 框架将浏览器优化方面的最新突破与 3D 呈现库提供的成熟概念结合起来,为移动 Web 应用程序提供了一个高性能、易于使用、可高度自动化的 UI 创建平台。现在 JavaScript 开发人员可轻松创建各种移动应用程序,其用户体验完全可以同本机代码实现相媲美。

致谢

作者在此感谢 Famo.us 的 Andrew De Andrade 和 Larry Robinson 在审校本文方面提供的协助;还要感谢 Jeanne Feldkamp 和 Steve Newcomb 挤出宝贵的时间与我们进行访谈。


下载

描述名字大小
Sample codesample-code.zip65KB

参考资料

学习

获得产品和技术

讨论

  • 加入 developerWorks 社区。查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, 移动开发
ArticleID=980534
ArticleTitle=使用 Famo.us 创建高性能移动 UI
publish-date=08132014