Ajax(pjax)遷移時におけるSNSボタン再構築

Updated / Published

HTML5のhistoryAPI(pushState)とAjaxを組み合わせた遷移(pjax)時に、Twitterのwidgets.jsで埋め込んだツイートボタンが更新されない事態に遭遇しました。pushStateでURLは変更できているにも関わらず、ツイートボタンを押しても取得されるタイトルとURLは最初にツイートボタンを表示したときの遷移前の状態であり、ごっそり入れ替えてもDOMが再構築されていない様子。

TwitterのAPIドキュメントを探すも、Ajax遷移時にDOMを再構築させるような使い方はどこにも載っていない。そこで、検索語を「Tweet button Ajax」に変えてみると、Cannot update tweet button with AJaxという記事がヒット、回答欄に解決策を提示してくれていました。

So, generate <a class="twitter-share-button" href="http://twitter.com/share" data-url="http://example.com/post/12345">Tweet</a>, insert it into the DOM, and then call twttr.widgets.load(), and everything should spring into life.

どうやらa要素のボタンのコードを挿入してから、twttr.widgets.load()を実行すれば、DOMを再構築してくれる模様。

以下、参考までに実際に書き換えたコードを記載しておきます。基本、何か挿入する必要があるものを<body>〜</body>の最後尾にしていますが、これはAjaxでの切替対象外の部分であればどこでも構いません。なお、本解説のコードでは一部にjQueryを用いているので、書き換えが必要な際は適宜置き換えてください。

あわせて他のSNSボタンやDisqusのコメント欄についても記載しておきます。

Ajax(pjax)遷移時におけるツイートボタン再構築

まず、ジェネレータが生成する標準のコード。

<a href="https://twitter.com/share" class="twitter-share-button" data-lang="ja">ツイート</a>
<script>!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');</script>

せっかくAjax遷移にするわけなので、widgets.jsは最初の読み込み時にのみ挿入して、あとはDOM再構築時にだけ呼び出すようにする必要があります。pjax遷移後に次のようなコードを評価させることで実現できます。

if(!window.twttr){
   $('body').append('<a href="https://twitter.com/share" class="twitter-share-button" data-lang="ja">ツイート</a>');
    var twitterjs = document.createElement("script");
    twitterjs.async = true;
    twitterjs.src = '//platform.twitter.com/widgets.js';
    document.getElementsByTagName('body')[0].appendChild(twitterjs);
}else{
    $('.twitter-share-button').replaceWith('<a href="https://twitter.com/share" class="twitter-share-button" data-lang="ja" data-url="' + encodeURI(location.href) + '" data-text="' + document.title + '">ツイート</a>');
    twttr.widgets.load();
}
  1. window.twttrのオブジェクトがあるかないかをキーとします。
  2. window.twttrが存在しなければ、<body>〜</body>の最後尾にa要素のボタンのコードを挿入してから、widgets.jsを生成します。これで最初の読み込み時のツイートボタンが展開されます。
  3. 次にページ遷移した時には、window.twttrオブジェクトが存在しているので、以降は下段のコードが評価されることになります。さきほどのa要素のボタンのコードはすでにツイートボタンとして<iframe class="twitter-share-button" ...>〜</iframe>に展開されいるので、これを置換して、もとのa要素のボタンのコードに戻します。
  4. 気をつけたいのが、必ずdata-urldata-textに新しいページのURLとタイトルを付け足すことです。これがないとURLもタイトルもうまく書き換えてくれません。
  5. 新しいページ用のdata-urldata-textを付け足したa要素のボタンのコードに置き換えできたら、最後にtwttr.widgets.load();を実行します。

Ajax(pjax)遷移時におけるfacebook いいね!ボタン再構築

これは公式サイトで検索すれば簡単に情報が見つかりました(FB.XFBML.parse)。FB.XFBML.parse();を実行するようです。まず、ジェネレータが生成する標準のコードについて。

