◆ ページロード後にボタンを作らずその場で作ってるのがありそう

このブログのトップやカテゴリなどの一覧画面を開くとわかると思うのですが 全部表示されるまでがすごく重いです
止まってるところはソーシャルボタンのロードみたいです

昔 管理画面でチェックボックスをオンにするだけで追加できるなら多いほうがどのサービス使ってる人でも使えて便利だよね と思ってあれこれ入れました
それが最近になって後悔してます
ページのロードをブロックしないで 後からロードするような作りになってるだろうと思ったのですが 動きを見た感じではそうでもなさそうです

使ってるソーシャルボタン

今表示しているのは

  • Hatena
  • Twitter
  • Facebook
  • Google+1
  • Evernote
  • Pocket

です

減らしたいのですが 個人的にwebサービスで使えてた機能が使えなくなる変更がすごく嫌いなので 自分のところではあまりしたくないなーという気持ちです
使われてないとはっきりわかればいいのですが シェアや保存した数が見れるのは Hatena と Facebook と Pocket だけです
これらはたまに 1 や 2 と数字が出ているのがあるので使われてそうです

ただ 実際にはそのソーシャルボタンのサービスを使っていてもどこでも使えるようにブラウザエクステンションなどを使うことが多くて 埋め込みボタンを使うケースなんてほとんど無い気もします
例えばはてなだとタグやメモ代わりのコメントなどを書くことを考えると埋め込みのじゃ使いづらいですから
でもポケットだと単純に押すだけでいいので記事タイトルだけ見て後で見そうなのを適当にクリックしていくだけで保存できるのでブログ内にボタンがあるなら使いそうな気もします
ボタン自体の利用率が見れれば良いのですけどね

どうしよう

とりあえず今のところは 「そのまま放置」 か 「使われてそうなものだけを後からロードするように作り直す」 のどっちかの予定です

Google+1 なんてもう数年名前すら聞かないようなものであのすぐにサービスを切り捨てる Google がよく続けてたなと思うほどです
ググっても最近の紹介記事なんて全然ないですね 2010 年代前半ばかりです
Evernote も 必要なところをコピペするだけなのであえてボタンから使うなんてケースはレアでしょう
そもそも Evernote 自体がユーザ減ってる気がします
自分の周りだけかもしれませんが これも 2010 年代前半はけっこう注目されてるサービスだったと思うのですけどね
なので自分でボタンの再配置なら Hatena, Facebook, Twitter, Pocket になりそうです

Pocket の例

自分で設置する場合にどうすればいいのかを調べてみました
基本は用意されてるコードの URL を書き換えるだけだと思いますけど一応です

とりあえず一番新しそうな雰囲気の Pocket で見てみました

https://getpocket.com/publisher/button

表示形式ごとにコードが用意されていて貼り付けるだけのようです
ただ これだと URL 指定がないので自身のページのみみたいです

一覧画面みたいな URL 指定が必要な方法はドキュメントの方に書いてある方法で a タグに data-save-url を追加して URL を指定します
https://getpocket.com/publisher/button_docs

どんなことをしてるのか見るために script 部分をフォーマットしてみました

<a data-pocket-label="pocket" data-pocket-count="horizontal" class="pocket-btn" data-lang="en"></a>
<script type="text/javascript">
!(function(d, i) {
if (!d.getElementById(i)) {
var j = d.createElement("script")
j.id = i
j.src = "https://widgets.getpocket.com/v1/j/btn.js?v=1"
var w = d.getElementById(i)
d.body.appendChild(j)
}
})(document, "pocket-btn-js")
</script>

「var w =」 の行が無意味で消し忘れのような気がしますが それは気にせず読むと pocket-btn-js という id が document 上に無いときだけ処理をしています
その id は 1 回目の処理で作られるので一覧画面みたいなところでも 1 度だけ実行させるということですね
処理というのは https://widgets.getpocket.com/v1/j/btn.js?v=1 の JavaScript ファイルをロードするというものです
a タグボタン化する処理でしょう

その中身ですが一部省略するとこういうものです

!function()
{
var pktObj = {ic:1};
pktObj.l = function()
{
/* 略 */
}

pktObj.chk = function()
{
if (document.readyState === "complete" || document.readyState === "interactive")
{
if (pktObj.to)
clearTimeout(pktObj.to);
pktObj.l();
}

else
{
var wait = pktObj.ic * 10;
if (wait > 100) wait = 100;
pktObj.to = setTimeout(function(){pktObj.chk()},wait);
pktObj.ic++;
}
}

pktObj.chk();
}();

