これからpjaxを使う人に知っておいてほしいこと

49270 8

連載モノとなっております。
今回より全5回に渡り、pjax(pushState + Ajax)に関する基本から使用時の注意点、トラブルシューティングやtips、果てはちょっとマニアックな内容を書き綴らせていただきます。

この記事を書こうとした2016年の春頃にはReactだ、Vueだ、Web Componentsだ、Riotだ…と、これまでの高コストなDOM操作をする方法ではなく、ユーザーの操作に応じてインタラクションを返す際は、Virtual DOMを使ったりコンポーネントを取り替えるようにしろ、的なものが主流になってきていたのでpjax自体もう枯れた技術になるかな…と考えた為、お蔵入りにしていました。

しかし、ちょっと調べ物をした時に、最近でもまた結構記事が増えていることに気付いて、まだ興味を持っている人も多いのだ、ということと、『pjaxを使ううえで忘れてはならない大事なポイント』にまだ誰も触れていないことに気付き、改めてちゃんと書こうと思い、筆をとった次第です。

これまで約3年半に渡りpjaxを使い続けてきて、色々と蓄積されたノウハウを公開していければと思います。

第一回目の今回では、主に基礎知識となる部分のお話しをさせていただきます。
「今更基礎…?」と思われるかもしれませんが、突っ込んだ内容も書いております。ここをしっかり理解しておかなければ、実装の際いらぬところで詰まったり、無用なエラーを招くことが多いため、どうぞお付き合いください。

1.最初に、僕がpjaxを使ってきた経歴

初めてpjaxを使ったのは2015年1月のThe New York Times様の案件でのことです。

『未来の新聞』をイメージし、コンテンツのカテゴリーに応じて動的にレイアウトが変化していく、というコンセプトで作成しました。
その変化もリアルタイムに見て取れるように。つまり、『(ユーザーの操作に合わせて)レイアウトがリアルタイムで変化していくサイト』があったら面白いな、と考えたのがきっかけです。 しかも、『その状態が一時的ではないもの』で。

JavaScriptやAjaxを使えば、リアルタイムでレイアウトやコンテンツを変化させるのは造作もありません。しかし、それは一時的なものでしかなく、スタートの形状は常に同じ。 ブラウザの戻る/進む/リロードを使えばスタートの形状へ強制的に戻されます。
ハッシュを使えばそれも多少解消されますが、検索には引っかかりません。(検索エンジンから見れば、「ハッシュは一時的なもの」という認識に近い)

検索からの直接流入でも、変化した状態でアクセスし、 変化した状態それぞれヒストリーが残る必要がある = 変化状態それぞれにユニークなURLが存在すれば可能(逆説的ですが…)

つまり、pjax(pushState + Ajax)を応用すればそれも可能、ということです。

それ以来、これまで複数ページある製作の案件ほぼすべてでpjaxを採用してきました。(このブログでも使っていますね)

その経験から、これからpjaxの利用を考えている製作者の方に、覚えておいていただきたいポイントと、使い方に関するtipsを紹介できればと思います。

2.pjaxって結局何がいいの?

極論するとこの2つだと考えます。

  1. 高速なページ遷移
  2. 新鮮でユニークなユーザー体験

pjaxの利点としては、必要な部分のみを取り替えるという方法ゆえにページロードに必要なデータ量を大幅に削減できます。 その結果、ページ遷移が非常に高速になり、従来型のサイトやアプリケーションの製作においては、非常に強力なツールではないかと考えています。

高速なページ遷移

Webサイトなどでは、基本header、footerはどのページでも共通になることがほとんどでしょう。場合によってsidebarがあったりなかったりすることがありませんが、その表示内容は一定であることがほとんどではないでしょうか?

そうなると、各ページ間の差異は画像でいう「#contents」の箇所のみになります。

しかし、通常のhttpリクエストにおいてはページ間を遷移する際、遷移元で読み込まれレンダリングされていたこのDOMをすべてリフレッシュして、あらためて遷移先のDOMをすべて読み込んでレンダリングします。cssやJavaScriptももう一度、一から読み込んで実行する、という処理が行われます。

