查看: 32|回复: 0

[技术教程] Typecho集成Pjax

[复制链接]
  • 打卡等级:常驻代表
  • 打卡总天数:47
  • 打卡月天数:1
  • 打卡总奖励:373
  • 最近打卡:2025-08-14 14:42:50
发表于 2025-7-21 14:38:48 | 显示全部楼层 |阅读模式
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:
  1. // 获取当前域名
  2. const currentDomain = window.location.hostname;

  3. $('a').each((index, element) => {
  4.   const href = $(element).attr('href');
  5.   const target = $(element).attr('target');

  6.   // 检查链接是否包含当前域名,且不含有 target="_blank"
  7.   if (href && href.includes(currentDomain) && !target) {
  8.     $(element).addClass('pjax-link');
  9.   }
  10. });
复制代码
只有包含 pjax-link class 的链接才会添加PJAX。

PJAX容器元素
PJAX需要一个容器元素,AJAX请求完成后只会更新容器元素内的内容,每个页面都需要有相同的容器元素,如果没有容器元素页面就会刷新。

如果你想省事的话,可以直接在body内放一个id为app的div,把除了script的所有内容,包括顶部导航栏、内容区域、和底部内容都放到这个 div 里,请求完成后会直接更新这个 div 内的所有内容。你也不需要手动更改导航栏链接的选中状态。

上面的方式比较简单,但是也会增加一些不必要的 DOM 处理和替换。如果你能手动处理导航栏样式的话,可以只给内容部分添加容器元素,AJAX 请求完成后也只会更新内容部分。

链接绑定PJAX
上面已经给需要PJAX请求的链接加入了一个pjax-link的class,下面就给这些链接绑定PJAX:
  1. $(document).pjax('.pjax-link', '#main', {
  2.   fragment: '#main',
  3.   timeout: 20000
  4. });
复制代码
上面的 $(document).pjax 的第一个参数就是绑定PJAX的链接,第二个参数是容器元素,我的容器元素是一个id为main的div,第三个参数是选项。

下面是选项说明:

fragment:新页面的容器元素
timeout:超时(毫秒),如果超时页面就会刷新
给链接绑定PJAX后,点击链接就可以无刷新跳转了。

表单绑定PJAX
Typecho的评论和搜索是通过form表单提交的,为了方便区分,你可以给这两个form表单添加一个id,比如comment-form和search-form。

下面给表单绑定PJAX:
  1. $(document).on('submit', '#comment-form', ev => {
  2.   $.pjax.submit(ev, '#main', {
  3.     fragment: '#main',
  4.     replace: true,
  5.     push: false,
  6.     timeout: 20000
  7.   });
  8. });
复制代码
上面的 $(document).on 是监听评论表单的提交事件,我监听的是id为 comment-form 的评论表单。 $.pjax.submit 是使用PJAX提交表单,第一个参数是表单提交事件的event对象,第二个参数是容器元素,第三个参数是选项。

下面是用到的一些选项说明:

replace:只替换 URL,不添加历史记录
push:添加浏览器历史记录
fragment 和 timeout 和上面的链接选项是一样的。

PJAX事件
PJAX提供了一些事件,这些事件会在PJAX请求的不同阶段被触发,下面是PJAX提供的事件:
  1. $(document).on('pjax:start', () => {
  2.   // PJAX 即将开始请求
  3. });

  4. $(document).on('pjax:send', () => {
  5.   // PJAX 开始请求
  6. });

  7. $(document).on('pjax:complete', () => {
  8.   // PJAX 请求完成
  9. });

  10. $(document).on('pjax:end', ev => {
  11.   // PJAX 页面更新完成
  12. });
复制代码
其中的 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代码能在文章页和独立页面的容器元素中输出:
  1. <script type="text/javascript">
  2.   (function() {
  3.     window.TypechoComment = {
  4.       dom: function(id) {
  5.         return document.getElementById(id);
  6.       },
  7.       create: function(tag, attr) {
  8.         var el = document.createElement(tag);
  9.         for (var key in attr) {
  10.           el.setAttribute(key, attr[key]);
  11.         }
  12.         return el;
  13.       },
  14.       reply: function(cid, coid) {
  15.         var comment = this.dom(cid),
  16.             parent = comment.parentNode,
  17.             response = this.dom('<?php echo $this->respondId; ?>'),
  18.             input = this.dom('comment-parent'),
  19.             form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0],
  20.             textarea = response.getElementsByTagName('textarea')[0];
  21.         if (null == input) {
  22.           input = this.create('input', {
  23.             'type': 'hidden',
  24.             'name': 'parent',
  25.             'id': 'comment-parent'
  26.           });
  27.           form.appendChild(input);
  28.         }
  29.         input.setAttribute('value', coid);
  30.         if (null == this.dom('comment-form-place-holder')) {
  31.           var holder = this.create('div', {
  32.             'id': 'comment-form-place-holder'
  33.           });
  34.           response.parentNode.insertBefore(holder, response);
  35.         }
  36.         comment.appendChild(response);
  37.         this.dom('cancel-comment-reply-link').style.display = '';
  38.         if (null != textarea && 'text' == textarea.name) {
  39.           textarea.focus();
  40.         }
  41.         return false;
  42.       },
  43.       cancelReply: function() {
  44.         var response = this.dom('<?php echo $this->respondId; ?>'),
  45.             holder = this.dom('comment-form-place-holder'),
  46.             input = this.dom('comment-parent');
  47.         if (null != input) {
  48.           input.parentNode.removeChild(input);
  49.         }
  50.         if (null == holder) {
  51.           return true;
  52.         }
  53.         this.dom('cancel-comment-reply-link').style.display = 'none';
  54.         holder.parentNode.insertBefore(response, holder);
  55.         return false;
  56.       }
  57.     };
  58.   })();
  59. </script>
复制代码
PJAX更新页面的时候,也会把上面的代码插入到页面中。
原文地址:https://www.misterma.com/archives/946/

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表