l という関数がタグの属性を読み取って iframe を作る部分です
即実行される chk 関数はロードされるまでまって ロード後に l 関数を実行するものです

ということは a タグを作った後に https://widgets.getpocket.com/v1/j/btn.js?v=1 の JavaScript ファイルをロードさせるだけで良さそうです
それ以前に今の処理でもロード後に iframe 化するので特に遅い原因ではないと思います

ブロックしてるのは?

Pocket みたいな作りなのも多いのかなと思ってもう一度一覧画面を見てみると Twitter や Facebook は出るのは遅めで最初から出てるように見えるのは Hatena や Evernote です

Hatena

ブロックしてそうなはてなのボタンを見てみます
http://b.hatena.ne.jp/guide/bbutton

<a href="http://b.hatena.ne.jp/entry/b.hatena.ne.jp/guide/bbutton"
class="hatena-bookmark-button"
data-hatena-bookmark-layout="basic-counter"
data-hatena-bookmark-width="60"
data-hatena-bookmark-height="20"
title="このエントリーをはてなブックマークに追加">
<img src="https://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="https://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script>

基本は Pocket と同じようなものです
href に保存する URL をつければ良いようです

Pocket と違って JavaScript ファイルを直接ロードしてるので一覧に 30 項目あれば 30 回ロードして実行されそう ですが同じ URL なのでたぶんキャッシュが使われるでしょう
実際に devtools の Network タブで見ると bookmark_button.js ファイルは 1 つしかありませんでした
form disk cache というのが並んでそうでしたのに 1 つだけです

ロードしてる https://b.st-hatena.com/js/bookmark_button.js を見ると JavaScript の変数を使ってすでに実行済みかを判断していたので Pocket 同様一度しか処理が行われないはずです
script タグの属性に async があるのでブロックして即実行はしないでスクリプトがロードできたタイミングで実行されます
一覧画面のロードの遅さを考えれば ページのロードより先に JavaScript が実行されてるので早いうちにボタンが見えてるのでしょう

中身はけっこう長かったのですがこういう部分がありました

extend(B.BookmarkButton, {
interval: 428, /* Welcome to Shibuya.js! */
timerId: 0,
setup: function () {
if (this.timerId) return;
var Button = this;
this.timerId = window.setInterval(function () { Button.tryCreate(); }, this.interval);
this.tryCreate();
},
lastLinkCount: 0,
tryCreate: function () {
var links = document.getElementsByTagName('a');
var linkCount = links.length;
if (linkCount === this.lastLinkCount) return;
var buttonLinks = [];
var classRE = /(?:^|\s)hatena-bookmark-button(?:\s|$)/;
for (var i = 0; i < linkCount; i++)
if (classRE.test(links[i].className) &&
!links[i].getAttribute('data-hatena-bookmark-initialized'))
buttonLinks.push(links[i]);
for (var i = 0; i < buttonLinks.length; i++)
new B.BookmarkButton(buttonLinks[i]);
this.lastLinkCount = links.length;
},

実行は
Hatena.Bookmark.BookmarkButton.setup();
で始まるので setup 関数が呼び出されます

setInterval で 0.428 秒ごとにチェックしてボタン化してるようです
428 に合わせて Shibuya なんて書いてますが Hatena の会社って渋谷にあるのでしたっけ

clearInterval の処理は無いようなのでロード後も追加すればボタン化されますが小まめにムダな処理がされてるのはなんか嫌な気分です
そうは言っても古いブラウザのせいで MutationObserver とか使えなくて setInterval なのは仕方ないのでしょう

どちらかというと重くなると言うよりデバッグ処理の途中で余計なのが出てくるのが邪魔で setInterval が嫌いです
このライブラリは外部からアクセスできる作りなので ロード後に setInteral を止めたいなら このコードでできます

window.addEventListener("load", eve => clearTimeout(Hatena.Bookmark.BookmarkButton.timerId))

外部からアクセス可能な作りはこういうことをできるので好みです

話を戻すと ページのロード完了より先にボタン化処理をしてるので遅い部分はありますが そもそものページロードが遅い理由が他にありそうです
このボタンに限れば async を defer にすればロード後に実行されるので解決できます



コードも読んだりで思ったより時間がかかったので 今回はこの辺にしておきます
遅いのを見つけてそれを自分で後から追加するにしても ライブドアブログの埋め込み機能以外を使うものがあるなら全部そこで管理したいので 近い内に全置き換えにするかもしれません
残りのボタンは置き換えることがあればそのときにでも見ることにします

あと 無いような気はしますが もし頻繁にこのブログを見ていてボタンを使ってるという人がいるなら コメントに書いてもらえれば考慮します