admin 发表于 2025-7-21 14:38:48

Typecho集成Pjax

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);
      }
      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'),
            textarea = response.getElementsByTagName('textarea');
      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更新页面的时候,也会把上面的代码插入到页面中。
原文地址:https://www.misterma.com/archives/946/

页: [1]
查看完整版本: Typecho集成Pjax