关于 AMP,Webnovel 都做了些什么?

引言

AMP 是什么?它全称 Accelerated Mobile Pages 「AMP」,是 Google 推出的一个加速网页加载的开源框架,我们在此也不做太多的介绍,具体可以看我们之前发的 公众号文 [1] 。阅文前端团队作为国内最早践行 AMP 的开发团队之一,我们的 AMP 实践也经历了好几个大迭代,其成果也得到了 Google 官方的肯定,分别在 Google I/O 2019 和 AMP Conf 2019 大会上被feature(图1-1),希望我们积累的经验可以给大家提供帮助。

「图1-1:阅文集团在 Google I/O 大会、AMP conf 2019 被 feature」

Webnovel with AMP

是否值得去做?

目前 Webnovel 先后进行了 2 次 AMP 页面的转化。首次 AMP 的实施输出了站点的 2 个主要着陆页:首页、详情页,上线后 1 个月 Webnovel 的阅读页 PV 提升了 59%,可以归功于 AMP 详情页的转化。第二次我们则将 Webnovel 主要路径页面进行了 AMP 的转化(阅读页、书库页、漫画详情页、漫画阅读页等),目前从 Google Analytics「GA」的数据来看,还是非常值得去实施的 :这些 AMP 页面平均加载时间为 0.94s, 而被 AMP Cache 缓存的页面加载时间甚至为 0。

我们做了哪些?

AMP 不仅是一个前端技术框架,它还提供了一套完整的生态,包含一系列 ”服务于“ 页面加载体验的规范和约束,其核心思想是,构建出加载速度更快甚至达到秒出的页面,这要归功于它提供的3个重要组件:

AMP HTML – 提供了一系列 AMP 自定义的 HTML 标签及规则,有效地保证了 HTML 的加载体验。 AMP JS – 提供了一系列 JS 组件,其资源直接来源于 Google CDN。

为了达到极致的加载体验,AMP 并不允许开发者引入自定义的 JavaScript,而是提供了大量的 AMP JS 组件来实现用户想要达到的效果( AMP Conf 2019 新引入的 amp-script 可以帮助解决自定义组件不能完成的效果,不过当前这个功能还在实验当中)。 AMP Cache – 利用 Google 的缓存及预加载方案可以达到页面的秒出。

那么用户体验与开发体验是否可以达到能够接受的平衡点?我们先来看看 Webnovel AMP 具体的实施细节吧。

AMP HTML

Webnovel 的 AMP 页面采用了 EJS 模板引擎作为直出方案,和普通的 EJS 项目唯一的区别就是需要遵循 AMP HTML 的一些规则。 AMP HTML 提供了简单的 HTML 模板,并对一些 HTML 标签 进行了规则的约束,只要遵循它的 文档说明 [2] ,还是能够很快接入的。当然,Webnovel with AMP 最重要的实施步骤则是应用它的组件, 也就是 「AMP JS」。

「图1-2:AMP HTML 模板」

「图1-3:AMP HTML 标签的规则」

AMP 组件的约束

在参加 AMP 会议之前,我可能会说,这里需要先给各位打个预防针啦!在 AMP 的世界里,你不能够引入自己的脚本、不能外链引入自己的样式,只能一切按它的规则走!但此次 AMP 会议出乎意料地带来了一个新的技能 – amp-script 的支持,我们终于可以写自己的脚本了!但需要注意的是,amp-script 仅允许解压后最多 150KB 的大小,虽然如此,我们也可以方便地利用其异步加载一些我们自己的脚本。 但为什么 AMP 对脚本有很多约束?来看下 AMP 的官方文档中关于脚本的说明:

“JavaScript is powerful, it can modify just about every aspect of the page, but it can also block DOM construction and delay page rendering (see also Adding interactivity with JavaScript [3] ). To keep JavaScript from delaying page rendering, AMP allows only asynchronous JavaScript.  AMP pages can’t include any author-written JavaScript(即:“JavaScript很强大,它可以改变一个页面的任何方面,但它同时也阻碍了DOM的生成、页面的渲染。为了避免它对渲染的阻碍,amp仅仅允许异步加载 JavaScript,但它并不允许引入任何页面作者写的脚本“)

