pjax とは
pushstate + Ajax を用いたJQueryプラグイン。
ページの非同期遷移を可能にするのとブラウザに履歴を残せる。
の2つのプラグインがあるが、「falsandtru」のほうが多機能で便利そうなのでこちらで話しを進める。
非同期遷移だと何がいいのか
- ページ遷移の際に表示が早い、ページそのものを再描画するのではなく、大元のHTMLはそのままで必要な部分だけ差し替えるのでレンダリングが早いというのと、img,js,cssファイルのリクエスト数が減るのでその分高速になる。
pjaxだと何がいいのか
- 非同期遷移のページを作るための、オプション、callbackや、eventが沢山用意されていてカスタマイズしやすい。
- サーバーサイドでの処理をしなくてもクライアントサイドだけで完結できる。例えば、普通にSPA(single page application)を作る際に「ttp://hoge.com/index.html」しかないと「ttp://hoge.com/bbb」などに直でアクセスされた場合not foundになる。しかしpjaxは静的なファイルは用意されているので、この点をクリアできる。
- 上記の理由から、SEO的にも問題ない。SNSのシェアなどにも問題なく対応できる。
- 丁寧な日本語サイトがある。これはでかい。こちらに、機能については日本語で詳しく書いてある。http://falsandtru.github.io/jquery-pjax/guide/
- 対応していないものについては、普通のページ遷移をするサイトになるので問題なし。
demo
index.htmlとindex2.htmlを交互にpjaxで切り替える。
切り替えるのは赤枠の箇所のみで、他はそのまま。
pjaxで指定した箇所以外は更新されないので、
その分再描画に時間がかからない。なお、pjaxはインラインスクリプト以外は初回時の読み込みより先は
処理しない。
ソース
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>AAAAAAAAAAA</title>
<meta property="og:site_name" content="AAAAAA site">
<meta property="og:type" content="product">
<meta name="description" content="AAAAAAAAAA | AAAAAAAAAAAAAAAAAAAAAAAAA">
<meta name="keywords" content="aaa, aaa,aaaaaa,aaaaaaaaaa">
<meta property="og:title" content="AAAAAAAAAAA">
<meta property="og:description" content="AAAAAAAAAA | AAAAAAAAAAAAAAAAAAAAAAAAA">
<meta property="og:image" content="http://aaaaaaaaaaaaaaaaaaa/top/ogp.png">
<meta property="og:url" content="http://aaaaaaaaaaaaaaaaaaa">
<link rel="stylesheet" href="style.css">
<script>
if (!window.ga) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-56791268-2', 'auto');
ga('send', 'pageview');
}
ga('send', 'pageview', window.location.pathname.replace(/^\/?/, '/') + window.location.search);
</script>
</head>
<body id="aaa">
<div id="fb-root"></div>
<header id="header">
<h1>header</h1>
</header>
<h1 id="time"></h1>
<div id="commonSns">
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.yahoo.co.jp/" data-text="YAHOO"></a>
<div class="fb-share-button" data-layout="button_count" data-href="http://www.yahoo.co.jp/"></div>
</div>
<hr>
<div class="detailSns" id="detailSns">
<a href="https://twitter.com/share" class="twitter-share-button"></a>
<div class="fb-share-button" data-layout="button_count"></div>
</div>
<div id="contentsWrap">
<div id="contents" data-bodyId="aaa">
<nav id="nav">
<ul>
<li><a href="index.html" class="pjaxLink">index.html</a></li>
<li><a href="index2.html" class="pjaxLink">index2.html</a></li>
</ul>
</nav>
<p>
ああああああああああああああああああああああああああああああああああああああ
あああああああああああああああああああああああああああああああああああああああああああああああああああああああああ
あああああああああああああああああああ
</p>
</div>
</div>
<hr>
<img src="img/apple.jpg" width="200" alt="">
<img src="img/banana.jpg" width="200" alt="">
<img src="img/grape.jpg" width="200" alt="">
<img src="img/melon.jpg" width="200" alt="">
<img src="img/orange.jpg" width="200" alt="">
<footer id="footer">
<h1>footer</h1>
</footer>
<img src="img/loader.gif" height="80" width="80" alt="" id="loader">
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="jquery.pjax.js"></script>
<script src="moment.js"></script>
<script src="script.js"></script>
</body>
</html>
(function(){
// twitterと facebookの init関数みたいなものはインラインにすると何度も読まれるので外部化
// twitter //////////////////////////////////
!function(d,s,id){
var js,
fjs=d.getElementsByTagName(s)[0],
p=/^http:/.test(d.location)?'http':'https';
if(!d.getElementById(id)){
js=d.createElement(s);
js.id=id;
js.src=p+'://platform.twitter.com/widgets.js';
fjs.parentNode.insertBefore(js,fjs);
}
}(document, 'script', 'twitter-wjs');
// twitter end //////////////////////////////////
// facebook //////////////////////////////////
(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v2.4";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
// facebook end //////////////////////////////////
$(function(){
var $time = $("#time");
var $blackLayer = $("#blackLayer");
var $loader = $("#loader");
var $contentsWrap = $("#contentsWrap");
// 現在時刻を検証のために表示
var m = moment();
var output = m.format("YYYY年MM月DD日 HH:mm:ss dddd");
$time.text(output);
// pjaxの設定
$.pjax({
area : '.detailSns, #contents', //再描画させるエリア
link : '.pjaxLink', // pjaxを発動させるlinkにつけるclass
ajax : { timeout: 2500 }, // ajaxの通信のタイムアウト時間
wait : 200, // 押してから発動するまでの時間
cache: { click: false, submit: false, popstate: false }, // キャッシュの設定
load: { head: 'base, meta, link', css: true, script: true }, // 再描画するエリアを選べる、インラインスクリプトを動作させるかどうか
// http://falsandtru.github.io/jquery-pjax/api/callback/
callbacks:{ // callbackを設定出来る
ajax : { //
success: function(event, setting, data, textStatus, jqXHR){
console.log("ajax.success");
}
},
update : {
content: {
after : function( event, setting, srcContent, dstContent ) {
console.log(srcContent + ': update.content.after');
}
}
}
}
});
////// pjaxで登録された様々なイベントを取得できる。 ////////
// http://falsandtru.github.io/jquery-pjax/api/event/
// データ取得前に発生するイベントを設定できます。
$(document).on('pjax:fetch', function(){
$contentsWrap.addClass("add");
$blackLayer.fadeIn(200);
$loader.fadeIn(200);
});
// データの取得後、ページの更新前にwindowオブジェクトから発生します。
$(window).on('pjax:unload', function(){
console.log("pjax:unload")
});
// areaで指定された範囲のDOMの更新後、documentオブジェクトから発生します。
$(document).on('pjax:DOMContentLoaded', function(){
console.log("pjax:DOMContentLoaded")
});
// すべての更新範囲の描画後、documentオブジェクトから発生します。
$(document).on('pjax:render', function(){
console.log("pjax:render")
});
// SCRIPT要素を除くすべてのDOMの更新後、documentオブジェクトから発生します。
$(document).on('pjax:ready', function(){
console.log("pjax:ready")
});
// すべての画像(IMG要素)とフレーム(IFRAME, FRAME要素)の読み込み後、windowオブジェクトから発生します。
$(window).on('pjax:load', function(){
console.log("pjax:load")
});
// すべての更新範囲の描画後、documentオブジェクトから発生します。
$(document).on('pjax:render', function(){
// body のIDを変更
$("body").attr({
'id': $("#contents").data('bodyid')
})
// twボタンを再描画
twttr.widgets.load($("#detailSns")[0]);
// fbボタンを再描画
FB.XFBML.parse($("#detailSns")[0]);
// コンテンツ部分を表示
$blackLayer.fadeOut(300);
$loader.fadeOut(300,function(){
$contentsWrap.removeClass("add");
});
});
});
})();
pjaxを使う上で最低限必要な設定
gaタグを非同期できちんと動作させる
<script>
if (!window.ga) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-56791268-2', 'auto');
ga('send', 'pageview');
}
ga('send', 'pageview', window.location.pathname.replace(/^\/?/, '/') + window.location.search);
</script>
http://falsandtru.github.io/jquery-pjax/guide/
公式サイトに書いてあるが、非同期ページなので解析については注意が必要で、pjaxはインラインスクリプトは再処理されるのだが通常の書き方ではダメで、2回目以降の非同期遷移の場合は上記のようにwindow.ga オブジェクトがあるかないかで分岐させる、これで何度も無駄な処理をせずに済む。ちなみにgaがきちんと計測されているかの検証は[レポート]→[リアルタイム]→[サマリー]から取得できる。ローカルIPでも出来たので、開発時の検証も可能。
SNS(FBとTW)をページごとに切り替える & body要素へのCSSシグネチャに対応する
// すべての更新範囲の描画後、documentオブジェクトから発生します。
$(document).on('pjax:render', function(){
// body のIDを変更
$("body").attr({
'id': $("#contents").data('bodyid')
})
// twボタンを再描画
twttr.widgets.load($("#detailSns")[0]);
// fbボタンを再描画
FB.XFBML.parse($("#detailSns")[0]);
// コンテンツ部分を表示
$blackLayer.fadeOut(300);
$loader.fadeOut(300,function(){
$contentsWrap.removeClass("add");
});
});
pjaxでページ遷移すると、metaタグ周りも新しいページのものに差し替えてくれる。しかし、SNSボタンはこのままでは反映されない。
描画しなおしてから、pjax:renderのタイミングで以下のメソッド読むことで新しいmeta情報にてボタンが生成される。
twttr.widgets.load();
FB.XFBML.parse();
demoページで見てもらうとわかるが、サイト全体のSNSとページ固有のSNSを2つ設置している場合がよくある。この場合、再描画させたいのはページ固有のSNSなので
twttr.widgets.load($("#detailSns")[0]);
FB.XFBML.parse($("#detailSns")[0]);
のように要素を設定すると、その範囲内にあるSNSボタンを再描画してくれる。
次にCSSシグネチャの問題だが、
<body id="products">
のように、pageごとにIDを変えて、CSSを変更していることが多いと思うが、
このままだとbodyのIDが変わらないのでページ遷移の際に、切り替わるコンテンツにidを振っておいて、それをbodyに付加する
ようにしている。
// body のIDを変更
$("body").attr({
'id': $("#contents").data('bodyid');
})
ちょっとまだ、案件では使っていないがこれをテンプレートにして次の案件をやってみようと思う。