jQueryのAjaxからカウント数付のソーシャルボタンを出力するPHPを非同期で実行する

目次

ソーシャルボタンを自作して表示スピードを高速化

Twitter、Facebook、はてな、Pocketなど公式のボタンを使用するとかなりページを表示するスピードが遅くなるため、先日からページ表示を高速化するためにソーシャルボタンの自作にチャレンジしています。

Wordpressであればプラグインで簡単に高速化できのですが、Wordpressではないページで使用するため、どうしても自作するしか高速化する方法がありません。そこで最近行っている自作チャレンジしているメモのブログが以下なんですが、いろいろな方法を試して徐々にページスピードを上げることができました。

上から、Pocketのストックカウントを取得するだけで約1.4~2秒かかり、公式ボタンより遅くなる。

PHPの関数「cURL」を使用してTwitter、Facebook、はてな、Pocketのカウント数をAPIから同期で取得してボタンを自作して約2秒で表示できた。

PHPのcURLを非同期で実行できるcurl_multiを使用すると同じことを約1.5秒で表示できるようになりました。

でも同期でPHPを読み込むから1.5秒遅くなるということ

自作のSNSボタンのみの表示を約1.5秒まで縮めることができたのですが、このSNSボタンを表示させたいページに埋め込むと、そのページの表示は1.5秒遅くなるということ。curl_multiでAPIの取得は非同期でやっているけど、そのPHPはページから同期で呼び出すためです。これを非同期で呼び出すことができればページ全体の表示を速くできるのではないかと思います。

そこでPHPをJavascriptから呼び出す!

jQueryのAjaxでPHPを非同期で呼び出すことができるそうです。これを使えば、PHP内で各SNSのAPIからカウント数を非同期で取得し、そのPHPをJavascriptから非同期で実行させることがきてダブル非同期

サーバーサイドのPHPとクライアントサイドのJavascriptで負荷を分散してるので、これで更に速くなるに違いない!と想像しています。

そして作ったソース

PHP

PHPではjavascriptのページから$_GETでカウントを取得したいURLを受取り、そのURLを対象に各SNSのAPIからカウント数を取得します。そしてカウント数を組み込んだ自作シャーシャルボタンのHTMLを変数に入れます。ファイル名は仮にget_sns_button.phpとします。

<?php
function num_format($count) {
  //3桁区切りのカンマを削除
  $count = str_replace(',','',$count);
  //数値に変換
  $count = intval($count);
  // カウント数が取得できないときは0にする
  return isset($count) ? intval($count) : 0 ;
}