<div id="fb-root"></div>
<script>(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/all.js#xfbml=1&appId=ここにID";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<div class="fb-like" data-href=" ここにURL" data-width="90" data-height="20" data-colorscheme="light" data-layout="button_count" data-action="like" data-show-faces="false" data-send="false"></div>

このall.jsは最初の読み込み時にのみ挿入して、あとはDOM再構築時にだけ呼び出すようにします。流れはTwitterのwidgets.js同様、pjax遷移後に次のようなコードを評価させることで実現できます。

if(!window.FB){
    $('body').append('<div id="fb-root"></div><div class="fb-like" data-href="' + encodeURI(location.href) + '" data-width="90" data-height="20" data-colorscheme="light" data-layout="button_count" data-action="like" data-show-faces="false" data-send="false"></div>');
    var fbjs = document.createElement("script");
    fbjs.id = "facebook-jssdk";
    fbjs.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1&appId=ここにID";
    document.getElementsByTagName('body')[0].appendChild(fbjs);
}else{
    $('.fb-like').attr('data-href', encodeURI(location.href));
    FB.XFBML.parse();
}
  1. window.FBのオブジェクトがあるかないかをキーとします。
  2. window.FBが存在しなければ、<body>〜</body>の最後尾に<div id="fb-root"></div><div class="fb-like" ...></div>をあわせて挿入してから、all.jsを生成します。これで最初の読み込み時のいいね!ボタンが展開されます。
  3. 次にページ遷移した時には、window.FBオブジェクトが存在しているので、以降は下段のコードが評価されることになります。さきほどの<div class="fb-like" ...></div>data-hrefを新しいページのURLに書き換え、最後にFB.XFBML.parse();を実行します。

Ajax(pjax)遷移時におけるはてなブックマークボタン再構築

これは公式のドキュメントもなければ、検索しても何ひとつヒットしませんでした。とりあえず、bookmark_button.jsの中身を見て、Hatena.Bookmark.BookmarkButton.setup();で初期化してやるのが手っ取り早いと判断しました。まず、ジェネレータが生成するはてなブックボタンの標準のコードです。

<a href="http://b.hatena.ne.jp/entry/" class="hatena-bookmark-button" data-hatena-bookmark-layout="standard-balloon" data-hatena-bookmark-lang="ja" title="このエントリーをはてなブックマークに追加"><img src="http://b.st-hatena.com/images/entry-button/button-only@2x.png" alt="このエントリーをはてなブックマークに追加" width="20" height="20" style="border: none;" /></a><script type="text/javascript" src="//b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script>

bookmark_button.jsを最初の読み込み時にのみ挿入し、ページ遷移後に都度Hatena.Bookmark.BookmarkButton.setup();で初期化します。流れはTwitterのwidgets.js同様、pjax遷移後に次のようなコードを評価させることで実現できます。

if(!window.Hatena){
    $('body').append('<a href="//b.hatena.ne.jp/entry/' + encodeURI(location.href) + '" class="hatena-bookmark-button" data-hatena-bookmark-title="' + document.title + '" data-hatena-bookmark-layout="standard-noballoon" data-hatena-bookmark-lang="ja" title="このエントリーをはてなブックマークに追加"><img src="//b.hatena.ne.jp/images/entry-button/button-only@2x.gif" alt="このエントリーをはてなブックマークに追加" width="20" height="20" /></a>');
    var hatebujs = document.createElement("script");
    hatebujs.async = true;
    hatebujs.src = '//b.hatena.ne.jp/js/bookmark_button.js';
    document.getElementsByTagName('body')[0].appendChild(hatebujs);
}else{
    $('.hatena-bookmark-button-frame').replaceWith('<a href="//b.hatena.ne.jp/entry/' + encodeURI(location.href) + '" class="hatena-bookmark-button" data-hatena-bookmark-title="' + document.title + '" data-hatena-bookmark-layout="standard-noballoon" data-hatena-bookmark-lang="ja" title="このエントリーをはてなブックマークに追加"><img src="//b.hatena.ne.jp/images/entry-button/button-only@2x.gif" alt="このエントリーをはてなブックマークに追加" width="20" height="20" /></a>');
    Hatena.Bookmark.BookmarkButton.setup();
}
  1. window.Hatenaのオブジェクトがあるかないかをキーとします。
  2. window.Hatenaが存在しなければ、<body>〜</body>の最後尾にa要素のボタンのコードを挿入してから、bookmark_button.jsを生成します。これで最初の読み込み時のはてなブックマークボタンが展開されます。
  3. 次にページ遷移した時には、window.Hatenaオブジェクトが存在しているので、以降は下段のコードが評価されることになります。さきほどのa要素のボタンのコードはすでにはてなブックマークボタンとして<iframe class="hatena-bookmark-button-frame" ...>〜</iframe>に展開されいるので、これを置換して、もとのa要素のボタンのコードに戻します。
  4. hrefdata-hatena-bookmark-titleを新しいページのURLとタイトルにした状態のa要素のボタンのコードに置き換えできたら、最後にHatena.Bookmark.BookmarkButton.setup();を実行します。

Ajax(pjax)遷移時におけるDisqusコメント欄の再構築

これは公式のドキュメントがあります。Using Disqus on AJAX sitesを参照してください。pjax遷移後に以下のコードを実行するだけです。this.page.identifierに一意のIDを、this.page.urlにURLを入れる必要があるのですが、URLも一意なので両方同じでも問題ありませんでした。

DISQUS.reset({
    reload: true,
    config: function () {
        this.page.identifier = encodeURI(location.href);
        this.page.url = encodeURI(location.href);
    }
});

ここで紹介した以外のSNSボタンについても、やり方や手順はほとんど同じだと思われます。Disqusはさすがですね。最初からこういう使われ方を想定して設計されている点、非常に感心させられます。Twitterもfacebookも手立ては用意されているけど、イマイチ。はてなブックマークは。。。

なお、Ajax(pjax)遷移で読み込むことで、これらSNSボタンの遷移時の展開は格段に高速化できています。