相変わらずブログカードにこだわっています。 といってもこのところなかなか時間が取れず、10日ぶりくらいにこだわってみました(笑)。
何をやろうとしているかといいますと、自サイトの記事をブログカードにした場合に別ウィンドウ(タブ)ではなく、つまり target=_blank
ではなく、target=_top
にしたいということです。
で、前回で oEmbed の仕様もわかり、heroku でサーバも立ち上げて成功はしているのですが、結局のところ、はてなブログの記事にはきっちりと OGPデータが付与されているわけですから、わざわざサーバなどという大層なことをしなくても、HTMLファイルを読み込んで OGPデータからブログカードを作ればいいんじゃないか、そうすれば iframeを使わなくても div要素で行けるはずです。
01XMLHttpRequest(XHR)
読み込まれたファイルからさらにウェブサーバと通信するには XMLHttpRequestという組み込みオブジェクトを利用するようです。
とりあえず、XMLHttpRequest.onreadystatechange – Web API | MDN のサンプルコードを利用して htmlファイルが読み込めるかやってみましょう。
var xhr = new XMLHttpRequest(), method = "GET", url = "http://ausnichts.hateblo.jp/entry/imzmodules"; // 自サイトのページ xhr.open(method, url, true); xhr.onreadystatechange = function () { if(xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText); } }; xhr.send();
確かに読み込めています。ここから OGPデータを取り出せばいいのですが、このままの単なる文字列では使いづらいですので、DOMParserを使って DOMオブジェクトにパースしましょう。
02DOMParser
「実験的な機能」とあります。とりあえずやってみます。
var xhr = new XMLHttpRequest(), method = "GET", url = "http://ausnichts.hateblo.jp/entry/imzmodules"; // 自サイトのページ var parser = new DOMParser(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if(xhr.readyState === 4 && xhr.status === 200) { var doc = parser.parseFromString(xhr.responseText, "text/html"); console.log(doc); } }; xhr.send();
ちゃんと DOMオブジェクトになっています。あとは OGPデータを取り出してそれぞれ DIV要素の中に入れ込めばOKということになります。
03OGPデータを取り出してブログカードにする
では、実際に記事内にブログカードを作ってみましょう。方法は、ブログカードを置きたい場所にその記事の URLを置いておき、それを Javascriptでブログカードのコードに差し替えることでいけると思います。
記事内の HTMLをこんな感じで置いておきます。
<div id="blogcard">http://ausnichts.hateblo.jp/entry/imzmodules</div>
フッタに Javascriptを入れます。
<script> (function(){ var url = 'http://ausnichts.hateblo.jp/entry/imzmodules'; var xhr = new XMLHttpRequest(); var parser = new DOMParser(); xhr.open('GET', url); xhr.send(); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { var doc = parser.parseFromString(xhr.response, "text/html"); var meta = doc.getElementsByTagName('meta'); var og = {}; var re = /og:(.+)/; for(var i=0; i<meta.length; i++){ var p = meta[i].getAttribute('property'); if(p !== null){ var matches = p.match(re); if(matches !== null){ og[matches[1]] = meta[i].getAttribute('content'); } } } var re = /(.+) - .+/; og.title = og.title.match(re)[1]; og.site_url = og.url.split('/')[2]; og.description = og.description.substr(0, 100) + '...'; var blogcard = '<div class="thumb-wrapper">' + '<a href="' + og.url + '" target= "_top" class="thumb-link">' + '<img src="' + og.image + '" class="thumb"></a></div>' + '<p class="title">' + '<a href="' + og.url + '" target= "_top" class="title-link">' + og.title + '</a></p>' + '<p class="description">' + og.description + '</p>' + '<div class="footer"><p class="fav-wrap">' + '<a href="' + og.site_url + '" target="_top">' + '<img src="http://favicon.hatena.ne.jp/?url=' + og.url + '" class="favicon" />' + og.site_name + '</a>' + '<img src="http://b.hatena.ne.jp/entry/image/' + og.url + '" class="hatebu"></p></div>'; document.getElementById('blogcard').innerHTML = blogcard; } } })(); </script>
上がオリジナルのブログカードで、下がはてなのブログカードです。下のリンクは別タブで開きますが、上はそのタブで開きます。
04自サイト用オリジナルブログカード
実装するには複数のブログカードに対応しないといけませんので最終的に次のようになりました。
マークアップ
<div class="imzBlogcard">http://ausnichts.hateblo.jp/entry/imzmodules</div>
Javascript
<script> (function(){ var cards = document.getElementsByClassName('imzBlogcard'); Array.prototype.forEach.call(cards, function(card){ var url = card.children[0].innerHTML; var xhr = new XMLHttpRequest(); var parser = new DOMParser(); xhr.open('GET', url); xhr.send(); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { var doc = parser.parseFromString(xhr.response, "text/html"); var meta = doc.getElementsByTagName('meta'); var og = {}; var re = /og:(.+)/; for(var i=0; i<meta.length; i++){ var p = meta[i].getAttribute('property'); if(p !== null){ var matches = p.match(re); if(matches !== null){ og[matches[1]] = meta[i].getAttribute('content'); } } } var re = /(.+) - .+/; og.title = og.title.match(re)[1]; og.site_url = og.url.split('/')[2]; og.description = og.description.substr(0, 100) + '...'; var blogcard = '<div class="thumb-wrapper">' + '<a href="' + og.url + '" target= "_top" class="thumb-link">' + '<img src="' + og.image + '" class="thumb"></a></div>' + '<p class="title">' + '<a href="' + og.url + '" target= "_top" class="title-link">' + og.title + '</a></p>' + '<p class="description">' + og.description + '</p>' + '<div class="footer"><p class="fav-wrap">' + '<a href="' + og.site_url + '" target="_top">' + '<img src="http://favicon.hatena.ne.jp/?url=' + og.url + '" class="favicon" />' + og.site_name + '</a>' + '<img src="http://b.hatena.ne.jp/entry/image/' + og.url + '" class="hatebu"></p></div>'; card.innerHTML = blogcard; } } }); })(); </script>
サンプルCSS
.imzBlogcard a { text-decoration: none; } .imzBlogcard a:hover { text-decoration: underline; } .imzBlogcard { width: 100%; max-width: 500px; background: #fff; border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box; padding: 12px; } .imzBlogcard .thumb-wrapper{ width: 100px; height: 100px; float: right; margin: 0 0 10px 10px; padding: 0; position: relative; overflow: hidden; } .imzBlogcard .thumb-link { position: absolute; width: 1000%; left: 50%; margin: 0 0 0 -500%; text-align: center; } .imzBlogcard .thumb { width: auto; height: 100px; } .imzBlogcard .title { line-height: 1.3; } .imzBlogcard .title-link { color: #333; font-weight: bold; font-size: 17px; line-height: 1.4; } .imzBlogcard .description { color: #666; font-size: 12px; line-height: 1.5; } .imzBlogcard .footer { clear: both; } .imzBlogcard .fav-wrap { color: #999; margin: 5px 0 0 0; font-size: 12px; } .imzBlogcard .hatebu { margin: 0 0 0 5px; border: none; display: inline; vertical-align: middle; }
完成です。Javascriptはもう少し整理できそうには思います。
ブラウザの対応ですが、Chrome, Firefox, iPhone/Safari, iPhone/Chrome, Android/Chrome の最新版は問題ありません。Edge, IE は(今日ところは)未確認です。