Pjax 支持回退的无刷新技术

Logo

file

什么是 Pjax?

现在很多网站 ( facebook, twitter ) 都支持这样的一种浏览方式, 当你点击一个站内的链接的时候, 不是做页面跳转, 而是只是站内页面刷新. 这样的用户体验, 比起整个页面都闪一下来说, 好很多. 其中有一个很重要的组成部分, 这些网站的 ajax 刷新是支持浏览器历史的, 刷新页面的同时浏览器地址栏位上面的地址也是会更改, 用浏览器的回退功能也能够回退到上一个页面.

file

file

启动和不启动 Pjax 点击的网络请求有明显的差别. 点击链接是跳转的, 勾选了之后, 链接都是变成了 ajax 刷新, 但是地址栏也会更改, 网络请求变少了.

为什么用 Pjax?

用户体验提升

页面跳转的时候人眼需要对整个页面作重新识别, 刷新部分页面的时候, 只需要重新识别其中一块区域.

极大地减少带宽消耗和服务器消耗.

由于只是刷新部分页面, 大部分的请求( css / js ) 都不会重新获取, 网站带有用户登录信息的外框部分都不需要重新生成了.

如何使用 Pjax?

直接看 官方文档 就可以了.

为了能够处理问题, 我们需要能够理解 pjax 的运作方式. pjax 的代码只有一个文件: github.com/defunkt/jquery-pjax/blo...

1) 首先

我们在 HTML 里面指定, 需要做 Pjax 的链接内容有那些, 以及点击之后需要更新的部分 ( 放在 data-pjax 属性里面 )

$('a[data-pjax]').pjax()

当加载了 Pjax 脚本之后, 它会拦截这些链接的事件, 然后包装成一个 ajax 请求, 发送给服务器, 如下:

$.fn.pjax = function( container, options ) {
  return this.live('click.pjax', function(event){
    handleClick(event, container, options)
  })
}

function handleClick(event, container, options) {
  $.pjax($.extend({}, defaults, options))
  ...
  event.preventDefault()
}
var pjax = $.pjax = function( options ) {
  ...
  pjax.xhr = $.ajax(options)
}

下面代码块这个请求带有 X-PJAXHEADER 标识, 服务器在收到这样的请求的时候, 就知道只需要渲染部分页面返回就可以了:

xhr.setRequestHeader('X-PJAX', 'true')
xhr.setRequestHeader('X-PJAX-Container', context.selector)

2) 然后

Pjax 接受到返回的请求之后, 更新 data-pjax 指定的区域, 同时也会更新浏览器的地址:

options.success = function(data, status, xhr) {
  var container = extractContainer(data, xhr, options)
  ...
  if (container.title) document.title = container.title
  context.html(container.contents)
}

为了能够支持浏览器的后退, 利用到了 history 的 api, 记录下来对应的信息:

pjax.state = {
  id: options.id || uniqueId(),
  url: container.url,
  container: context.selector,
  fragment: options.fragment,
  timeout: options.timeout
}

if (options.push || options.replace) {
  window.history.replaceState(pjax.state, container.title, container.url)
}

当浏览器后退的时候, 拦截事件, 根据记录的历史信息, 产生一个新的 ajax 请求:

$(window).bind('popstate', function(event){
  var state = event.state
  if (state && state.container) {
    var container = $(state.container)
    if (container.length) {
      ...
      var options = {
        id: state.id,
        url: state.url,
        container: container,
        push: false,
        fragment: state.fragment,
        timeout: state.timeout,
        scrollTo: false
      }

      if (contents) {
        // pjax event is deprecated
        $(document).trigger('pjax', [null, options])
        container.trigger('pjax:start', [null, options])
        // end.pjax event is deprecated
        container.trigger('start.pjax', [null, options])

        container.html(contents)
        pjax.state = state

        container.trigger('pjax:end', [null, options])
        // end.pjax event is deprecated
        container.trigger('end.pjax', [null, options])
      } else {
        $.pjax(options)
      }
      ...
    }
  }
}

为了支持 fallback, 一个是在加载的时候判断浏览器是否支持 history push state API:

// Is pjax supported by this browser?
$.support.pjax = 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]|WebApps\/.+CFNetwork)/)

另一个是当发现请求一段时间没有回复的时候 ( 可以设置参数 timeout ), 直接做页面跳转.

options.beforeSend = function(xhr, settings) {
  if (settings.timeout > 0) {
    timeoutTimer = setTimeout(function() {
      if (fire('pjax:timeout', [xhr, options]))
        xhr.abort('timeout')
    }, settings.timeout)

    // Clear timeout setting so jquerys internal timeout isn't invoked
    settings.timeout = 0

3) 最后

既然都看到这里了, 你为什么不去实际使用一下 Pjax 呢? 有那么多好处, 我觉得几乎所有网站都应该采用 pjax. 赶紧用起来吧!

PS

感谢关注「GitHub 热门」公众号,带你了解技术圈内热门新鲜事!

file

本作品采用《CC 协议》,转载必须注明作者和本文链接
感谢关注「GitHub 热门」公众号
本帖由系统于 6年前 自动加精
Destiny
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 5
Summer

阿罗干嘛搞个跟 Monkey 一样的头像,差点认错人,之前头像挺好的

7年前 评论
Destiny

@Summer 这个头像好帅......那待会我 ps 下... 哈哈

7年前 评论
Destiny

@Summer 这个不错吧龙哥...

7年前 评论
bestcyt

你好,我想请教下,我这边绑定a标签后,替换的区域中有个form表单,然后我用的也是pjax的submit;我发现一个问题,就是比如我重复点击3次这个pjax绑定的a标签菜单,然后填写form内容的话,提交会有三次提交?这个是为什么呢?求解惑

5年前 评论
Destiny

@bestcyt 每次 click 事件先解绑 off 在绑定 on

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!