pjaxでは、読み込み済みの遷移元のDOMへ、遷移先のDOMからpjaxで指定した箇所を読み込んできて置き換えるだけになります。
その為、cssやJavaSciptを改めて読み込んで一から実行しなおす…といった無駄もありません。

ページ表示を遅らせる大きな原因であるhttpリクエスト数を大きく減らせること、scriptタグによるブロッキングが起こらないのも高速な遷移の一因です。(※HTTP2環境ではhttpリクエストの最大同時読み込み可能数が大きく向上している為、リクエスト時のブロッキングは起こりにくくなっていますが)

新鮮でユニークなユーザー体験

モバイルアプリなどでページ遷移(コンテンツ切り替え)がシームレスに行われるものが多く、非常に軽快で小気味のいい操作感のものが多くなっています。

その“今っぽい”操作感と比べて、webページはどうでしょう?
今でも、今までと大差ない動作をしていないでしょうか?

実際のところ、それで特に問題はないでしょう。
しかし、とても作り込まれているサイトなのに、ページを移動する際は「いつも通り」では、むしろ違和感にも感じ、なんだかそこだけが何十年も前のまま何も変わらない方法だな…と、興ざめしてしまいます。その部分だけが、異様に古臭くも感じます。
だからこそ、ページ移動時にも拘ったインタラクションがあったほうがいい

例えばこの動画の最後にあるようなスライダー型のpjax。
一見スライダーのように見えますが、ちゃんとURLは変わっています。これはある意味、理想的な遷移の一つではないかなと考えています。

ユーザーにとっては、ページ移動していると感じにくい。これは閲覧時の心的ストレスを大きく緩和します。
個人的にではありますが、ページ遷移時の「チラつき」がとても嫌いで、あの一瞬のチラつきを見たくないために、ページ遷移を極力しない、くらい。

同時に、一つの繋がりの輪として認識されるということ。

つまりは、複数ページに渡る情報量でありながら、それらを一括りとして感じてもらえる。 これはそれぞれのコンテンツが孤立しやすいデジタルコンテンツにおいて、大きなアドバンテージではないでしょうか?

Webサイトであれば当然、どのページから閲覧するか分からない。
一連の関連情報として認知しやすく、かつ「すぐにアクセスしやすい・できる」というものは使い勝手が良く、分かりやすいものとなるでしょう。

よくあるブログの「関連記事」的なもので十分と考えられるのも、それも間違いではないでしょう。 しかし、「ベスト」ではないはずです。 ならば、よりよい手法を創造していくべきだと考えます。

まして、すべてのWebページがブログ形式ではないのですから。

3.各Pjaxプラグインの特徴

今使われている主なpjaxのプラグインは下記の3つだと思われます。(これら以外もあるのでしょうが使ったことがない為割愛)
それぞれの特徴などを個人的な感覚で紹介します。(各特徴は2018年5月現在の最新Ver.のもの)

jquery-pjax PJAX Barba.js
サイズ 37KB(v2.0.1) 229KB(v3.21.3) 41KB(※minify 16KB)(v1.0)
必要環境 jQuery依存 Nativeのみ 両方可
実装の難易度 易しい 易しい やや高い
実行速度 やや遅い 速い 速い
pjaxエリアの自由度 ×
遷移時の演出
クリック時に処理を挟む ×
プリロード × ×
Use Case
  • jQueryベースでの開発時
  • 複雑なコンテンツ切り替えを行いたいケース
  • Native環境でシンプルなpjaxを利用したい時
  • 高速な遷移を実現したいケース
  • 凝った演出を使用したいケース
  • 複雑なイベント処理を行いたいケース

これらの中ではjquery-pjaxが最も古く、Barba.jsが一番新しいもので、それぞれにこのような特徴があります。

  • jquery-pjaxはjQuery依存だけあって実行速度などが他のものより遅くなる反面、様々なカスタマイズが簡単にでき、自由度も高い(だいたいのことは頑張ればなんとかなります)。
  • PJAXはインターフェースやAPIがかなりシンプルなので、実装そのものは簡単です。ただし、ほとんどを内部的に処理している為、カスタマイズはあまりできません。
  • Barbar.jsはAPIが非常に豊富な為、nativeでの実装でも様々なカスタムが可能です。pjax可能なエリアは固定という点に難があるのと、実装は少し難易度が上がります。