function fetch_sns_count($targeturl) {
   // 並列通信用マルチハンドルを用意
   $mh = curl_multi_init();

// APIごとにCurl Handleを作る
// Twitter
   $ch_twitter = curl_init("http://jsoon.digitiminimi.com/twitter/count.json?url=$targeturl");
   curl_setopt($ch_twitter, CURLOPT_RETURNTRANSFER, TRUE);
   curl_multi_add_handle($mh, $ch_twitter);
  
// Facebook
   $ch_facebook = curl_init("https://graph.facebook.com/?id=$targeturl");
   curl_setopt($ch_facebook, CURLOPT_RETURNTRANSFER, TRUE);
   curl_multi_add_handle($mh, $ch_facebook);

// Hatena
   $ch_hatena = curl_init("https://b.hatena.ne.jp/entry.count?url=$targeturl");
   curl_setopt($ch_hatena, CURLOPT_RETURNTRANSFER, TRUE);
   curl_multi_add_handle($mh, $ch_hatena);


// Pocket
   $ch_pocket = curl_init("https://widgets.getpocket.com/v1/button?v=1&count=horizontal&url=$targeturl&src=$targeturl");
   curl_setopt($ch_pocket, CURLOPT_RETURNTRANSFER, TRUE);
   curl_multi_add_handle($mh, $ch_pocket);

// 複数の通信を非同期で同時実行
   do { curl_multi_exec($mh, $running); } while ( $running );

// APIから戻ってきた値を変数に入れる
   $json_twitter  = curl_multi_getcontent($ch_twitter);
   $json_facebook = curl_multi_getcontent($ch_facebook);
   $count_hatena = curl_multi_getcontent($ch_hatena);
   $html_pocket = curl_multi_getcontent($ch_pocket);

// cURLハンドルセットを閉じる
   curl_multi_remove_handle($mh, $ch_twitter);
   curl_close($ch_twitter);
   curl_multi_remove_handle($mh, $ch_facebook);
   curl_close($ch_facebook);
   curl_multi_remove_handle($mh, $ch_hatena);
   curl_close($ch_hatena);
   curl_multi_remove_handle($mh, $ch_pocket);
   curl_close($ch_pocket);
   curl_multi_close($mh);

/*デバッグ用
   var_dump($json_twitter);
   var_dump($json_facebook);
   var_dump($count_hatena);
   var_dump($html_pocket);
   */

//Twitterのカウント数を取り出す
   $count_twtter = json_decode($json_twitter, true );
   $count_twtter = num_format(strval($count_twtter['count']));

//facebookのカウント数を取り出す
   $count_facebook = json_decode($json_facebook, true );
   $count_facebook = num_format($count_facebook['share']['share_count']);

//Hatenaのカウント数を取り出す
   $count_hatena = num_format($count_hatena);

//Pocketのカウント数を取り出す
   //HTML→DOM→XML→JSON→配列に変換
   $dom_pocket = new DOMDocument('1.0', 'UTF-8');
   $dom_pocket->preserveWhiteSpace = false;
   $dom_pocket->loadHTML($html_pocket);
   $xmlString_pocket = $dom_pocket->saveXML();
   $xmlObject_pocket = simplexml_load_string($xmlString_pocket);
   $count_pocket = json_decode(json_encode($xmlObject_pocket), true);
   $count_pocket = num_format(strval($count_pocket['body']['div']['a']['span']['em']));

//カウント付ソーシャルボタンのHTML
$res = '<ul class="sns-counter" title="'.$targeturl.'"><!--';
   $res .= '--><li class="btn-tw">';
   $res .= '<a href="//twitter.com/intent/tweet?url='.$targeturl.'" target="_blank">';
   $res .= '<i class="fab fa-twitter"></i><span>'.$count_twtter.'</span>';
   $res .= '</a>';
   $res .= '</li><!--';
   $res .= '--><li class="btn-fb">';
   $res .= '<a href="//www.facebook.com/sharer/sharer.php?u='.$targeturl.'" onclick="window.open(\'https://www.facebook.com/sharer/sharer.php?u='.$targeturl.'\', \'new\', \'width=500,height=300\');return false;" target="_blank">';
   $res .= '<i class="fab fa-facebook-f"></i><span>'.$count_facebook.'</span>';
   $res .= '</a>';
   $res .= '</li><!--';
   $res .= '--><li class="btn-hb">';
   $res .= '<a href="//b.hatena.ne.jp/entry/'.$targeturl.'" target="_blank">';
   $res .= '<i class="fa fa-hatena"></i><span>'.$count_hatena.'</span>';
   $res .= '</a>';
   $res .= '</li><!--';
   $res .= '--><li class="btn-pk">';
   $res .= '<a href="//getpocket.com/edit?url='.$targeturl.'" target="_blank">';
   $res .= '<i class="fab fa-get-pocket"></i><span>'.$count_pocket.'</span>';
   $res .= '</a>';
   $res .= '</li><!--';
   $res .= '--><li class="btn-line">';
   $res .= '<a href="//social-plugins.line.me/lineit/share?url='.$targeturl.'" target="_blank">';
   $res .= '<i class="fab fa-line"></i><span>LINE</span>';
   $res .= '</a>';
   $res .= '</li>';
   $res .= '</ul>';
   return $res;
}

$targeturl = $_GET['targeturl'];
echo fetch_sns_count($targeturl);
?>

Javascript

jQueryのAjaxからPHP(get_sns_button.php)を呼び出し、PHPの変数に入ったソーシャルボタンのHTMLをページ内に展開するページのソースです。

<!DOCTYPE html>
<html lang="ja" itemscope itemtype="http://schema.org/WebPage">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="https://ajax.cloudflare.com">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
<title>無題ドキュメント</title>
<style>
.sns-counter {
 width: 100%;
 }
.sns-counter li {
 display: inline-block;
 border-radius: 2px;
 width: 80px;
 padding: 3px;
 margin-left: 3px;
 }
.sns-counter li span {
 display: inline-block;
 text-align: center;
 width: 70%;
 font-size: 12px;
 font-weight: bold;
 color: #fff;
 }
