前言
上週看到一篇文章在分析簡書 個人主頁 頁面 3 個 tab 頁切換的 bug,起先覺得是尋常的樣式 bug 而已沒怎麼在乎,後來在文章中看到 pjax 這個術語,長得和 ajax 有點像,遂去了解了下。html
簡介
雖然傳統的 ajax 方式能夠異步無刷新改變頁面內容,但沒法改變頁面 URL,所以有種方案是在內容發生改變後經過改變 URL 的 hash 的方式得到更好的可訪問性(如 https://liyu365.github.io/BG-UI/tpl/#page/desktop.html),可是 hash 的方式有時候不能很好的處理瀏覽器的前進、後退,並且常規代碼要切換到這種方式還要作很多額外的處理。而 pjax 的出現就是爲了解決這些問題,簡單的說就是對 ajax 的增強。html5
pjax 結合 pushState 和 ajax 技術, 不須要從新加載整個頁面就能從服務器加載 Html 到你當前頁面,這個 ajax 請求會有永久連接、title 並支持瀏覽器的回退/前進按鈕。jquery
pjax 項目地址在 https://github.com/defunkt/jquery-pjax 。 實際的效果見: http://pjax.herokuapp.com 沒有勾選 pjax 的時候點擊連接是跳轉的, 勾選了以後連接都是變成了 ajax 刷新(實際效果以下圖的請求內容對比)。
不使用pjax
使用pjax
優勢:
減輕服務端壓力
按需請求,每次只需加載頁面的部份內容,而不用重複加載一些公共的資源文件和不變的頁面結構,大大減少了數據請求量,以減輕對服務器的帶寬和性能壓力,還大大提高了頁面的加載速度。git
優化頁面跳轉體驗
常規頁面跳轉須要從新加載畫面上的內容,會有明顯的閃爍,並且每每和跳轉前的頁面沒有連貫性,用戶體驗不是很好。若是再趕上頁面比較龐大、網速又不是很好的狀況,用戶體驗就更加雪上加霜了。使用pjax後,因爲只刷新部分頁面,切換效果更加流暢,並且能夠定製過分動畫,在等待頁面加載的時候體驗就比較舒服了。github
缺點:
不支持一些低版本的瀏覽器(如IE系列)
pjax使用了pushState來改變地址欄的url,這是html5中history的新特性,在某些舊版瀏覽器中可能不支持。不過pjax會進行判斷,功能不適用的時候會執行默認的頁面跳轉操做。web
使服務端處理變得複雜
要作到普通請求返回完整頁面,而pjax請求只返回部分頁面,服務端就須要作一些特殊處理,固然這對於設計良好的後端框架來講,添加一些統一處理仍是比較容易的,天然也沒太大問題。另外,即便後臺不作處理,設置pjax的fragment參數來達到一樣的效果。ajax
綜合來看,pajx 的優勢很強勢,缺點也幾乎能夠忽略,仍是很是值得推薦的,尤爲是相似博客這種大部分狀況下只有主體內容變化的網站。關鍵它使用簡單、學習成本小,即時全站只有極個別頁面能用獲得,嘗試下沒什麼損失。pjax 的 github 主頁介紹的已經很詳細了,想了解更多能夠看下源碼。spring
用法
引入 jquery 和 jquery.pjax.js
註冊事件
/**後端
// 方式二 直接對按鈕監聽,能夠不用指定容器,使用按鈕的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');
options默認參數說明
參數名 默認值 說明
timeout 650 ajax 超時時間(單位 ms ),超時後會執行默認的頁面跳轉,因此超時時間不該太短,不過通常不須要設置
push true 使用 window.history.pushState 改變地址欄 url( 會添加新的歷史記錄 )
replace false 使用 window.history.replaceState 改變地址欄 url( 不會添加歷史記錄 )
maxCacheLength 20 緩存的歷史頁面個數( pjax 加載新頁面前會把原頁面的內容緩存起來,緩存加載後其中的腳本會再次執行 )
version 是一個函數,返回當前頁面的pjax-version,即頁面中
標籤內容。使用 response.setHeader("X-PJAX-Version", "") 設置與當前頁面不一樣的版本號,可強制頁面跳轉而不是局部刷新。
scrollTo 0 頁面加載後垂直滾動距離( 與原頁面保持一致可以使過分效果更平滑 )
type "GET" ajax 的參數,http 請求方式
dataType "html" ajax 的參數,響應內容的 Content-Type
container 用於查找容器的 CSS 選擇器,[container] 參數沒有指定時使用
url link.href 要跳轉的鏈接,默認 a 標籤的 href 屬性
target link pjax 事件參數 e 的 relatedTarget 屬性,默認爲點擊的 a 標籤
fragment 使用響應內容的指定部分( CSS 選擇器 )填充頁面,服務端不進行處理致使全頁面請求的時候須要使用該參數,簡單的說就是對請求到的頁面作截取
除了上述參數外,ajax 的一些參數也是能夠設置在這裏的,不過通常沒什麼必要。
// ajax 最終參數:
options = $.extend(true, {}, $.ajaxSettings, pjax.defaults, options);
pjax失效狀況
會有一些狀況致使 pjax 失效,下面結合源碼分析下(省略部分無關代碼)
function handleClick(event, container, options) {
...
// 1. 點擊事件的事件源不是a標籤。使用a標籤能夠作到對舊版本瀏覽器的兼容,因此不建議使用其餘標籤註冊事件 if (link.tagName.toUpperCase() !== 'A') throw "$.fn.pjax or $.pjax.click requires an anchor element" // 2. 使用鼠標滾輪點擊(新標籤頁打開) // 點擊超連接的同時按下Shift、Ctrl、Alt和Meta(在Windows鍵盤中是Windows鍵,在蘋果機中是Cmd鍵) // 做用分別表明新窗口打開、新標籤打開(不切換標籤)、下載、新標籤打開(切換標籤) if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return // 3. 跨域(網絡通信協議,域名不一致) if (location.protocol !== link.protocol || location.hostname !== link.hostname) return // 4. 當前頁面的錨點定位 if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location)) return // 5. 已經阻止元素髮生默認的行爲(url跳轉) if (event.isDefaultPrevented()) return ... var clickEvent = $.Event('pjax:click') $(link).trigger(clickEvent, [opts]) // 6. pjax:click事件回調中已經阻止元素髮生默認的行爲(url跳轉) if (!clickEvent.isDefaultPrevented()) { pjax(opts) event.preventDefault()// 阻止url跳轉 $(link).trigger('pjax:clicked', [opts]) }
}
除了上述狀況以外,還有下列幾種狀況:
ajax 請求失敗,或者 timeout 後請求被停止
當前頁面的 X-PJAX-Version 和請求的新頁面版本不一致
請求獲得完整的頁面(包含 html 標籤)卻沒設置 fragment 參數
事件
常規模版內容:
<!doctype html>
@Value("${X-PJAX-Version}") private String X_PJAX_VERSION; /** * Controller 方法調用以後,頁面渲染前執行 * * @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);// 設置當前頁面的pjax版本 if (isPajx) { model.addAttribute("layout", "layout_pjax.vm");// 指定pjax請求時使用的模版 // 在vm頁面中經過 #set($layout = 'xxx.vm') 的方式指定模版 response.setHeader("X-PJAX-Version", X_PJAX_VERSION);// 響應內容的pjax版本,有新模版發佈時,經過配置文件修改版原本強制頁面刷新 } } }
}
xml 配置
mvc:interceptors
mvc:interceptor
<mvc:mapping path="/**"/>
</mvc:interceptor>
</mvc:interceptors>
pjax 請求模版頁面:layout_pjax.vm
針對沒有服務端處理的方案以下:
// 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);
結語
雖然我的仍是比較喜歡造輪子(有成就感),不怎麼喜歡用插件(通常插件使用複雜,文檔少學習成本大,還不如本身寫),但看了 pjax 的源碼後感受真要本身也使用 pushState + ajax 的方式簡單的實現它的功能,仍是要踩很多坑的,因此爲何要放着這麼個易用又精緻的小輪子不用呢?個人項目是一個管理系統,統一的 左側菜單 + 右側table 的佈局,每一個頁面都須要一個獨立訪問的 url,很是適合使用 pjax。因爲使用的 velocity 模版技術,集成 pjax 就是分分鐘的事,不只對原先的代碼徹底沒影響,還提高了加載速度,頁面過分效果更好,再用上了 NProgress,感受逼格又上升很多,哈哈。
前段時間工做比較忙很久沒寫文章了,這段時間有點閒下來就抽空學了些新東西記錄下,對於此次的學習成果仍是比較滿意的。( _ )
轉載請註明出處:https://www.jianshu.com/p/557cad38e7dd
做者:anyesu
連接:https://www.jianshu.com/p/557cad38e7dd 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。