製作するものの要件に応じて、それぞれを選ぶと良いでしょう。今後、主にこの3つを使用しての方法で説明していきます。

4.pjaxを使用する際の注意点

2の箇所で少し触れていましたが、『遷移元のDOMへ』『遷移先のDOMから部分的に』変更する為、遷移元と遷移先のDOM構造に差異があれば、pjaxでの遷移時と通常のリクエスト時で表示内容に違いが出てしまいます

上記のような状態でpjaxを用いるとエラーやバグを生みます。

たしかにpjax後にjsファイルやcssファイルの追加読み込みは可能です。しかし、遷移毎に追加されていくファイルのコンフリクト回避などのチェックに返って時間が生じるでしょう。はじめからすべて読み込ませる方式のほうが安全です。(※腕に自信があればこの限りではありません)

pjaxを使用した製作では、以下のことを注意してください。

  • あらかじめDOMの構造をpjaxで更新したい箇所を中心に考えた構成にしましょう。
    (ex. サイドバーの有無がページ毎にあるならば、pjaxさせるエリア内にサイドバーを配置する など)
  • 必要ファイルは最初からすべて読み込ませる。(1ファイルにまとめるのもおすすめです)

同時に、これらの点を考慮した製作になるということは、工数ばかりが増えることもあり、決まったデータをただひたすら表示するだけの静的なhtmlサイトにはあまりpjaxを採用するメリットはありません。(もちろん使うことには大賛成ですが)
MVCフレームワークなどを使ってViewがテンプレート化されている状態や、phpやRuby、pythonなどを使って様々な処理を行ったうえでhtmlを生成する(もちろんパート毎にテンプレート化されている)ようなサイトでこそ真価を発揮します。

5.ちゃんとイベント管理してますか?

過去teratailで回答者をやっていた頃にも論じていたのですが、いまだに誰もこのことに触れていないのでちょっと書かせていただきます。

最初に覚えておいていただきたいのが、pjaxを採用したサイトでは多くの場合、メモリリークさせてしまいがちです

5,6ページほども遷移すれば何だか動作が重くなる…なんてことはないでしょうか?そういう場合、メモリリークを疑ってみてください。

このTimeline(現Performance->Memory)は適切なイベント削除を行っていないプログラムを使用してpjaxによる遷移を11ページ行ったものです。

見てみるとListeners(黄色)がどんどん増えていっています。(※Nodesも増え続けていますが…)こういうものが積もっていってメモリを圧迫するのがメモリリークです。

皆様はURLの変化を伴わないアプリケーションを製作したことはありますでしょうか?
pjaxサイトの製作方法は、それとほぼ同様です。
スクリプトの記述の仕方によっては、明示的にイベントや変数などを削除するなどする必要があります。

明示的な削除も重要ですが、上記の状態を起こす原因となりやすいものがもう一つあります。

//.selectorは#contents内の要素
$('.selector').on('click', fun);

jQueryを使用していると、よく上記みたいな指定をしていたりしませんでしょうか?

pjaxサイトでは、このイベント指定方法は御法度です。

なぜならこの場合、この.selectorは暗黙的にdocumentオブジェクトに紐付けられます。

documentオブジェクトというのは、上記の画像のように、htmlドキュメントの上のノードにあたります。

当然、#contentsに紐付いていない為、この指定方法を行ったイベントは削除しない限り残り続けます。(しかも、pjax遷移前に削除しないと削除できなくなったりするケースもあります。。)

//.selectorは#contents内の要素
$('#contents .selector').on('click', fun);

