【はてなブログ高速化】自作ソーシャルボタン・シェアボタンで軽量化、ブログを高速に読み込む(完成)

【はてなブログ高速化】自作ソーシャルボタン・シェアボタンで軽量化、ブログを高速に読み込む(完成)

(2016/9/6)プロフィールページ(/about)のエラー修正可読版参照
(2016/12/4)記事の上下に表示可能な改訂版があります

はてなブログの読み込みが重い理由のひとつは、ソーシャルボタンがiframeで読み込まれているからです。以下5回の記事で、hatena, facebook, twitter, google+ をダイレクトリンクに変更し、シェアカウントを素の Javascript 非同期で読み込む方法を試してきましたが、何とか完成しましたので公開します。

  1. シェアボタン(ソーシャルボタン)を改良して読み込みを速くする
  2. Facebookシェアボタン/シェアカウントを素の Javascript で取得する
  3. Facebookシェアダイアログでシェアする/Facebook SDK for JavaScript
  4. ツイート数付きツイートボタンをオリジナルでつくる(素の javascript 付き)
  5. Google+のシェアボタンをカウント付きで自作する

ボタンの見た目

ボタンの見た目は、 CSSで自由に変更できます。デフォルトは次の通りです。

スマートフォンの場合

現在のところ、レスポンシブデザインのテーマを使用していない場合はスマートフォンでは表示されません。はてなブログのスマートフォン向けテーマではトップ画面は記事一覧でソーシャルボタンは表示されませんすので、記事表示の場合に自作のものを表示するよう、記事の記事下やフッタに入れて、CSS をどうするかを考えればいけるとは思います。

レスポンシブデザインのテーマを使用している場合は次のように表示されます。

挿入コード(Javascript 圧縮版)

必須項目

Facebook と Twitter のシェアカウントを取得するために次の2つの登録が必要です。

  1. Facebook アプリID
    【はてなブログ高速化3】Facebookシェアダイアログでシェアする/Facebook SDK for JavaScript – IMUZA.com」を参考にアプリID を取得し、以下のコードの26行目「Facebook のアプリID」を自分のアプリID に変更してください。
  2. count.jsoon への登録
    widgetoon.js & count.jsoon | digitiminimi」でサイトを登録してください。

上記完了後、下のコードをカスタマイズ > フッタに入れてください。

<ul id="tmplShareButtons">
  <li class="hatena">
    <a class="hatena-bookmark-button" data-hatena-bookmark-layout="simple">
      <span class="hatena-bookmark-count share-count"></span>
      <i class="blogicon-bookmark lg"></i><span class="share-label">ブックマーク</span></a>
  </li>
  <li class="facebook">
    <a href="javascript:void(0)" class="facebook-share-button"> 
      <span class="facebook-count share-count"></span>
      <i class="blogicon-facebook lg"></i><span class="share-label">シェア</span></a>
  </li>
  <li class="twitter">
    <a class="twitter-button">
      <span class="twitter-count share-count"></span>
      <i class="blogicon-twitter lg"></i><span class="share-label">ツイート</span></a>
  </li>
  <li class="googleplus">
    <a class="googleplus-button">
      <span class="googleplus-count share-count"></span>
      <i class="blogicon-plus lg"></i>
      <svg version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" width="36px" height="22px" viewBox="-14 -8 72 44" class="u7 uzlpSb"><path d="M32 8.2h-3v4h-4V15h4v4h3v-4h4v-2.9h-4V8.2zm6-2V8h3v15h3V4l-6 2.2z"></path><path d="M11.4 11.3v4.5h6c-.4 2.6-2.7 4.5-6 4.5-3.6 0-6.6-3.1-6.6-6.7s2.9-6.7 6.6-6.7c1.7 0 3.1.5 4.3 1.7l3.2-3.2c-2-1.8-4.5-2.9-7.5-2.9C5.3 2.5.3 7.5.3 13.6s5 11.1 11.1 11.1c6.5 0 10.7-4.6 10.7-10.9 0-.8-.1-1.7-.2-2.5H11.4z"></path></svg></a>
  </li>
