pjax 使用小結(jié)

pjax

前言


上周看到一篇文章在分析簡書 我的主頁 頁面 3 個 tab 頁切換的 bug,起先以為是尋常的樣式 bug 而已沒怎么在意,后來在文章中看到 pjax 這個術(shù)語,長得和 ajax 有點像,遂去了解了下。

簡介


雖然傳統(tǒng)的 ajax 方式可以異步無刷新改變頁面內(nèi)容,但無法改變頁面 URL,因此有種方案是在內(nèi)容發(fā)生改變后通過改變 URLhash 的方式獲得更好的可訪問性(如 https://liyu365.github.io/BG-UI/tpl/#page/desktop.html),但是 hash 的方式有時候不能很好的處理瀏覽器的前進、后退,而且常規(guī)代碼要切換到這種方式還要做不少額外的處理。而 pjax 的出現(xiàn)就是為了解決這些問題,簡單的說就是對 ajax 的加強。

pjax 結(jié)合 pushState 和 ajax 技術(shù), 不需要重新加載整個頁面就能從服務(wù)器加載 Html 到你當(dāng)前頁面,這個 ajax 請求會有永久鏈接、title 并支持瀏覽器的回退/前進按鈕。

pjax 項目地址在 https://github.com/defunkt/jquery-pjax 。 實際的效果見: http://pjax.herokuapp.com 沒有勾選 pjax 的時候點擊鏈接是跳轉(zhuǎn)的, 勾選了之后鏈接都是變成了 ajax 刷新(實際效果如下圖的請求內(nèi)容對比)。

不使用pjax

使用pjax
優(yōu)點:
  • 減輕服務(wù)端壓力

按需請求,每次只需加載頁面的部分內(nèi)容,而不用重復(fù)加載一些公共的資源文件和不變的頁面結(jié)構(gòu),大大減小了數(shù)據(jù)請求量,以減輕對服務(wù)器的帶寬和性能壓力,還大大提升了頁面的加載速度。

  • 優(yōu)化頁面跳轉(zhuǎn)體驗

常規(guī)頁面跳轉(zhuǎn)需要重新加載畫面上的內(nèi)容,會有明顯的閃爍,而且往往和跳轉(zhuǎn)前的頁面沒有連貫性,用戶體驗不是很好。如果再遇上頁面比較龐大、網(wǎng)速又不是很好的情況,用戶體驗就更加雪上加霜了。使用pjax后,由于只刷新部分頁面,切換效果更加流暢,而且可以定制過度動畫,在等待頁面加載的時候體驗就比較舒服了。

缺點:
  • 不支持一些低版本的瀏覽器(如IE系列)

pjax使用了pushState來改變地址欄的url,這是html5中history的新特性,在某些舊版瀏覽器中可能不支持。不過pjax會進行判斷,功能不適用的時候會執(zhí)行默認的頁面跳轉(zhuǎn)操作。

  • 使服務(wù)端處理變得復(fù)雜

要做到普通請求返回完整頁面,而pjax請求只返回部分頁面,服務(wù)端就需要做一些特殊處理,當(dāng)然這對于設(shè)計良好的后端框架來說,添加一些統(tǒng)一處理還是比較容易的,自然也沒太大問題。另外,即使后臺不做處理,設(shè)置pjax的fragment參數(shù)來達到同樣的效果。

綜合來看,pajx 的優(yōu)點很強勢,缺點也幾乎可以忽略,還是非常值得推薦的,尤其是類似博客這種大部分情況下只有主體內(nèi)容變化的網(wǎng)站。關(guān)鍵它使用簡單、學(xué)習(xí)成本小,即時全站只有極個別頁面能用得到,嘗試下沒什么損失。pjaxgithub 主頁介紹的已經(jīng)很詳細了,想了解更多可以看下源碼。

用法


  1. 引入 jquery 和 jquery.pjax.js
  2. 注冊事件
/**
  * 方式一 按鈕父節(jié)點監(jiān)聽事件
  *
  * @param selector  觸發(fā)點擊事件的按鈕
  * @param container 展示刷新內(nèi)容的容器,也就是會被替換的部分
  * @param options   參數(shù)
  */
$(document).pjax(selector, [container], options);

// 方式二 直接對按鈕監(jiān)聽,可以不用指定容器,使用按鈕的data-pjax屬性值查找容器
$("a[data-pjax]").pjax();

// 方式三 常規(guī)的點擊事件監(jiān)聽方式
$(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);
});
// 加載內(nèi)容到指定容器
$.pjax({ url: this.href, container: '#main' });
// 重新當(dāng)前頁面容器的內(nèi)容
$.pjax.reload('#container');