のような指定方法をしてやるとこれを(一応)回避することができます。(ベターなのは#contents下にユニークなidを付与した要素があり、その要素のidに紐づけるべき)

このあたりに関しては、後日の「pjaxでのイベント処理」回で詳しく書かせていただきますので、今回はさわりとして簡単に。
今回はこのへんで。

次回は実際のコーディングに関する内容を書いていきます。

pluginpjax

Comments

  • jikka より:

    はじめまして。
    一つ質問させてください。

    barbaを使用しサイトを構築しておりますが
    遷移がうまくいかず困っております。
    事象としては、サイト内の別ページへ遷移した際に
    遷移先のページが読み込まれておらず(ブラウザがクルクル)、
    数秒後、遷移元のページへ戻ってしまいます。

    リンク上へマウスオンした際の読み込み状況を
    chromeのデベロッパーツールで調べたところ
    遷移先のコンテンツ読み込みに、1秒以上かかっているようで、
    読み込み前にリンクをクリックしたことで遷移に失敗しているようです。

    他のサーバで同じサイトデータを動作させたらうまくいきました。
    サーバのスペックが低い場合、このような事象もあり得るものなのでしょうか

    何かご存知でしたら教えて頂けると幸いです。

    • 深井 学 より:

      初めまして、コメントありがとうございます。
      スペックや負荷状況、回線状況によって「起こり得る」です。

      ただ、タイムアウトの場合、遷移元ではなく遷移先へ通常遷移します。
      なのでその動作は何かしらスクリプトエラーがあるのでしょう。
      同じデータで別サーバー上ではうまく動作したとのことですが
      完全に同じでしょうか?(サイト解析用などのスクリプトの記述有無、記述順などまで)
      結構その辺りが原因で意図しないエラーが発生したりするケースもあります。

      • jikka より:

        ご返信ありがとうございます。
        通常、タイムアウトは遷移先に移動するんですね
        別サーバへUPしたものは全く同じはずですが
        何か差異がないか確認してみます。

        ありがとうございました。

        • 深井 学 より:

          とんでもございません。
          Barba(及びpjax系)ではリンクの処理をpreventDefaultでキャンセルし、JavaScriptで後の処理を行なっていくため
          記述内容にエラーがあればリンクが効かないor pjaxしない(通常遷移)
          タイムアウトに関してもBarbaはデフォルトで2000ミリ秒のタイムアウトマージンがあります。(Barba.initのtimeoutプロパティで指定されており、変更可能)

          デベロッパーツールのブレークポイントを使ってリンククリック〜Barbaのイベント処理あたりをチェックすると遷移の直前に何かしらエラーが発生しているのが見つけられるかもしれません。
          ご参考までに。

  • suzuki より:

    数あるpjaxプラグインの特徴の比較表を拝見して,jquery-pjaxが直感的に理解しやすそうかと思い,その勉強をしております。
    不躾な質問で申し訳ないのですが、例えば
    <a href="‘hoge’" rel="nofollow">Text </a>
    $(‘#content’).on(‘click’,’a’,function(){
    console.log( $(this).text() )
    $.pjax(option)
    }
    みたいな記述があったとして,
    $(document).on(pax:end,func)に前述の$(this).text()の値をそのまま使いたいのですが,
    そのようなことは不可能でしょうか。
    何か実現できる方法があれば教えていただきたいです。

    • 深井 学 より:

      コメントありがとうございます。
      結論から言うと可能です。
      jquery-pjaxではoptionの"url"プロパティを任意に設定できる為、極論すればどんな値でも設定できてしまいます。(※当然URL以外の値を入れるとpjaxに失敗しますが)

      第2回や第3回はまだご覧になってらっしゃらない感じでしょうか?
      第2回の「遷移時の演出」の項目をご覧になれば分かりやすいと思いますが、urlプロパティの値をsuzukiさまの例で言えば$(this).text()の値を代入してやれば実現できるでしょう。

  • pako より:

    はじめてコメントさせていただきます。
    pjaxの全くの初心者です。とても詳細で為になるリポートに感謝とともに読ませていただいています。
    windows10,firefox Version 65.0にて、リンクをクリックすると次のページではコンテンツが全くない状態のページが多数あるようです。リロードすると現れます。是非何が原因なのかまたリポートいただけるとありがたいです。

    • 深井 学 より:

      pakoさま
      コメントありがとうございます。恐縮です!個人的にpjaxが好きで使っていただけにそう言っていただけると大変嬉しく思います。
      コンテンツが全くない状態のページというのはこのブログで、ということでしょうか…?
      同じ環境を用意して確認してみましたが再現できなかったため、pakoさまが入れてらっしゃるアドオンなどが影響しているか設定により何かしらをブロックしているのかもしれませんね…。
      よろしければどういった時に(どのリンク等)その現象が起こるのでしょう?

Add a Comment

メールアドレスが公開されることはありません。

認証コード * Time limit is exhausted. Please reload CAPTCHA.

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください