原创声明
本文为阅文体验设计 YUX 成员出品,请尊重原创,转载请联系阅文体验设计微信公众号 ( id: YUX_design ) 获取授权,并注明作者、出处和链接。
阅读页作为小说阅读类网站访问量很高的页面,阅读体验上的优化是至关重要的。我有幸在起点项目中负责阅读页的前端开发,成就感满满。
起点中文网访问地址:http://www.qidian.com ,欢迎体验!
在整个起点项目已经有很完善的性能优化体系的情况下,阅读页在改版之后,页面性能统计数据如下:
▲ 上报系统统计数据
▲ 全国访问速度均值 10% 采样结果
由上组统计数据我们可以看到,起点中文网阅读页平均 DOM Ready 的时间为 0.2s ,Onload 的时间为 0.9s ,加载最慢地区页面 Onload 时间也仅为 1.44s 。
这已经充分体现了起点中文网阅读页的优异性能,本篇着重探讨在页面性能已经如此良好的情况下,如何进一步的优化起点中文的阅读体验。本次优化侧重在以下两点:
一、章节加载逻辑优化;
二、滚动加载优化-函数节流;
一、章节加载逻辑优化
通常用户在阅读完上一章之后,会明显的看到我们加载下一章的 Loading 状态。这对于用户体验来说影响是比较大的。如果能在用户触发到某一特定时间点的时候,就提前加载好下一章节内容,用户停留在 Loading 的时间就能明显减少甚至感知不到。这种体验上的优化我们称之为无缝阅读。基于这个点我们采取了如下的两种方式:
1. 适当提前 Scroll 触发加载时机;
2. 持续预加载后面章节数据;
1. 适当提前 Scroll 触发加载时机
什么是触发加载时机提前?即当你预知到加载即将被触发,就提前触发当前事件,这可以大大减少用户等待的时间。
起点中文阅读页提前触发事件的机制是,当页面剩余高度小于等于页面视窗的 1.5 倍时,就立即加载下一章节。
可以看到实现原理其实很简单,就是达到触发点去拉取下一章节,实现代码如下:
// 使用函数节流的形式监听scroll事件
$(window).on('scroll', function(){
// 获取当前页面高度
var pageHeight = $(document).height();
// 获取滚动条距离页面顶部的距离
var winSTop = $(window).scrollTop();
// 获取浏览器高度
var winHeight = $(window).height();
// 当页面未显示高度页还剩1.5倍视窗高度时,加载
var cHeight = 2.5 * winHeight;
//当剩下小于1。5屏未显示的时候,加载新的章节
if( pageHeight <= ( winSTop + cHeight ) ){
// ... do somethings
}
});
// 页面初始化的时候主动去触发一次scroll事件
$(window).trigger('scroll');
当写完上述代码后,会发现页面加载下一章节变的快多了,但是在滚动很快的情况下或者数据接口返回较慢的时候,页面还是会显示 Loading 态,如下图所示:
Loading 会 Block 阅读进度,影响用户阅读心情。所以在这种情况下,我们引入第二个体验上的优化点。
2. 持续预加载后面章节数据
什么是预加载?提前加载数据,当用户需要查看时可直接从本地缓存或者内存中读取。
为什么需要预加载?可以从缓存中读取数据,不通过网络传输,速度更加快,无延迟。
利用用户阅读的空余时间,提前加载下面一章节的信息存入内存中,减少用户等待 Loading 的时间,从而达到进一步提升用户体验的目的。
▲ 预加载逻辑流程
上图描述了我们利用内存存储下一章数据的流程。下面具体来看一下代码的逻辑:
1) 首先页面 Load 完之后立马加载一章节内容存储在内存中;
var chapterInfo = {
// 标示字段中是否有 章节存储
isHas: boolean,
chapterId: 21463,
content: ''
};
// 是否正在预加载标识
var chapterLoad = false;
2) Scroll 滚动触发逻辑 ,添加预加载章节逻辑,保证页面加载无断续状态。代码如下:
// 使用函数节流的形式监听scroll事件
$(window).on('scroll', function() {
//当正在加载章节时,不再加载下面章节
if (that.chapterLoad) return false;
// do something ...
// 当剩下小于1.5屏未显示的时候,加载新的章节
if (pageHeight <= (winSTop + cHeight)) {
// 当内存中有章节存储 && 储存的章节id === 下一张展现至页面上的章节id
if (chapterInfo.isHas && chapterInfo.id == nextChapterId) {
// 把内存中的章节append 至页面中,并清除chapterInfo参数中的数据
} else {
// capterLoad 置为true,禁止发送请求
that.chapterLoad = true;
// 发送ajax请求 加载数据 append 至页面
}
} else if (!chapterInfo.isHas && !that.chapterLoad) {
// 当内存中没有章节信息 && 没有章节正在加载时 进行预先加载
//重置为true,禁止发送请求
that.chapterLoad = true;
chapterInfo.isHas = true;
// ajax拉取章节信息至内存 ,并设置 that.chapterLoad = false
// 在数据返回存入内存后,回调中主动触发一次 window.scroll 事件,防止在ajax请求时还未返回数据时, 页面已经滚动至底部不再触发scroll 事件,页面出现加载停滞情况
}
});
从上述代码可以看到预加载和直接 Append 数据至页面中的逻辑是互斥的,既保证页面逻辑清晰,也优化了页面阅读体验,达到我们预期的效果。现在我们来看下页面滚动的效果:
页面加载下一章节的时候几乎没有 Loading 态显示,赞!
二、滚动加载优化-函数节流
做了上述优化之后,页面滚动加载已经很完美了,这个时候我们可以去思考一下代码层面上的优化。在前端领域中滚动加载是一个十分常见的加载机制。监听 window.scroll 事件,判断当前是否到达触发加载的时机,加载后面一段内容。常见的监听事件代码为:
$(window).on('scroll',function(){
// ... do somethings
});
上诉章节预加载就是使用这种 Scroll 监听方式。
但是 window.scroll 事件监听是实时触发的,也就说每隔 16.7ms 就触发一次,单位时间内事件触发的次数过多,会影响系统性能。此时函数节流是一个不错的解决方案。
函数节流是什么意思呢?好比生活中拧紧水龙头的过程,在拧紧这个动作中,我们单位时间内流出的水就会减少,从而达到我们节流的目的。
在代码逻辑中我们通常预先设定一个执行周期,当调用动作的时刻大于或等于执行周期时则执行该动作,然后进入下一个新周期。
我们通过设置一个合适时间间隔来执行 Scroll 事件,以减少请求增加性能。在停止 Scroll 滚动之后,再触发一次滚动事件,处理滚动时机不对,触发不及时的问题。定义一个节流函数,如下:
function throttle(func, wait, mustRun) {
var timeout,
startTime = new Date();
return function() {
var context = this,
args = arguments,
curTime = new Date();
// 清除定时器
clearTimeout(timeout);
// 如果达到了规定的触发时间间隔,触发 handler
if(curTime - startTime >= mustRun) {
func.apply(context,args);
startTime = curTime;
} else {
// 没达到触发间隔,重新设定定时器
timeout = setTimeout(func, wait);
}
};
};
监听滚动的方式就可以优化成:
// 使用函数节流的形式监听scroll事件
$(window).on('scroll', throttle(function () {
// do something
}, 200 ,300));
如下所示,左边是普通 Scroll 事件触发的方式,右边是使用了节流函数触发的方式。使用节流函数的方式明显减少了事件触发的频率,并且在停止滚动的时候也会触发一次 Scroll 事件,防止错过一些特殊的情况,未能执行页面下拉加载事件。
最终我们在没有影响页面滚动加载体验的情况下,节省了浏览器的性能开销,完美。
结尾
第一次做阅读相关的项目,并负责如此重要的一个页面,我融入了自己的想法,优化阅读体验,最终达到了我要的效果,我很满意。
一个细节的优化是可以决定产品的好坏的,良好的用户体验,会吸引跟多的用户,获得更多的称赞。针对阅读体验上的优化,我们其实能做的还有很多,希望之后可以再进一步的优化。
本文作者:刘文涛
转载请向阅文体验设计微信公众号 ( id: YUX_design ) 获取授权,并注明作者、出处和链接。
本篇文章来源于微信公众号: 阅文体验设计YUX