阅读体验优化之起点阅读页

阅读 690
收藏 67
2018-06-15
原文链接:zhuanlan.zhihu.com

阅读体验优化之起点阅读页

本文作者:刘文涛
原创声明:本文为阅文前端团队 YFE 成员出品,请尊重原创,转载请联系公众号 ( id: yuewen_YFE ) 获取授权,并注明作者、出处和链接。

阅读页作为小说阅读类网站访问量很高的页面,阅读体验上的优化是至关重要的。我有幸在起点项目中负责阅读页的前端开发,成就感满满。

起点中文网访问地址:www.qidian.com ,欢迎体验!

在整个起点项目已经有很完善的性能优化体系的情况下,阅读页在改版之后,页面性能统计数据如下:

上报系统统计数据
全国访问速度均值 10% 采样结果

由上组统计数据我们可以看到,起点中文网阅读页平均 DOM Ready 的时间为 0.2s ,Onload 的时间为 0.9s ,加载最慢地区页面 Onload 时间也仅为 1.44s 。

这已经充分体现了起点中文网阅读页的优异性能,本篇着重探讨在页面性能已经如此良好的情况下,如何进一步的优化起点中文的阅读体验。本次优化侧重在以下两点:
一、章节加载逻辑优化;
二、滚动加载优化-函数节流;

一、章节加载逻辑优化

通常用户在阅读完上一章之后,会明显的看到我们加载下一章的 Loading 状态。这对于用户体验来说影响是比较大的。如果能在用户触发到某一特定时间点的时候,就提前加载好下一章节内容,用户停留在 Loading 的时间就能明显减少甚至感知不到。这种体验上的优化我们称之为无缝阅读。基于这个点我们采取了如下的两种方式:

  • 适当提前 Scroll 触发加载时机;
  • 持续预加载后面章节数据;

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 事件,防止错过一些特殊的情况,未能执行页面下拉加载事件。

最终我们在没有影响页面滚动加载体验的情况下,节省了浏览器的性能开销,完美。

结尾

第一次做阅读相关的项目,并负责如此重要的一个页面,我融入了自己的想法,优化阅读体验,最终达到了我要的效果,我很满意。

一个细节的优化是可以决定产品的好坏的,良好的用户体验,会吸引跟多的用户,获得更多的称赞。针对阅读体验上的优化,我们其实能做的还有很多,希望之后可以再进一步的优化。

更多分享,请关注YFE:

评论