</ul>
<script>
  window.fbAsyncInit = function() {
    FB.init({appId:'Facebook のアプリID',xfbml:true,version:'v2.7'});
  };
  (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";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>


<script>
!function(){var e=function(e,t){var n="https://clients6.google.com/rpc?key=AIzaSyCKSbrvQasunBoV16zDH9R33D88CeLr9gQ",o=new XMLHttpRequest;o.onreadystatechange=function(){if(4===o.readyState&&200===o.status){var e=JSON.parse(o.responseText);t(e.result.metadata.globalCounts.count)}else t(0)},o.open("post",n,!0),o.setRequestHeader("Content-Type","application/json"),o.send(JSON.stringify(e))},t=function(e,t,n){var o=document.createElement("script");o.type="text/javascript";var a="ExternalCallback_"+t;window[a]=function(e){o.parentNode&&o.parentNode.removeChild(o);try{delete window[a]}catch(t){window[a]=null}n(e)},o.src=e+"&callback="+a,document.body.appendChild(o)},n=function(e,t){var n=new XMLHttpRequest;n.open("get",e,!0),n.onreadystatechange=function(){if(4===n.readyState&&200===n.status){var e=JSON.parse(n.responseText),o="share"in e?e.share:{},a="share_count"in o?o.share_count:0;t(a)}else t(0)},n.send(null)},o=0,a=document.getElementById("tmplShareButtons");a.removeAttribute("id");var s=document.getElementsByClassName("hentry");Array.prototype.forEach.call(s,function(s){var r=s.getElementsByClassName("bookmark")[0],i=r.getAttribute("href"),l=r.innerHTML,c=a.cloneNode(!0),u=c.getElementsByClassName("hatena-bookmark-button")[0];u.setAttribute("href","http://b.hatena.ne.jp/entry/"+i),u.setAttribute("data-hatena-bookmark-title",l);var p="http://api.b.st-hatena.com/entry.count?url="+encodeURIComponent(i);t(p,o,function(e){u.getElementsByClassName("hatena-bookmark-count")[0].innerHTML=e}),o++;var m=c.getElementsByClassName("facebook-share-button")[0];m.addEventListener("click",function(){FB.ui({method:"share",href:i},function(){})});var p="https://graph.facebook.com/?id="+encodeURIComponent(i);n(p,function(e){m.getElementsByClassName("facebook-count")[0].innerHTML=e});var d=c.getElementsByClassName("twitter-button")[0];d.setAttribute("href","http://twitter.com/intent/tweet?url="+encodeURIComponent(i)+"&text="+l);var p="http://jsoon.digitiminimi.com/twitter/count.json?url="+encodeURIComponent(i);t(p,o,function(e){d.getElementsByClassName("twitter-count")[0].innerHTML=e.count}),o++;var h=c.getElementsByClassName("googleplus-button")[0],g=500,y=500,f=(window.screen.width-g)/2,b=(window.screen.height-y)/2;h.setAttribute("href","javascript:void(window.open('https://plus.google.com/share?url="+encodeURIComponent(i)+"', '_blank', 'width="+g+",height="+y+",left="+f+",top="+b+"'))");var v={method:"pos.plusones.get",id:"p",params:{nolog:!0,id:i,source:"widget",userId:"@viewer",groupId:"@self"},jsonrpc:"2.0",key:"p",apiVersion:"v1"};e(v,function(e){h.getElementsByClassName("googleplus-count")[0].innerHTML=e}),s.getElementsByClassName("social-buttons")[0].appendChild(c)}),a.parentNode.removeChild(a)}();
</script>

CSS

以下のコードをカスタマイズ > デザインCSSに入れてください。

.social-buttons ul {
  padding: 0;
  margin: 0;
}
.social-buttons ul li {
  display: inline-block;
  margin: 0;
  padding: 0;
  list-style-type: none;
  width: 15%;
  border-radius: 3px;
  vertical-align: text-top;
}
.social-buttons ul li a {
  width: 100%;
  display: inline-block;
  color: #fff;
  text-decoration: none;
  position: relative;
  text-align: center;
}
.social-buttons ul li a span.share-count {
  display: inline-block;
  width: 100%;
  color: #000;
  background: #fff;
  line-height: 2rem;
  border-radius: 3px 3px 0 0;
  opacity: 1;
  -webkit-transition: opacity 0.4s, transform 0.4s;
  transition: opacity 0.4s, transform 0.4s;
}
.social-buttons ul li a i {
  padding: 0 5px;
}
.social-buttons ul li a i.blogicon-plus {
  color: #dd5144;
}
.social-buttons ul li a svg {
  position: absolute;
  left: 0;
  right: 0;
  margin: 0 auto;
}
.social-buttons ul li a svg path {
  fill: #fff;
}
.social-buttons ul li a span.share-label {
  font-size: .7rem;
}
.social-buttons ul li a:hover span.share-count {
  opacity: 0.8;
}
.social-buttons ul li.hatena {
  background: #00a4de;
  border: solid 1px #00a4de;
}
.social-buttons ul li.facebook {
  background: #3e59a5;
  border: solid 1px #3e59a5;
}
.social-buttons ul li.twitter {
  background: #1b95e0;
  border: solid 1px #1b95e0;
}
.social-buttons ul li.googleplus {
  background: #dd5144;
  border: solid 1px #dd5144;
}
@media (max-width: 767px) {
  .social-buttons ul li a span.share-label {
    display: none;
  }
}

これで完了です。

このサイトの場合はいろいろ DOM 操作をやっていますのでもともとやや重めですが、このソーシャルボタンに変更したら、8秒くらいかかっていた読み込みが4秒くらいになりました。

以下は、Javascript の圧縮前のソースです。

Javascript 可読版

/*--------------------------------------------------------------------------*
 *
 *  はてなブログ用ソーシャルボタン
 *
 *  Copyright (c) 2016 IMUZA.com http://www.imuza.com
 *  Released under the MIT license
 *  http://opensource.org/licenses/mit-license.php
 *  
 *--------------------------------------------------------------------------*/


(function(){
  var getGoogleShareCount = function(obj, callback) {
    var url = 'https://clients6.google.com/rpc?key=AIzaSyCKSbrvQasunBoV16zDH9R33D88CeLr9gQ';
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        var obj = JSON.parse(xhr.responseText);
        callback(obj.result.metadata.globalCounts.count);
      } else {
        callback(0);
      }
    };
    xhr.open('post', url, true);
    xhr.setRequestHeader( 'Content-Type', 'application/json' );
    xhr.send(JSON.stringify(obj));
  };


  var getCountByJSONP = function(url, count, callback) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    var callbackName = 'ExternalCallback_' + count;
    window[callbackName] = function (data){
      if(script.parentNode){
        script.parentNode.removeChild(script);
      }
      try{
        delete window[callbackName];
      }catch(e){
        window[callbackName] = null;
      }
      callback(data);
    };
    script.src = url + '&callback=' + callbackName;
    document.body.appendChild(script);
  };


  var getFbShareCount = function(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('get', url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        var obj1 = JSON.parse(xhr.responseText),
          obj2 = 'share' in obj1 ? obj1.share : {},
            count = 'share_count' in obj2 ? obj2.share_count : 0;
        callback(count);
      } else {
        callback(0);
      }
    };
    xhr.send(null);
  };


  var count = 0;
  var tmpl = document.getElementById('tmplShareButtons');
  tmpl.removeAttribute('id');