脚本可以阻碍渲染,而为了保证快速呈现页面内容,脚本的约束似乎不可避免,对于 amp-script 大小的限制,也是为了保证在低端手机上依然有不错的加载体验。

AMP 组件的基础应用

AMP 提供了超级多的组件,只需: 1、外链引入该组件提供的 JavaScript 脚本。 2、找到组件示例代码、嵌入 HTML DOM 结构中,按需更改组件参数。

「图1-4 组件一览表」

AMP 文档中,每个组件基本都有具体的 示例代码 [4] , copy 下来就可以在项目中使用,这一点很赞!但想要非常 “精准地” 实现我们想要的交互效果,还是非常难的,毕竟这个组件不是我们自己写的,我们也不能够拓展它,这种感觉就好比我们有手有脚、却只能用假肢,身体变轻盈了,但行动受到了很大的限制,不过比较欣慰的是,AMP 的常用组件也基本满足了我们的需求。这里例举了一些 Webnovel 使用的 AMP 常用组件:

amp-img

遵循 AMP HTML的规则,所有的图片都需要用

实现,它用于响应式地展示图片、并完美自带懒加载功能(不在视区内不进行加载)。目前 Webnovel 的漫画阅读页,所有的图片都用其实现了宽度100%、高度自适应(图1-5)。

「图1-5:amp-img轻松实现图片高度自适应」

在此之前我们是将图片绝对定位,同时在容器上添加样式 「padding-top:图片宽高比 」来实现的,相比之下更方便

「图1-6:旧的实现方式」

「图1-7:使用设置 layout 为 responsive 即可」

amp-list

目前我们大部分amp页面都使用了这个组件,只要是异步加载某些模块,就会有它的用武之地(图1-8):

1. 书详情页 – 打赏信息、标签信息、书评的异步加载。 2. 漫画列表页 – 分页加载漫画书籍 3. 漫画阅读页 – 分页加载漫画章节图片

「图1-8:详情页、漫画阅读页、漫画列表页 amp-list 异步加载模块」

amp fallback & placeholder

非常赞的 API !有了它,我们无需检测加载失败、加载超时的情况,只需设定好该情况对应的 UI 展示即可!amp-img、amp-list 等组件中,只需在组件内的 DOM 元素标签上添加 fallback & placeholder 属性即可。

amp-mustache

提供 mustache 模板引擎,一种 logicless「弱化数据逻辑」 的模板语言, 通常配合 amp-list 等异步加载组件来使用,amp-list 发起请求拿到数据后,会渲染其中的 mustache 模板、完成数据、模板的拼接。

amp-sidebar

用于侧边栏、工具栏等。目前我们左侧滑出的侧边栏就是利用它实现的(图1-9),实现成本很低,只需将其对应的 DOM 结构放置于 body 下,同时在触发侧边栏展示的元素上绑定 AMP 提供的 tap 事件。

「图1-9:amp-siderbar实现侧边目录」

amp-bind

可配合 amp-state 使用,amp-state组件用于初始化 json 数据,而 amp-bind 可以绑定数据到 DOM 元素上,同时支持「 amp.setstate() 」,「 amp.pushstate() 」等方法实现数据的更新。目前页面中需要动态展示的元素、文本,我们通过它绑定了数据、数据改变后页面对应的元素、文本也会对应地变成绑定的数据。 比较局限的方面是,只有在触发事件后数据才会生效,不适用于页面 onload 时的数据逻辑处理。

AMP 的事件机制

AMP 也支持事件的处理,例如 tap 点击事件、change 事件等,更高级的有:滚动到某位置触发动画等效果,具体可参考 AMP 文档。

AMP Cache