.sns-counter li i {
 display: inline-block;
   text-align: center;
   width: 30%;
   color: #fff;
   font-size: 14px;
   position: relative;
   top: 1px;
   }
   .sns-counter .btn-tw {
   background-color: #55ACEE;
   }
   .sns-counter .btn-fb {
   background-color: #3A5795;
   }
   .sns-counter .btn-hb {
   background-color: #008CE1;
   }
   .sns-counter .btn-pk {
   background-color: #ef4357;
   }
   .sns-counter .btn-line {
   background-color: #1dcd00;
   }
   .sns-counter .btn-total {
   background-color: #000;
   }
   .btn-hb:hover,.btn-tw:hover,.btn-fb:hover,.btn-pk:hover,.btn-line:hover,.btn-total:hover {
   background-color: #ccc;
   }
   i.fa-hatena:before {
   content: "B!";
   font-family: Verdana;
   font-weight: bold;
   }
   </style>
</head>
<body>


<!--↓↓ここにソーシャルボタンが表示される↓↓--> <div id="sns-counter"></div> <script> window.addEventListener("load",function(){ const css=[ "//cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css" ];for(i in css){ let html=document.createElement("link");html.rel="stylesheet";html.href=css[i];async=true;document.head.appendChild(html);}});</script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script> <!--↓↓ここが非同期でPHPを呼び出す部分↓↓--> <script type="text/javascript"> //ポケットのストック数を取得 function fetch_sns_count(url, selector) { var defer = jQuery.Deferred(); jQuery.ajax({ url: 'get_sns_button.php?targeturl=<?php echo urlencode("https://".$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]); ?>', dataType:'text', timeout: 10000, //10sec }).done(function(res){ jQuery( selector ).html( res || 0 ); }).fail(function(){ jQuery( selector ).html('error;'); }); } fetch_sns_count('<?php echo urlencode("https://".$_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]);?>', '#sns-counter'); </script> <!--↑↑ここが非同期でPHPを呼び出す部分↑↑--> </body> </html>

ん?遅くなった?

なぜか遅くなりました。まだ組み込みたいWEBページに埋め込んでいない状態(ショーシャルボタンしかないページ)ですが平均2.2秒。GTmetrixのWaterfallを見ると、確かに非同期で動作は確認できました。

GTmetrix

ちょっと実際のWEBページに組み込んでいろいろなパターンをベンチマークしてみないとわからなくなってきました。

そこで仮のページに埋め込んでベンチマーク

以下の3パターンであるページにソーシャルボタンを仮のダミーページ内に読み込んだ時のスピードをGTmetrixでベンチマークしてみました。

  今回のAjaxから非同期でPHPを呼び出した場合 共有ボタン配信サービスのAddThisを使った場合 普通に同期でPHPを実行させた場合
1回目 3.9秒 4.9秒 5.7秒
2回目 3.6秒 3.7秒 4.3秒
3回目 3.5秒 4.3秒 4.5秒
4回目 3.3秒 6.1秒 4.2秒
5回目 4.0秒 4.4秒 4.5秒
平均 3.66秒 4.68秒 4.64秒

やっぱり速かった!

AjaxからPHPを呼び出す場合、非同期なので他の読み込みをしている間に並行してPHPの実行結果を読み込んでいるので全体的にページの表示スピードが速くなりました。しかも何度ベンチマークしても誤差の範囲が狭く表示時間が安定しています。ばらつきがあるのは多分Adsenseやその他のJavascriptやCDNの読込が原因っぽいです。

AddThisも速いときは速いのですが、遅いときはめっちゃ遅いです。管理が楽なので各SNSのAPIが変更になっても修正に困ることがなさそうなので、メンテナンスのことを考えると多少遅い時があってもアリかなと思いました。GTmetrixのWaterfallのグラフをみるといろんなバックグラウンドでJavascriptが降ってきているので、そこが不安定な原因かもしれません。

普通に同期でPHPを動かした場合は当然PHPの動作分遅いですね。しかも安定してばらつき無く遅いです。

ということで、予想通りAjaxから非同期でPHPを読み込む方が速いという結論になりました。

もっと速くしようと思ったら、CRONジョブで定期的にカウント数を取得しデータベース化して、キャッシュしておくのがいいかもしれませんが、ちょっと大変そうです。そういうプラグインがWordpressにはあるみたいですが、それはまたいつか試してみようと思います。

参考ページ

前へ

PHPのcurl_multiを使って非同期でカウント数を取得するSNSのシェアボタンを自作してみた

次へ

jQuery.jsの読み込みにDeferやAsyncを使ったらエラーでスクリプトが動かないとき