说起 PJAX,已是由来已久,在很多网站上经常会遇到。它通过 PushState + Ajax 技术,实现 HTML 页面局部刷新等功能,提供了一种极速的浏览体验。避免每次载入过多的重复资源,耗费额外加载时间,提升了网站的整体访问速度。最直观的体验就是音乐能全局播放了!
Tips:本站集成的 PJAX 功能会随着 v2.2 版本一起发布!
在 Github 上,PJAX 用的最多的主要是这两版:defunkt/jquery-pjax 和 MoOx/pjax
defunkt/jquery-pjax 依赖 JQuery,最近更新停留在三年前,文档介绍有服务端配置:
1 2 3 4 5 6 7 8 def index if request.headers['X-PJAX' ] render :layout => false end end
不过按操作配置完后,在我的网站效果不是很好,网上也没找到解决办法,没提到服务端配置啥的,多次尝试无果后,我也就放弃了。感兴趣的朋友可以试一下,这里贴上几篇参考文章
PJAX站点加速之翼
PJAX原理和使用
typecho实现pjax
整合PJAX网页无刷新,支持评论和搜索…
MoOx/pjax (重点介绍) 去除了 JQuery 依赖,其它功能和上一个类似。网上教程不是很多,这篇 倒是可以参考下。高版本 NEXT 集成的 PJAX 也是这个。
本站 HEXO:v3.9,NEXT:v5.1.4,升级可不是件容易的事,想想还是自己折腾吧!
首先,参照官方,在主题 _config.yml 文件中配置 在 next/layout/_custom/ 新建 pjax.swig,内容如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <div class ="pjax_loading" > <div id ="pjax_loader" > </div > </div > <script src ="https://cdn.jsdelivr.net/npm/pjax@0.2.8/pjax.min.js" > </script > <script type ="text/javascript" > var pjax = new Pjax({ elements: 'a' , selectors: [ 'title' , '.content-wrap' , '.sidebar-inner' ], analytics: false , cacheBust: false , debug: false , }); $(document ).on('pjax:send' , function ( ) { loadingBefore(); }); $(document ).on('pjax:success' , function ( ) { loadingAfter(); }); function loadingBefore () { $("#main" ).fadeOut(100 ); $(".pjax_loading" ).css("display" , "block" ); } function loadingAfter () { var styl = '{{theme.pjax.style}}' ; $("#main" ).fadeIn(100 ); $(".pjax_loading" ).css("display" , "none" ); } </script >
在 next/layout/_custom/custom.swig 或者 next/layout/_layout.swig 中引入 pjax.swig 1 2 3 {% if theme.pjax.enable %} {% include 'pjax.swig' %} {% endif %}
这样配置后,网站已经初步具备 PJAX 功能了,那么接下来,就是要处理一大堆兼容的事情!
灵感来源 刚开始想着按照 NEXT 高版本 PJAX 的代码来就行了,可后来发现版本间变动太大,要找起这个功能来还真不容易。后来发现也有 NEXT 5 版的主题整合了 PJAX 功能,如 Sagiri pjax 、Leesin’s Blog 给了我一定启发。前一位将部分依赖整成 NodeJS 的 node_modules 进行引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 require('./utils'); require('./motion'); require('./affix'); require('./pisces')(); require('./scrollspy'); require('./post-details')(); require('./bootstrap'); require('./evanyou'); require('./leancloud')(); require('./share')(); require('./scroll'); require('./since'); require('./title'); require('./type'); require('./kanban'); require('./mix'); require('./clipboard'); require('./pjax'); require('./online'); require('./search');
不会 NodeJS 还真看不明白,后一位则将 JS 代码重新复制了一份,耦合性太高了,虽然后期改用 RequireJS。不过吧,文章整体逻辑太乱,看着真的很懵,还是自己想办法吧!
柳暗花明 一篇 NEXT 作者的 文章 引起了我的注意,看来很多主题都是类 EJS,代码相似度都挺高。多次调试代码后,终于对其渲染逻辑有了一定了解,一周多的折腾总算告一段落,再整都 HEXO 底层了。(⊙﹏⊙)b
进入网站首页,会加载很多 JS/CSS 等等,其它页面无需再次加载,直接引用即可,这就是 PJAX 要做的事情了。然而 NEXT 很多 JS 都是使用 $(document).ready(function(){...})
这种写法(DOM 树加载完执行),所以我们需要将这些方法提取出来,使其在别的地方能被调用到,以 next/source/js/src/schemes/pisces.js 这个 JS 为例
修改前:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 $(document ).ready(function ( ) { var sidebarInner = $('.sidebar-inner' ); initAffix(); resizeListener(); function initAffix ( ) { var headerOffset = getHeaderOffset(), footerOffset = getFooterOffset(), sidebarHeight = $('#sidebar' ).height() + NexT.utils.getSidebarb2tHeight(), contentHeight = $('#content' ).height(); if (headerOffset + sidebarHeight < contentHeight) { sidebarInner.affix({ offset: { top: headerOffset - CONFIG.sidebar.offset, bottom: footerOffset } }); } setSidebarMarginTop(headerOffset).css({ 'margin-left' : 'initial' }); } function resizeListener ( ) { var mql = window .matchMedia('(min-width: 991px)' ); mql.addListener(function (e ) { if (e.matches){ recalculateAffixPosition(); } }); } function getHeaderOffset ( ) { return $('.header-inner' ).height() + CONFIG.sidebar.offset; } function getFooterOffset ( ) { var footerInner = $('.footer-inner' ), footerMargin = footerInner.outerHeight(true ) - footerInner.outerHeight(), footerOffset = footerInner.outerHeight(true ) + footerMargin; return footerOffset; } function setSidebarMarginTop (headerOffset ) { return $('#sidebar' ).css({ 'margin-top' : headerOffset }); } function recalculateAffixPosition ( ) { $(window ).off('.affix' ); sidebarInner.removeData('bs.affix' ).removeClass('affix affix-top affix-bottom' ); initAffix(); } });
修改后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 $(document ).ready(function ( ) { piscesJs(); }); function piscesJs ( ) { initAffix(); resizeListener(); } function initAffix ( ) { var sidebarInner = $('.sidebar-inner' ); var headerOffset = getHeaderOffset(), footerOffset = getFooterOffset(), sidebarHeight = $('#sidebar' ).height() + NexT.utils.getSidebarb2tHeight(), contentHeight = $('#content' ).height(); if (headerOffset + sidebarHeight < contentHeight) { sidebarInner.affix({ offset: { top: headerOffset - CONFIG.sidebar.offset, bottom: footerOffset } }); } setSidebarMarginTop(headerOffset).css({ 'margin-left' : 'initial' }); } function resizeListener ( ) { var mql = window .matchMedia('(min-width: 991px)' ); mql.addListener(function (e ) { if (e.matches){ recalculateAffixPosition(); } }); } function getHeaderOffset ( ) { return $('.header-inner' ).height() + CONFIG.sidebar.offset; } function getFooterOffset ( ) { var footerInner = $('.footer-inner' ), footerMargin = footerInner.outerHeight(true ) - footerInner.outerHeight(), footerOffset = footerInner.outerHeight(true ) + footerMargin; return footerOffset; } function setSidebarMarginTop (headerOffset ) { return $('#sidebar' ).css({ 'margin-top' : headerOffset }); } function recalculateAffixPosition ( ) { $(window ).off('.affix' ); var sidebarInner = $('.sidebar-inner' ); sidebarInner.removeData('bs.affix' ).removeClass('affix affix-top affix-bottom' ); initAffix(); }
提取出 piscesJs() 这个能外部调用的方法。其它需要修改的如 bootstrap.js、motion.js、post-details.js、scroll-cookie.js、utils.js 等,做法都类似,这里就不一一列举了。
在 pjax.swig 中引用抽取出来的方法,重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 $(document ).on('pjax:send' , function ( ) { loadingBefore(); motionJs(); }); $(document ).on('pjax:success' , function ( ) { bootstrapJs(); postDetailsJs(); initAffix('473' , '164' ); initCarousel(); initReadMore(); initIndexPostVisitor(); initValineAdmin(); linkCardFunc(); macPanelMan(); loadingAfter(); socialShare('.social-share' ); initRating(); initCodeCopy(); initBusuanzi(); });
这里的 initBusuanzi(),直接是又请求了一遍
1 2 3 function initBusuanzi ( ) { $.getScript("https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ); }
后期使用 revolvermaps 替换不蒜子,参考枫糖的 这篇文章
一些问题 $(“…”).lazyload is not a function 此错误一般是重复引入 JQuery 引起的冲突,仔细核查下引入 JQuery 的地方
FancyBox 2 Ajax 跨域 NEXT 5 集成的图片灯箱效果是 FancyBox 2 ,文档中也有 Ajax 渲染的方式
1 $(".open_ajax" ).fancybox({type : 'ajax' });
不过在 PJAX 后总提示跨域,首次点击图片预览时有 Bug,尝试了多种方法仍是无果,官方有说
1 Note, ajax requests are subject to the same origin policy. If fancyBox will not be able to get content type, it will try to guess based on 'href' and will quit silently if would not succeed (this is different from previous versions where 'ajax' was used as default type or an error message was displayed).
最后没办法,升级到 FancyBox 3 问题解决。utils.js 中的 wrapImageWithFancyBox
方法需要修改,用 data-fancybox
属性来渲染。下边的对比,可以看出 FancyBox 版本间变动还是挺大的,新版加了很多个性化功能。
FancyBox 2 预览:
FancyBox 3 预览:
写在最后 集成 PJAX 后,网站访问速度有了很大提升,请求数也少了很多,虽然还附带着这么多花里胡哨的东西,Who Care 呢。托管在 Github 上,没有个人服务器,这效果也算可以了。对于其它类 EJS 主题,这里只是抛砖引玉,大家可作个参考。
其它优化相关的文章:
hexo 优化与加速