0%

Next主题启用Pjax

Pjax是网页的局部刷新技术,使用AJAX (XmlHttpRequest) 来请求网页的局部信息,使用pushState() 的来更新浏览器当前URL,Pjax使得用户体验从浏览器升级为APP。

1. 开启Pjax

Next主题使用theme-next-pjax,它不依赖于任何lib,包括jquery。

Pjax does not rely on other libraries, like jQuery or similar. It is written entirely in vanilla JS.

修改网站配置文件:

/source/_data/next.yml
1
2
3
pjax: true
vendors:
pjax: //cdn.jsdelivr.net/gh/theme-next/[email protected]/pjax.min.js

启用Pjax后,如果浏览器不支持pushState()函数,Pjax不会做任何事情。

Pjax only works with browsers that support the history.pushState() API. When the API isn’t supported, Pjax goes into fallback mode (and it just does nothing).

1
2
3
4
5
6
7
8
9
10
11
function pjaxIsSupported (){
return (
window.history &&
window.history.pushState &&
window.history.replaceState &&
// pushState isn’t reliable on iOS until 5.
!navigator.userAgent.match(
/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/
)
);
}

2. 查看Pjax默认配置

/themes/next/layout/_scripts/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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<script>
var pjax = new Pjax({
//designate the part to replace using CSS selectors
//使用css选择器指定使用pjax刷新的部分
selectors: [
'head title',
'#page-configurations',
'.content-wrap',
'.post-toc-wrap',
'#pjax'
],
//This is an object containing callbacks that can be used to switch old elements with new elements.
//回调函数,用返回的新元素替换旧元素
switches: {
'.post-toc-wrap': Pjax.switches.innerHTML
},
//Function that allows you to add behavior for analytics. By default it tries to track a pageview with Google Analytics (if it exists on the page). It's called every time a page is switched, even for history navigation.
analytics: false,
//When set to true, Pjax appends a timestamp query string segment to the requested URL in order to skip the browser cache.
cacheBust: false,
//Set this to false to disable scrolling, which will mean the page will stay in that same position it was before loading the new elements.
scrollTo : !CONFIG.bookmark.enable
});

window.addEventListener('pjax:success', () => {
//重载页面中的<script>链接,使其生效。
//包括<script src="xx.js"></script>形式
//包括<script>funtion(){}</script>形式
document.querySelectorAll('script[pjax], script#page-configurations, #pjax script').forEach(element => {
var id = element.id || '';
var src = element.src || '';
var code = element.text || element.textContent || element.innerHTML || '';
var parent = element.parentNode;
parent.removeChild(element);
var script = document.createElement('script');
if (id !=='') {
script.id = element.id;
}
if (src !== '') {
script.src = src;
// Force synchronous loading of peripheral JS.
script.async = false;
}
if (code !== '') {
script.appendChild(document.createTextNode(code));
}
parent.appendChild(script);
});
NexT.boot.refresh();
// Define Motion Sequence & Bootstrap Motion.
if (CONFIG.motion.enable) {
NexT.motion.integrator
.init()
.add(NexT.motion.middleWares.postList)
.bootstrap();
}
NexT.utils.updateSidebarPosition();
});
</script>

Pjax默认配置指定了需要Pjax刷新的元素,并且为了防止Pjax刷新的内容中包含的<script>标签不生效,移除并重新添加了一遍,使其生效。

3. 修复Pjax造成的hexo-encrypt失效的情况

查看报错,是decrypt函数没有找到,crypto-js.js也没有加载。原因是Pjax取回.content-wrap中的内容,把它刷新到了dom里面,但这个加密文章页面的.content-wrap里面,包含者decrypt函数和crypto-js.js的链接,并没有起作用。解决方法就是更改Pjax默认配置,刷新.post-body中的<script> ,让.content-wrap里面的<script>生效。