AMP Cache 算是 AMP 的核心概念了,简单来讲,通过 Google 搜索引擎搜索到的 AMP 页面可以被 AMP 缓存,同时也会被 Google 进行预加载,Google 使用的一些安全性机制、缓存更新机制,可以让开发者更无忧地使用。AMP 也提供了被缓存页面的 CDN 地址拼接规则,利用这个缓存优势,Webnovel 将所有 AMP 页面之间的跳转链接改为了 AMP Cache 的地址,如果所有 AMP 页面都成功被 Cache ,那么页面之间几乎可以做到无缝切换(图1-10),如果页面没有被 Cache,也不用担心会跳转到未知的 404 页面,因为 AMP Cache 会将其重定向到我们自己站点域名下的 AMP 页面。

「图1-10:利用AMP Cache实现 AMP 页面无缝切换」

Webnovel AMP 页面的体验优化

漫画阅读页多个 AMP 组件结合使用打造更好的交互体验

Webnovel 漫画阅读页提供了另外一种不同于小说的阅读方式,由于漫画的阅读相比于小说更为方便快捷,因此其加载体验变得尤为重要,这里 Webnovel 利用 AMP 组件对漫画阅读页的加载体验做了优化,原本「刷新页面」切换章节的体验优化为「无刷新」加载章节内容。

amp-list、amp-form、 及 amp-state 合力实现无刷新向下加载

除了服务端渲染的首章节,其它的章节内容若要实现异步加载,amp-list 组件是必然要用的,需要注意的是,如果其 src 参数为接口地址,那么页面 onload 时就会触发,但我们希望做到的是点击下一章再进行加载,因此实现方案为:

1. amp-state 组件初始化章节模板所需 json 数据(图2-1),其 id (例如 comicContent)可看作该 json 数据的变量名。 2. amp-list 去除 src 参数,改为 [src] 参数,其值为上一步的 comicContent(图2-2)。 3. 添加 amp-form 组件,其 action 参数值为下一章节的接口地址,同时参数 on 中处理表单提交成功的逻辑(图2-3), 即:获取数据后更新章节数组,这将会重新渲染 amp-list 中的章节模板。

「图2-1: amp-statue 初始化的数据」

「图2-2: amp-list 中绑定 amp-state 中的数据」

「图2-3: amp-form 的成功回调」 关键点:

amp-state 中定义的章节数据需要前端自己维护一个数组(图2-1中的 chapters 字段)来存储各章节,实现 amp-list 中循环渲染章节模板,展示所有加载的章节。 amp-form 中的 submit-success 逻辑中,需要通过 AMP.setState() 更新章节数组,将最新章节 push 进数组。 amp-form 中的 submit-success 回调仅仅是接口请求成功的回调,请求成功不代表业务逻辑中也是成功的,因此需要通过业务接口成功与否的标识字段再次区分成功和失败的逻辑。 amp-state 最多存储 100 kb 的数据,前端需要处理加载章节过多的情况,例如缓存当前加载的章节数,若超过一定数量,点击下一章刷新页面。

最终实现出来的效果

「图2-4:无刷新加载下一章漫画」

小说阅读页 amp-list 组件进阶应用

在上一节中,通过 amp-list、amp-form、 amp-state 3个组件的结合使用,终于实现了无刷新加载下一章,其实 amp-list 近期已提供实验性高级 API: load-more 及 load-more-bookmark 等参数,可以做到无限加载,并同时支持点击加载、无限滚动加载2种方式,无需前端自己维护章节数组,所有的一切都由 amp-list 自己实现。这里对小说阅读页进行了实践。

amp-list-load-more 实现无限滚动加载

amp-list-load-more 的实践非常简单,只需在 amp-list 组件中,加入 load-more、load-more-bookmark参数即可(图2-4),具体 api 说明可参考 amp-list 的文档 [5] 。虽然当前功能为实验性功能,但相信 Google 不久后就会在正式环境进行支持。

