周天记 我在人间混日子

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插件,你的引入顺序应该是:

  1. jquery
  2. jquery.pjax
  3. 你自己的 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:添加浏览器历史记录

fragmenttimeout 和上面的链接选项是一样的。

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

评论

😃
暂无评论数据

暂无评论数据