/themes/next/layout/_scripts/pjax.swig
1
2
3
document.querySelectorAll('.post-body script, script[pjax], script#page-configurations, #pjax script').forEach(element => {
...
}

4. 修复Disqus报错

我的Next版本是v7.4.0,配置Disqus的文件是\themes\next\layout\_third-party\comments\disqus.swig,还有一个disqusjs.swig,跟它没关系。

开启Pjax后,Disqus评论框报错原因有两个:

一个是开启disqus.lazyload后,document.getElementById('comments') 会报找不到元素的错误,原因是到了有Disqus评论框的页面,window.addEventListener('scroll', disqus_scroll)就会绑定滚动事件,而通过Pjax再去没有Disqus评论框的页面,这个滚动事件仍然绑定着,但是document.getElementById('comments')找不到评论框div。解决思路是在没有Disqus评论框的页面解除滚动事件的绑定,在有Disqus评论框的页面添加滚动事件的绑定。博主为了省事,直接用try-catch包裹起来,防止它报错。

一个是如果页面很短,页面刷新后,应该立即加载Disqus评论框,而Disqus评论框是通过window.addEventListener('load', loadComments, false)加载的,Pjax刷新局部页面,没有load事件发生,Disqus评论框就不会加载。博主的解决方式是监听pjax:success事件,用来在Pjax刷新完页面后加载评论框。

开启Pjax后,Disqus文章留言数只能显示一次的原因:

它的<script>标签只加载了一次。

/themes/next/layout/_third-party/comments/disqus.swig
1
2
3
4
5
6
7
8
function loadCount() {
var d = document, s = d.createElement('script');
s.src = 'https://{{ theme.disqus.shortname }}.disqus.com/count.js';
s.id = 'dsq-count-scr';
(d.head || d.body).appendChild(s);
}
// defer loading until the whole page loading is completed
window.addEventListener('load', loadCount, false);

解决方法是和Pjax默认配置的做法一样,移除再添加<script>标签就好了。

下面是解决上面提到的三个问题的代码:

/themes/next/layout/_third-party/comments/disqus.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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
{%- if theme.disqus.count %}
<script>
/*
function loadCount() {
var d = document, s = d.createElement('script');
s.src = 'https://{{ theme.disqus.shortname }}.disqus.com/count.js';
s.id = 'dsq-count-scr';
(d.head || d.body).appendChild(s);
}
// defer loading until the whole page loading is completed
window.addEventListener('load', loadCount, false);
*/
//disqus的script src="https://blog-maplesugar.disqus.com/count.js" id="dsq-count-scr"
//disqus先加载count.js,count.js又去加载count-data.js?1=hexo%2Fnext-jsdelivrcdn-for-postimg%2F,并且传入网页url。
//注意,这两个加载都是通过添加script标签实现的。
//启用pjax后,首先是count.js会缓存下来,并不去加载。其次是count-data.js由于已经在html中,也不能加载。
//所以首先解决js问题,通过在url后面添加随机参数解决。
//其次通过移除和添加标签,使得count-data.js生效。
//实践证明,仅仅移除count-data.js标签不起作用
function loadJs(url, callback) {
var done = false;
var script = document.createElement('script');
script.type = 'text/javascript';//do not 'application/javascript',because Low version of the browser is not compatible
script.language = 'javascript';
script.charset = "utf-8";
script.src = url;
script.id = "dsq-count-scr";
script.onload = script.onreadystatechange = function () {
if (!done && (!script.readyState || script.readyState == 'loaded' || script.readyState == 'complete')) {
done = true;
script.onload = script.onreadystatechange = null;
if (callback) {
//console.log('load ' + url + ' success.');
callback;
}
}
};
document.getElementsByTagName("head")[0].appendChild(script);
};
function removeScript() {
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
if (scripts[i] && scripts[i].src && scripts[i].src.indexOf("disqus") != -1) {
//console.log(scripts[i]);
scripts[i].parentNode.removeChild(scripts[i]);
}
}
}
if (window.DISQUSWIDGETS) {
removeScript();
window.DISQUSWIDGETS=undefined;
loadJs("https://blog-maplesugar.disqus.com/count.js?"+Math.random(),null);
}else{
loadJs("https://blog-maplesugar.disqus.com/count.js?"+Math.random(),null);
}
</script>
{%- endif %}
{# 启用pjax,disqus报错是因为,虽然切换页面pjax每次都重新加载这个swig,但是disqus绑定了事件是不能消除的,在其他页面加载了disqus后,然后在没有#comments的页面滚动,就会报错 #}
{# 所以需要在不需要disqus的页面取消事件绑定。但是这样解决,disqus.lazyload就会失效,用try包裹,不弹出错误就好了。 #}
{%- if page.comments %}
<script>
function disqus_config() {
this.page.url = {{ page.permalink | json }};
this.page.identifier = {{ page.path | json }};
this.page.title = '{{ page.title | addslashes }}';
{%- if __('disqus') !== 'disqus' -%}
this.language = '{{ __('disqus') }}';
{% endif -%}
};
function loadComments() {
try {
console.log("disqus loadComments");
if (window.DISQUS) {
DISQUS.reset({
reload: true,
config: disqus_config
});
} else {
var d = document, s = d.createElement('script');
s.src = 'https://{{ theme.disqus.shortname }}.disqus.com/embed.js';
s.setAttribute('data-timestamp', '' + +new Date());
(d.head || d.body).appendChild(s);
}
} catch (e) {
//console.log(e);
}
}
{%- if theme.disqus.lazyload %}
(function() {
try {
var offsetTop = document.getElementById('comments').offsetTop - window.innerHeight;
if (offsetTop <= 0) {
// load directly when there's no a scrollbar
//如果是通过pjax加载的,页面将不会有load事件
window.addEventListener('load', loadComments, false);
window.addEventListener('pjax:success', () => {
loadComments();
});
} else {
var disqus_scroll = () => {
try {
// offsetTop may changes because of manually resizing browser window or lazy loading images.
var offsetTop = document.getElementById('comments').offsetTop - window.innerHeight;
var scrollTop = window.scrollY;

// pre-load comments a bit? (margin or anything else)
if (offsetTop - scrollTop < 60) {
window.removeEventListener('scroll', disqus_scroll);
loadComments();
}
} catch (e) {
//console.log(e);
}
};
window.addEventListener('scroll', disqus_scroll);
}
} catch (e) {
//console.log(e);
}
})();
{% else %}
//如果是通过pjax加载的,页面将不会有load事件
window.addEventListener('load', loadComments, false);
window.addEventListener('pjax:success', () => {
loadComments();
});
{%- endif %}
</script>
{%- endif %}

5. 其他解决方法

Pjax有它自己的api,可能通过它的api能很好解决上面的问题。博主为了省事,就这么将就着用了,基本思路不会有问题,供其他小伙伴参考。