「图2-4:amp-list-load-more 代码片段」

由于 amp-list-load-more 是 AMP 的实践性功能,除了添加参数,还是需要做些准备工作的。

1. 正式环境中,需要在小说阅读页的访问地址域名下(webnovel.com 或 AMP cache 的 Google 域名)种一个 Cookie( name: AMP_EXP,value: amp-list-load-more )才可体验此功能。 2. load-more-bookmark 参数只能为章节返回数据中的字段名称,其值必须为下一章的接口地址,不支持前端自己拼接地址,因此需要后端配合添加此字段。 3. 接口返回的下一章 url 不能为相对路径,必须为带协议头、自己站点域名的 url,否则在 Google 域名下请求地址的域名会出现问题。

最终实现效果

Webnovel 更多的 AMP 实践

当然,Webnovel 不只用了上文中提到的 AMP 基础组件,也没有止步于组件级别的体验优化,Webnovel 还使用了 AMP 主推的一些王者功能。

更优的体验 – AMP 主推功能的实践

Signed HTTP Exchange 「SXG」

它也是 AMP conf 2019 的亮点,其效果为:访问了 Webnovel Google 域名下的 AMP 页面,浏览器 url 地址展示的是 Webnovel 主站的域名(如图3-1)!这是 AMP 今年主推的功能之一,有些公司已进行实践,例如 Yahoo Japan,而 Webnovel 也抓住了时机、成功地进行了实践,其效果及实现细节可以参考「 Webnovel 的 SXG 实践  [6] by 刘鹏」。

「图3-1:Webnovel SXG 实施效果」

amp-story

算是 AMP 相当主推的功能了!使用其提供的多样性模板可以更加丰富地全屏展示 Webnovel 的各种故事「 关于 AMP Story,你需要知道这些 by 芯芯」,视频和 animation 的结合使 Webnovel 的用户可以更沉浸的浏览书籍。amp-story 还提供本地化、兼容桌面与移动端的展示等功能,此次 AMP 会议还带来了 amp-story 的一些新推进,例如 amp-story 自动生成工具、 Google Search 结果页直接收录并展示 amp story 等多方面的支持,可以说是非常牛了。

AMP conf 2019

目前 AMP 还有很多值得实践的功能,这次 Google 团队在东京举办的 AMP Conf 2019 也带来了很多新的想法,不少来自世界各地的开发、产品、设计都簇拥于此,可见 AMP 已逐渐具备广阔的市场。我们也很幸运地被邀请参加这次会议,一起见证了AMP 的新技术及主推的功能、同时也从世界各个公司的 AMP 案例中,再次深刻地体会到了 AMP 技术的价值、潜力。

「AMP conf 2019 in Tokyo」

总结

一切新技术的应用都应该服务于用户,而衡量一个技术是否值得实施的途径之一就是看数据。Webnovel 在实践 AMP 后,最显著的改变非常符合 AMP 的核心目标 – 页面加载体验的极致提升(87%的提升),除此之外,GA 的数据也证实了一些其他的优势,例如,Webnovel 主要页面的 PV 在应用 AMP 技术后有所提升(阅读页 PV 增加近 59%)、Webnovel Google 搜索引擎的来源提升了 18%,从数据上来看 Webnovel 的 AMP 实践是非常有意义的。

References

[1] 公众号文章:  https://juejin.im/post/5cb97f446fb9a0686362dd22

[2] 文档说明:  https://amp.dev/documentation/guides-and-tutorials/start/create/basic_markup.html?format=websites

[3] Adding interactivity with JavaScript:  https://developers.google.com/web/fundamentals/performance/critical-rendering-path/adding-interactivity-with-javascript

[4] 示例代码:  https://amp.dev/documentation/examples/?format=websites

[5] amp-list 的文档:  https://amp.dev/documentation/components/amp-list.html?format=websites

[6] Webnovel 的 SXG 实践 :  https://juejin.im/post/5cb97f446fb9a0686362dd22