//(2016/9/6)修正
// about ページで、記事ではない article を取得をしないようにクラス名に変更
//  var articles = document.getElementsByTagName('article');
  var articles = document.getElementsByClassName('hentry');
  Array.prototype.forEach.call(articles, function(article) {
    var bm = article.getElementsByClassName('bookmark')[0];
    var permalink = bm.getAttribute('href');
    var title = bm.innerHTML;
    var clone = tmpl.cloneNode(true);


    var hatenaButton = clone.getElementsByClassName('hatena-bookmark-button')[0];
    hatenaButton.setAttribute('href', 'http://b.hatena.ne.jp/entry/' + permalink);
    hatenaButton.setAttribute('data-hatena-bookmark-title', title);    
    var url = 'http://api.b.st-hatena.com/entry.count?url=' + encodeURIComponent(permalink);
    getCountByJSONP(url, count, function(data) {
      hatenaButton.getElementsByClassName('hatena-bookmark-count')[0].innerHTML = data;
    });
    count++;


    var facebookButton = clone.getElementsByClassName('facebook-share-button')[0];
    facebookButton.addEventListener('click', function() {
      FB.ui({
        method: 'share',
        href: permalink,
      }, function(response){});
    });
    var url = 'https://graph.facebook.com/?id=' + encodeURIComponent(permalink);
    getFbShareCount(url ,function(data) {
      facebookButton.getElementsByClassName('facebook-count')[0].innerHTML = data;
    });


    var twitterButton = clone.getElementsByClassName('twitter-button')[0];
    twitterButton.setAttribute('href', 'http://twitter.com/intent/tweet?url=' + encodeURIComponent(permalink) + '&text=' + title);
    var url = 'http://jsoon.digitiminimi.com/twitter/count.json?url=' + encodeURIComponent(permalink);
    getCountByJSONP(url, count, function(data) {
      twitterButton.getElementsByClassName('twitter-count')[0].innerHTML = data.count;
    });
    count++;


    var googleButton = clone.getElementsByClassName('googleplus-button')[0];
    var w = 500, h = 500, x = (window.screen.width - w ) / 2, y = (window.screen.height - h) / 2;
    googleButton.setAttribute('href', "javascript:void(window.open('https://plus.google.com/share?url=" + encodeURIComponent(permalink) + "', '_blank', 'width=" + w + ",height=" + h + ",left=" + x + ",top=" + y + "'))");
    var obj = {
      "method":"pos.plusones.get",
      "id":"p",
      "params":{
        "nolog":true,
        "id":permalink,
        "source":"widget",
        "userId":"@viewer",
        "groupId":"@self"
      },
      "jsonrpc":"2.0",
      "key":"p",
      "apiVersion":"v1"
    };
    getGoogleShareCount(obj, function(data) {
      googleButton.getElementsByClassName('googleplus-count')[0].innerHTML = data;
    });


    article.getElementsByClassName('social-buttons')[0].appendChild(clone);


  });
  tmpl.parentNode.removeChild(tmpl);
})();