背景

想当年,我做了一个新媒体网站项目(AIISPO,已下线)。跟普通资讯网站不一样的是,老板要求PC端前台的文章阅读模式一定得是瀑布流+模态框瀑布流指的是以瀑布流的形式将文章罗列出来,而模态框则指的是点击瀑布流中代表文章的某个文块时,直接在当前页面弹出模态框来显示文章正文。

瀑布流式的文章列表

利用模态框直接显示文章正文

点击瀑布流的某个文块后,直接在当前页面弹出模态框来显示正文以及文章相关内容,可依稀透过模态框背景看到底层的瀑布流。点击模态框背景可关闭模态框,相当于回到瀑布流。

平常的开始

其时,我正痴迷于MVVM框架avalon,于是,理所当然地用avalon来渲染瀑布流的DOM树。至于文章正文嘛,就用avalon给瀑布流中的各文块绑定个click事件,顺便把文章id给传到click事件的callback里,执行callback时就ajax读一下文章正文,最后放到模态框里显示就是了。
至此,老板要的“用户体验”就达成了,老板夸我厉害还给我涨工资,我心里美美哒 n(≧▽≦)n

问题初现

官网上线了几天,老板给我提出了一个非常“实际”的问题,他没法把文章的网址分享出去呀,这是因为:官网本来就没有独立的文章页面,更勿论文章的网址了!。当务之急是创建出可供分享的文章网址。

Hashbang登场

老板不接受“跳转新页面”打开文章正文的方案,坚持一定要瀑布流+模态框,我只好琢磨别的思路了。首先我试用window.location.href="/article/1",这是一定会使浏览器跳转而无法保持在当前页面的,pass。接下来我查资料就搜到这Hashbang的方案:利用改变锚点#不会导致页面跳转这一特点,并加上!这一独特的标识,形成形如http://aiispo.cn/#!/article/1的网址。

具体的方案是这样的:

  1. 大体上跟最初的方案一致。
  2. 不一样的地方在于,打开模态框的同时window.location.href="/#!/article/1",这时地址栏的地址便变为http://aiispo.cn/#!/article/1,也能保持不跳转。
  3. 另外,给document.load绑上callback,也就是在页面加载好后取当前的hash(window.location.hash),会得到形如#!/article/1的字符串。正则匹配该字符串把文章ID取出,就可以直接显示文章正文了。
  4. 在关闭模态框时,应把地址栏恢复回来。

如此一来,用户在阅读文章时地址栏里的正是文章的“网址”,而当用户把网址分享给别人,别人复制到浏览器一打开,就能看到那篇文章了。老板又夸我了,我心里又美美哒 n(≧▽≦)n

问题再现

官网上线月余,百度仅收录了首页,我打开首页的快照一看,可只有avalon的模板标签,我一下子就醒悟过来了:百度根本就没能爬到任何的文章,因为首页根本没有任何文章的链接!
这时候我才意识到在SEO方面出了大问题了,这对一个新媒体网站来说可是致命的呀,把问题报告老板后就赶紧开动脑筋想解决方案了。

小尝试

把心一横,把原本用avalon渲染的瀑布流,全部改回用PHP来渲染,同时给瀑布流的文块加上<a>标签,例如<a href="/#!/article/1">。由于加上了<a>标签,地址栏就不需要手动去改了。

问题未解

又过了几天,各个搜索引擎还是没有动静,我便又开始查资料:原来,国内的搜索引擎在抓取页面的时候,是不执行js的。换句话说,搜索引擎从http://aiispo.cn/#!/article/1这样的网址进去,只能看到瀑布流而看不到文章正文,因为文章正文是后面用js渲染的,不执行js就没法渲染,而瀑布流是用PHP渲染成html的,搜索引擎能看得到。据说Google是会执行js的,不过作为一个国内的网站,还是得优先保证国内的搜索引擎。

Html5 History Api

思量良久,问题还是出在文章没有独立的页面上,另外Hashbang这种URL也不可靠,无法被后端识别。痛定思痛,这次一定要彻底解决问题。

改造如下:

  1. 仿照模态框的UI,我给做了文章的独立页面,URL形如http://aiispo.cn/article/1
  2. Hashbang不成,我就找其它能修改地址栏但不跳转的方案,结果就找到了Html5 History Api

    1. 把瀑布流文块里的<a href="/#!/article/1">改为<a href="/article/1">
    2. 改这href会导致用户点击后跳转,因此需要用js拦截<a>不让其跳转,并改为用window.history.pushState()来设置地址栏,此时用户的地址栏应为http://aiispo.cn/article/1
    $('#article-list a').on('click', function() {
      var url = $(this).attr('href');
      window.history.pushState(null, null, url);
      return false;
    });
    1. 照样用正则匹配出文章ID,并用模态框显示文章正文。

如此一来,便兼顾了三方的需求:

  1. SEO的需求,搜索引擎抓取瀑布流能抓到文章独立页面的URL(形如http://aiispo.cn/article/1),通过此URL进入文章独立页面能抓取到文章正文。
  2. 用户体验的需求,完美地保留了瀑布流+模态框的阅读模式。
  3. 用户分享文章网址的需求,用户在瀑布流打开文章时,地址栏正是文章独立页面的URL。

兼容性修正

上述方案依赖于Html5 History Api,而IE9及以下版本都是不支持Html5 History Api的,需要进行兼容性修正。
在权衡利弊后,最终决定放弃IE9-用户的用户体验

  1. 检测当前浏览器是否支持Html5 History Api
  2. 不支持的话,就不拦截瀑布流文块的<a href="/article/1">,也就是直接让其跳转。

总结

我的这套方案,本质上跟Prerender没有区别,都是让后端模拟前端渲染的方式生成一个独立的页面供搜索引擎抓取,既兼顾用户体验,又不失SEO

03-05 19:47