タブブラウザが全盛の昨今では、タブに表示されるWEBサイトのアイコン(favicon)に新着数などの数字を動的に表示しているサービスが多い。例えば、facebookやBacklog、Gmailなど、結構色んなWEBサイトで数字バッジでのアイコン通知を行っている。
どうやっているのかと調べてみたら、結構目からウロコ的なやり方だった。
簡単に説明すると、HTML5のCanvasで<link>タグに指定されているアイコン画像を読み込ん来て、その上に数字とバッジを描画して新しい画像を作成し、出来た画像を<link>タグに再設定してあげるという方法だ。いやぁ、よく考えついたもんだ。
──と言うことで、この仕組みをWordPressに連動させて、未読の新着記事数のバッジをアイコンの上に描画してみようと思う。未読記事の判定は以前記事にしたので、詳細はそちらを見てもらうこととして、本項ではバッジ付きアイコン(favicon)の生成処理を紹介する。
ウチのサイトを例としているので、あまり汎用的ではないところはご了承いただきたい。
まずはサイトのTOPページとして出力されるHTMLのHEAD内に下記の記述をして、WEBサイトのアイコンを有効化しておく。
HTML
<head>
~(省略)~
<link rel="shortcut icon" href="/assets/img/favicon.ico" type="image/x-icon">
</head>
次に、ページ内に読み込まれるJavaScriptにてアイコンのバッジ付き再描画の処理関数を追加する。
JavaScript
// add badge to favicon
function addBadgeToIcon(new_posts) {
if (new_posts > 0) {
$(document).find('link').each(function() {
if ($(this).attr('rel').indexOf('icon') != -1) {
var $icon = $(this);
var icon = $icon[0];
var cvs = document.createElement('canvas');
cvs.width = cvs.height = 16;
var ctx = cvs.getContext('2d');
var img = document.createElement('img');
img.crossOrigin = 'Anonymous';
img.onload = function() {
// when base icon image size 32 x 32
ctx.clearRect(0, 0, 32, 32);
// resize to 16 x 16 for view size
ctx.drawImage(img, 0, 0, 32, 32, 0, 0, 16, 16);
ctx.beginPath();
ctx.fillStyle = '#ff2020'; // background color of badge
ctx.arc(11, 11, 5, 0, Math.PI*2, true);
ctx.fill();
ctx.font = '6px Arial';
ctx.fillStyle = '#ffffff'; // color of number text in badge
ctx.textBaseline = 'bottom';
ctx.textAlign = 'right';
ctx.fillText(new_posts, 14, 17, 16);
icon.href = cvs.toDataURL();
};
img.src = icon.getAttribute('href');
}
});
}
}
これで、addBadgeToIcon()関数に新着投稿数の数字new_postsを渡して呼び出すだけで、新着投稿数のバッジが付与されたアイコンが再描画されます。
あとは、新着投稿数を算出する処理をJavaScriptに書くか、もしくはテンプレート側のPHPなどで算出してJavaScriptに渡してあげればOKです。このあたりの処理については、利用しているWordPressのテーマやテンプレートによって千差万別なので、汎用的な処理例として紹介できないのですが、ウチのサイトでやっている処理を一例としてあげておきます。
JavaScript (ka2.orgでの例)
var new_posts = $('.new-item').length;
if (new_posts > 0) {
$('.new-item').each(function(){
var rp_id = Number($(this).parent('.brick').attr('data-postid'));
var data = getCookie('already_read').split(',');
var post_ids = data.filter(function(x, i, self) { return self.indexOf(x) === i; });
var rp_already_read = false;
post_ids.forEach(function() {
if (Number(arguments[0]) == rp_id) {
rp_already_read = true;
}
});
if (rp_already_read) new_posts--;
});
addBadgeToIcon(new_posts);
}
ウチのサイトではTOPページの投稿一覧を出力する時に、その記事が新着記事だった場合、<div class="new-item"></div>という要素を、記事本体<div class="brick" data-postid="投稿ID">~</div>の子要素として追加しています。なので、new-itemというクラスを持つ要素をカウントすることで、新着投稿数が算出できます。その後で、以前記事にも書いたCookieによる未読・既読の判定を行って、もしその新着記事が既読であった場合は前出の要素カウントをマイナスして行きます。これを表示されている記事数分繰り返して、出た数字が新着かつ未読な投稿数ということになります。その数をaddBadgeToIcon()関数に渡してあげることでバッジ付きアイコンを実現しています。
Cookieでの未読判定が入っているので、テーマやテンプレートなどのPHP側だけで処理が完結できなくて、ちょっと迂遠な感じですが、PHPを介させようとするとAjaxとか考えないといけなくなるので、一応スマートなやり方かと思います。
実際に未読の新着記事があった場合は、こんな感じになります。