天天看點

【Jquery】Pjax的了解與應用什麼是pjax?為什麼要用pjax?如何使用pjax?pjax的原理pjax失效情況DemoDemo2

什麼是pjax?

現在很多網站( facebook, twitter)都支援這樣的一種浏覽方式, 當你點選一個站内的連結的時候, 不是做頁面跳轉, 而是隻是站内頁面重新整理。 這樣的使用者體驗, 比起整個頁面都閃一下來說, 好很多。 其中有一個很重要的組成部分, 這些網站的ajax重新整理是支援浏覽器曆史的, 重新整理頁面的同時, 浏覽器位址欄位上面的位址也是會更改, 用浏覽器的回退功能也能夠回退到上一個頁面。 那麼如果我們想要實作這樣的功能, 我們如何做呢? 我發現pjax提供了一個腳本支援這樣的功能。 pjax項目位址在 https://github.com/defunkt/jquery-pjax 。

為什麼要用pjax?

pjax有好幾個好處:

  • 使用者體驗提升。

    頁面跳轉的時候人眼需要對整個頁面作重新識别, 重新整理部分頁面的時候, 隻需要重新識别其中一塊區域。 同時, 由于重新整理部分頁面的時候提供了一個loading的提示, 以及在重新整理的時候舊頁面還是顯示在浏覽器中, 使用者能夠容忍更長的頁面加載時間。

  • 極大地減少帶寬消耗和伺服器消耗。

    由于隻是重新整理部分頁面, 大部分的請求(css/js)都不會重新擷取, 網站帶有使用者登入資訊的外框部分都不需要重新生成了。

如何使用pjax?

  • 引入jquery和jquery.pjax.js
  • 注冊事件
/**
 * 方式一 按鈕父節點監聽事件
 *
 * @param selector  觸發點選事件的按鈕
 * @param container 展示重新整理内容的容器,也就是會被替換的部分
 * @param options   參數
 */
$(document).pjax(selector, [container], options);
// 方式二 直接對按鈕監聽,可以不用指定容器,使用按鈕的data-pjax屬性值查找容器
$("a[data-pjax]").pjax();
// 方式三 正常的點選事件監聽方式
$(document).on('click', 'a', $.pjax.click);
$(document).on('click', 'a', function(event) {
 var container = $(this).closest('[data-pjax-container]');
 $.pjax.click(event, container);
});
// 下列是源碼中介紹的其他用法,
// 表單送出
$(document).on('submit', 'form', function(event) {
 var container = $(this).closest('[data-pjax-container]');
 $.pjax.submit(event, container);
});
// 加載内容到指定容器
$.pjax({ url: this.href, container: '#main' });
// 重新目前頁面容器的内容
$.pjax.reload('#container');
           

具體文檔詳見:https://github.com/defunkt/jquery-pjax

pjax的原理

為了能夠處理問題, 我們需要能夠了解pjax的運作方式。 pjax的代碼隻有一個檔案: https://github.com/defunkt/jquery-pjax/blob/master/jquery.pjax.js 如果有能力, 可以自己去看一遍。 我這裡解釋一下原理。 首先, 我們在html裡面指定, 需要做pjax的連結内容是哪些, 以及點選之後需要更新的部分(放在data-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-PJAX的HEADER辨別, 伺服器在收到這樣的請求的時候, 就知道隻需要渲染部分頁面傳回就可以了。

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

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+[-]|WebApps\/.+CFNetwork)/)
           

另一個是當發現請求一段時間沒有回複的時候(可以設定參數timeout), 直接做頁面跳轉。

options.beforeSend = function(xhr, settings) {
  if (settings.timeout > ) {
    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 = 
           

pjax失效情況

會有一些情況導緻pjax失效,下面結合源碼分析下(省略部分無關代碼)

function handleClick(event, container, options) {
    ...

    //  點選事件的事件源不是a标簽。使用a标簽可以做到對舊版本浏覽器的相容,是以不建議使用其他标簽注冊事件
    if (link.tagName.toUpperCase() !== 'A')
        throw "$.fn.pjax or $.pjax.click requires an anchor element"

    //  使用滑鼠滾輪點選(新标簽頁打開)
    // 點選超連結的同時按下Shift、Ctrl、Alt和Meta(在Windows鍵盤中是Windows鍵,在蘋果機中是Cmd鍵)
    // 作用分别代表新視窗打開、新标簽打開(不切換标簽)、下載下傳、新标簽打開(切換标簽)
    if (event.which >  || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
        return

    //  跨域(網絡通訊協定,域名不一緻)
    if (location.protocol !== link.protocol || location.hostname !== link.hostname)
        return

    //  目前頁面的錨點定位
    if (link.href.indexOf('#') > - && stripHash(link) == stripHash(location))
        return

    //  已經阻止元素發生預設的行為(url跳轉)
    if (event.isDefaultPrevented())
        return

    ...

    var clickEvent = $.Event('pjax:click')
    $(link).trigger(clickEvent, [opts])

    //  pjax:click事件回調中已經阻止元素發生預設的行為(url跳轉)
    if (!clickEvent.isDefaultPrevented()) {
        pjax(opts)
        event.preventDefault()// 阻止url跳轉
        $(link).trigger('pjax:clicked', [opts])
    }
}
           

除了上述情況之外,還有下列幾種情況:

  • ajax請求失敗,或者timeout後請求被中止
  • 目前頁面X-PJAX-Version和請求的新頁面版本不一緻
  • 請求得到完整的頁面(包含html标簽)卻沒設定fragment參數

Demo

index.php

<!DOCTYPE html>
<html>
<head>
    <title>pjax</title>
    <meta charset="utf-8">
</head>
<body>
<h1>My Site</h1>
<div>
    Go to <a href="res1.php">第一頁</a>.<a href="res2.php">第二頁</a>
</div>
<div id="container"></div>
</body>
<script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
<script src="jquery.pjax.js"></script>
<script type="text/javascript">
    $(document).pjax('a', '#container')
</script>
</html>
           

res1.php

<?php
echo "<div style='background:red;'>第一頁</div>";
           

res2.php

<?php
echo "<div style='background:red;'>第二頁</div>";
           

效果:

【Jquery】Pjax的了解與應用什麼是pjax?為什麼要用pjax?如何使用pjax?pjax的原理pjax失效情況DemoDemo2

Demo2

index.html

<!DOCTYPE html>
<html>
<head>
  <title>pjax</title>
    <meta charset="utf-8">
</head>
<body>
  <h1>My Site</h1>
  <div>
    <input type="button" id="th" value="GO">
  </div>
  <div id="container"></div>    
</body>
<script src="../jquery-2.1.4.min.js"></script>
<script src="../jquery.pjax.js"></script>
<script type="text/javascript">
$(function(){
    $('#th').click(function(){
        $.pjax({
            url: './res3.php',
            container: '#container'
        });
    });
});
</script>
</html>
           

re3.php

<?php 
echo "<div style='background:red;'>第三頁</div>";