options默認參數(shù)說明


參數(shù)名 默認值 說明
timeout 650 ajax 超時時間(單位 ms ),超時后會執(zhí)行默認的頁面跳轉(zhuǎn),所以超時時間不應(yīng)過短,不過一般不需要設(shè)置
push true 使用 window.history.pushState 改變地址欄 url( 會添加新的歷史記錄 )
replace false 使用 window.history.replaceState 改變地址欄 url( 不會添加歷史記錄 )
maxCacheLength 20 緩存的歷史頁面?zhèn)€數(shù)( pjax 加載新頁面前會把原頁面的內(nèi)容緩存起來,緩存加載后其中的腳本會再次執(zhí)行 )
version 是一個函數(shù),返回當(dāng)前頁面的pjax-version,即頁面中 <meta http-equiv="x-pjax-version"> 標(biāo)簽內(nèi)容。使用 response.setHeader("X-PJAX-Version", "") 設(shè)置與當(dāng)前頁面不同的版本號,可強制頁面跳轉(zhuǎn)而不是局部刷新。
scrollTo 0 頁面加載后垂直滾動距離( 與原頁面保持一致可使過度效果更平滑 )
type "GET" ajax 的參數(shù),http 請求方式
dataType "html" ajax 的參數(shù),響應(yīng)內(nèi)容的 Content-Type
container 用于查找容器的 CSS 選擇器,[container] 參數(shù)沒有指定時使用
url link.href 要跳轉(zhuǎn)的連接,默認 a 標(biāo)簽href 屬性
target link pjax 事件參數(shù) erelatedTarget 屬性,默認為點擊的 a 標(biāo)簽
fragment 使用響應(yīng)內(nèi)容的指定部分( CSS 選擇器 )填充頁面,服務(wù)端不進行處理導(dǎo)致全頁面請求的時候需要使用該參數(shù),簡單的說就是對請求到的頁面做截取

除了上述參數(shù)外,ajax 的一些參數(shù)也是可以設(shè)置在這里的,不過一般沒什么必要。

// ajax 最終參數(shù): 
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options);

pjax失效情況


會有一些情況導(dǎo)致 pjax 失效,下面結(jié)合源碼分析下(省略部分無關(guān)代碼)

function handleClick(event, container, options) {
    ...
    
    // 1. 點擊事件的事件源不是a標(biāo)簽。使用a標(biāo)簽可以做到對舊版本瀏覽器的兼容,所以不建議使用其他標(biāo)簽注冊事件
    if (link.tagName.toUpperCase() !== 'A')
        throw "$.fn.pjax or $.pjax.click requires an anchor element"

    // 2. 使用鼠標(biāo)滾輪點擊(新標(biāo)簽頁打開)
    // 點擊超鏈接的同時按下Shift、Ctrl、Alt和Meta(在Windows鍵盤中是Windows鍵,在蘋果機中是Cmd鍵)
    // 作用分別代表新窗口打開、新標(biāo)簽打開(不切換標(biāo)簽)、下載、新標(biāo)簽打開(切換標(biāo)簽)
    if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
        return

    // 3. 跨域(網(wǎng)絡(luò)通訊協(xié)議,域名不一致)
    if (location.protocol !== link.protocol || location.hostname !== link.hostname)
        return

    // 4. 當(dāng)前頁面的錨點定位
    if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location))
        return

    // 5. 已經(jīng)阻止元素發(fā)生默認的行為(url跳轉(zhuǎn))
    if (event.isDefaultPrevented())
        return

    ...

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

    // 6. pjax:click事件回調(diào)中已經(jīng)阻止元素發(fā)生默認的行為(url跳轉(zhuǎn))
    if (!clickEvent.isDefaultPrevented()) {
        pjax(opts)
        event.preventDefault()// 阻止url跳轉(zhuǎn)
        $(link).trigger('pjax:clicked', [opts])
    }
}

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

  • ajax 請求失敗,或者 timeout 后請求被中止
  • 當(dāng)前頁面的 X-PJAX-Version 和請求的新頁面版本不一致
  • 請求得到完整的頁面(包含 html 標(biāo)簽)卻沒設(shè)置 fragment 參數(shù)

