PJAX(pushState + ajax)是一种页面加载方式,点击链接时,通过AJAX无刷新的从服务器请求 HTML 内容,然后用请求到的内容来更新页面,可以实现类似于单页应用的使用体验,而且不会影响搜索引擎抓取页面。
兼容性
Typecho程序在提交评论时,除了提交评论用户能看到的评论表单内容外,还会提交一个隐藏的表单来校验评论是否是从本网站提交的,也会检查评论的提交页面URL是否是文章 URL。如果要使用PJAX就需要在Typecho后台的评论设置关闭下面两项设置:
- 检查评论来源页 URL 是否与文章链接一致
- 开启反垃圾保护
关闭后可以正常使用 PJAX 无刷新提交评论,但是因为不会校验评论提交,所以无法防止其它程序提交垃圾评论。
下载和引入
这里使用的 PJAX 是一个 jQuery 插件,需要依赖 jQuery 才能使用。
你可以直接使用 script
引入 https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js
国内访问的速度可能会慢一些,你也可以直接下载JS文件,通过script引入下载的JS文件。
这是一个jQuery插件,你的引入顺序应该是:
- jquery
- jquery.pjax
- 你自己的 JS 文件
我下面的JS代码也会使用jQuery的方式来操作DOM之类的。
PJAX链接添加class
PJAX使用jQuery选择器来给链接添加PJAX请求,如果你直接使用 $('a') 来选择链接的话,会给所有链接都添加PJAX。我个人的选择是只给包含我网站域名的链接添加PJAX,而且链接不能包含target="_blank"属性。
下面给包含我网站域名的链接添加一个pjax-link的class:
// 获取当前域名
const currentDomain = window.location.hostname;
$('a').each((index, element) => {
const href = $(element).attr('href');
const target = $(element).attr('target');
// 检查链接是否包含当前域名,且不含有 target="_blank"
if (href && href.includes(currentDomain) && !target) {
$(element).addClass('pjax-link');
}
});
只有包含 pjax-link class
的链接才会添加PJAX。
PJAX容器元素
PJAX需要一个容器元素,AJAX请求完成后只会更新容器元素内的内容,每个页面都需要有相同的容器元素,如果没有容器元素页面就会刷新。
如果你想省事的话,可以直接在body内放一个id为app的div,把除了script的所有内容,包括顶部导航栏、内容区域、和底部内容都放到这个 div 里,请求完成后会直接更新这个 div 内的所有内容。你也不需要手动更改导航栏链接的选中状态。
上面的方式比较简单,但是也会增加一些不必要的 DOM 处理和替换。如果你能手动处理导航栏样式的话,可以只给内容部分添加容器元素,AJAX 请求完成后也只会更新内容部分。
链接绑定PJAX
上面已经给需要PJAX请求的链接加入了一个pjax-link的class,下面就给这些链接绑定PJAX:
$(document).pjax('.pjax-link', '#main', {
fragment: '#main',
timeout: 20000
});
上面的 $(document).pjax
的第一个参数就是绑定PJAX的链接,第二个参数是容器元素,我的容器元素是一个id为main的div,第三个参数是选项。
下面是选项说明:
- fragment:新页面的容器元素
- timeout:超时(毫秒),如果超时页面就会刷新
给链接绑定PJAX后,点击链接就可以无刷新跳转了。
表单绑定PJAX
Typecho的评论和搜索是通过form表单提交的,为了方便区分,你可以给这两个form表单添加一个id,比如comment-form和search-form。
下面给表单绑定PJAX:
$(document).on('submit', '#comment-form', ev => {
$.pjax.submit(ev, '#main', {
fragment: '#main',
replace: true,
push: false,
timeout: 20000
});
});
上面的 $(document).on
是监听评论表单的提交事件,我监听的是id为 comment-form
的评论表单。 $.pjax.submit
是使用PJAX提交表单,第一个参数是表单提交事件的event对象,第二个参数是容器元素,第三个参数是选项。
下面是用到的一些选项说明:
- replace:只替换 URL,不添加历史记录
- push:添加浏览器历史记录
fragment
和 timeout
和上面的链接选项是一样的。
PJAX事件
PJAX提供了一些事件,这些事件会在PJAX请求的不同阶段被触发,下面是PJAX提供的事件:
$(document).on('pjax:start', () => {
// PJAX 即将开始请求
});
$(document).on('pjax:send', () => {
// PJAX 开始请求
});
$(document).on('pjax:complete', () => {
// PJAX 请求完成
});
$(document).on('pjax:end', ev => {
// PJAX 页面更新完成
});
其中的 pjax:end
事件是用的比较多的,这个事件会在页面更新完成后触发,一些页面初始化的JS,比如代码高亮之类的就会放到这个事件里。
pjax:end
事件的event对象的 currentTarget.URL
可以获取点击链接的URL,如果你需要设置导航栏链接选中状态的话,可以通过这个URL来判断设置。
评论回复问题
如果你从其它页面通过PJAX进入文章页,也就是你一开始打开的不是文章页,点击回复评论的时候,评论表单可能无法插入到回复区域。
Typecho的文章页和独立页面的head区域会输出一段JS代码,这段JS代码包含了页面显示的所有评论的coid,当你点击回复评论的时候,就会通过 oid来把评论表单插入到回复位置。
PJAX的容器元素一般都是body里的元素,更新页面的时候不会替换head里的元素,所以回复相关的JS代码也就无法被插入到页面。
下面的代码需要放到PJAX的容器元素里,你可以放到 comment.php
或其它评论相关的PHP文件中,需要确保 JS代码能在文章页和独立页面的容器元素中输出:
<script type="text/javascript">
(function() {
window.TypechoComment = {
dom: function(id) {
return document.getElementById(id);
},
create: function(tag, attr) {
var el = document.createElement(tag);
for (var key in attr) {
el.setAttribute(key, attr[key]);
}
return el;
},
reply: function(cid, coid) {
var comment = this.dom(cid),
parent = comment.parentNode,
response = this.dom('<?php echo $this->respondId; ?>'),
input = this.dom('comment-parent'),
form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0],
textarea = response.getElementsByTagName('textarea')[0];
if (null == input) {
input = this.create('input', {
'type': 'hidden',
'name': 'parent',
'id': 'comment-parent'
});
form.appendChild(input);
}
input.setAttribute('value', coid);
if (null == this.dom('comment-form-place-holder')) {
var holder = this.create('div', {
'id': 'comment-form-place-holder'
});
response.parentNode.insertBefore(holder, response);
}
comment.appendChild(response);
this.dom('cancel-comment-reply-link').style.display = '';
if (null != textarea && 'text' == textarea.name) {
textarea.focus();
}
return false;
},
cancelReply: function() {
var response = this.dom('<?php echo $this->respondId; ?>'),
holder = this.dom('comment-form-place-holder'),
input = this.dom('comment-parent');
if (null != input) {
input.parentNode.removeChild(input);
}
if (null == holder) {
return true;
}
this.dom('cancel-comment-reply-link').style.display = 'none';
holder.parentNode.insertBefore(response, holder);
return false;
}
};
})();
</script>
PJAX更新页面的时候,也会把上面的代码插入到页面中。
原文地址:Typecho主题开发 - PJAX
暂无评论数据