事件


1. 點擊鏈接后觸發(fā)的一系列事件, 除了 pjax:clickpjax:clicked 的事件源是點擊的按鈕,其他事件的事件源都是要替換內(nèi)容的容器??梢栽?pjax:start 事件觸發(fā)時開始過度動畫,在 pjax:end 事件觸發(fā)時結(jié)束過度動畫。
事件名 支持取消 參數(shù) 說明
pjax:click ? options 點擊按鈕時觸發(fā)。可調(diào)用 e.preventDefault(); 取消pjax
pjax:beforeSend ? xhr, options ajax 執(zhí)行 beforeSend 函數(shù)時觸發(fā),可在回調(diào)函數(shù)中設(shè)置額外的請求頭參數(shù)。可調(diào)用 e.preventDefault(); 取消 pjax
pjax:start xhr, options pjax 開始(與服務(wù)器連接建立后觸發(fā))
pjax:send xhr, options pjax:start 之后觸發(fā)
pjax:clicked options ajax 請求開始后觸發(fā)
pjax:beforeReplace contents, options ajax 請求成功,內(nèi)容替換渲染前觸發(fā)
pjax:success data, status, xhr, options 內(nèi)容替換成功后觸發(fā)
pjax:timeout ? xhr, options ajax 請求超時后觸發(fā)??烧{(diào)用 e.preventDefault(); 繼續(xù)等待 ajax 請求結(jié)束
pjax:error ? xhr, textStatus, error, options ajax 請求失敗后觸發(fā)。默認失敗后會跳轉(zhuǎn) url,如要阻止跳轉(zhuǎn)可調(diào)用 e.preventDefault();
pjax:complete xhr, textStatus, options ajax 請求結(jié)束后觸發(fā),不管成功還是失敗
pjax:end xhr, options pjax 所有事件結(jié)束后觸發(fā)
  • 注意:
    pjax:beforeReplace 事件前 pjax 會調(diào)用 extractContainer 函數(shù)處理頁面內(nèi)容,即以 script[src] 的形式引入的 js 腳本不會被重復(fù)加載,有必要可以改下源碼。
2. 瀏覽器前進/后退導(dǎo)航時觸發(fā)的事件(暫時沒做過多研究)
事件名 參數(shù) 說明
pjax:popstate 頁面導(dǎo)航方向: 'forward'/'back'(前進/后退)
pjax:start null, options pjax 開始
pjax:beforeReplace contents, options 內(nèi)容替換渲染前觸發(fā),如果緩存了要導(dǎo)航頁面的內(nèi)容則使用緩存,否則使用 pjax 加載
pjax:end null, options pjax 結(jié)束

服務(wù)端配置


我的項目是 Spring MVC + velocity 的組合,這里就以此為例子,其他語言和框架的服務(wù)端可以參考下這里的思路。
項目中使用的視圖解析器是 org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver 這個類,好處是可以使用模版技術(shù),每個頁面可以只寫主體內(nèi)容,公共部分統(tǒng)一寫在模版里面,是不是和 pjax 絕配哈!pjax.js 默認會在請求頭加入 X_PJAX 字段,并置為 true,所以以此來判斷是否 pjax 請求。對于普通的請求使用常規(guī)的模版,pjax 請求則使用空模版或者特定的模版。

  • 常規(guī)模版內(nèi)容:
<!doctype html>
<html>
    #set($basePath = "screen/contain")
    <head>
        <meta http-equiv="x-pjax-version" content="$!{X-PJAX-Version}"/>
        #parse("$basePath/html-head.vm")
    </head>
    <body>
        <section id="container">
            #parse("$basePath/frame-head.vm")
            #parse("$basePath/frame-left.vm")
            <section id="main-content">
                <section class="wrapper">
                    $screen_content ##頁面內(nèi)容
                </section>
            </section>
            #parse("$basePath/frame-bottom.vm")
        </section>
    </body>
</html>
  • 添加 SpringMVC 中的 Interceptor 攔截器,用于后端渲染前插入 pjax 處理
public class PjaxInterceptor extends HandlerInterceptorAdapter {

    @Value("${X-PJAX-Version}")
    private String X_PJAX_VERSION;

    /**
     * Controller 方法調(diào)用之后,頁面渲染前執(zhí)行
     * 
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (modelAndView != null) {
            boolean isPajx = Boolean.parseBoolean(request.getHeader("X-PJAX"));// 值為true表示pjax請求,這是重點
            ModelMap model = modelAndView.getModelMap();
            model.addAttribute("X-PJAX-Version", X_PJAX_VERSION);// 設(shè)置當(dāng)前頁面的pjax版本
            if (isPajx) {
                model.addAttribute("layout", "layout_pjax.vm");// 指定pjax請求時使用的模版
                // 在vm頁面中通過 #set($layout = 'xxx.vm') 的方式指定模版
                response.setHeader("X-PJAX-Version", X_PJAX_VERSION);// 響應(yīng)內(nèi)容的pjax版本,有新模版發(fā)布時,通過配置文件修改版本來強制頁面刷新
            }
        }
    }
}
  • xml 配置
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean id="pjaxInterceptor" class="xxx.PjaxInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • pjax 請求模版頁面:layout_pjax.vm
<title>$!{title}</title>
$screen_content

模版中使用 title 標(biāo)簽,這樣執(zhí)行 pjax 請求時不僅地址欄 url 會變化,而且瀏覽器標(biāo)簽的標(biāo)題內(nèi)容也會變化。

針對沒有服務(wù)端處理的方案如下:

// fragment一般同container一致
$(document).pjax('a[data-pjax]', '#main-content .wrapper', {fragment: '#main-content .wrapper'});

插件伴侶——NProgress


比較漂亮的一款進度條插件,用法十分簡單,很適合做pjax的過度動畫,詳細用法在該項目 github 上有介紹

NProgress
  • 示例:
$(document).on('pjax:start', NProgress.start).on('pjax:end', NProgress.done);

結(jié)語


雖然個人還是比較喜歡造輪子(有成就感),不怎么喜歡用插件(一般插件使用復(fù)雜,文檔少學(xué)習(xí)成本大,還不如自己寫),但看了 pjax 的源碼后感覺真要自己也使用 pushState + ajax 的方式簡單的實現(xiàn)它的功能,還是要踩不少坑的,所以為什么要放著這么個易用又精致的小輪子不用呢?我的項目是一個管理系統(tǒng),統(tǒng)一的 左側(cè)菜單 + 右側(cè)table 的布局,每個頁面都需要一個獨立訪問的 url,非常適合使用 pjax。由于使用的 velocity 模版技術(shù),集成 pjax 就是分分鐘的事,不僅對原先的代碼完全沒影響,還提升了加載速度,頁面過度效果更好,再用上了 NProgress,感覺逼格又上升不少,哈哈。

前段時間工作比較忙好久沒寫文章了,這段時間有點閑下來就抽空學(xué)了些新東西記錄下,對于這次的學(xué)習(xí)成果還是比較滿意的。( *_* )


轉(zhuǎn)載請注明出處:http://www.lxweimin.com/p/557cad38e7dd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 27,576評論 1 45
  • 【轉(zhuǎn)載】CSDN - 張林blog http://blog.csdn.net/XIAOZHUXMEN/articl...
    竿牘閱讀 3,506評論 1 14
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,503評論 1 92
  • 27、移動端響應(yīng)式布局開發(fā) 響應(yīng)式布局開發(fā) 1、什么是響應(yīng)式布局開發(fā)?把我們開發(fā)完成的產(chǎn)品,能夠讓其適配不同的設(shè)備...
    萌妹撒閱讀 1,089評論 0 0
  • ??JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。 ??事件,就是文檔或瀏覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,526評論 1 11