ふと気になったので静的ページで WordPress の埋め込みに対応できるかテストしてみることにしました。
調査
調べてみたところ、 WordPress の埋め込みは oEmbed の仕様をベースに独自カスタマイズが入った仕組みとなっている模様。
必要なのは以下の3つ
- oEmbed の仕様に則ったデータ ( XML または JSON 、あるいは両方)
- ページ自体に上述のデータへの
link
タグ - 埋め込み時に表示する HTML
サンプルコード
今回はひとまず以下のような構成に。
dist/
├ css/ // 略
├ embed/ // 埋め込み用のリソース
│ ├ index.html // 埋め込み時に表示するHTML
│ └ index.json // 埋め込みに必要なデータ
│
├ js/ // 略
└ index.html // 本来の閲覧者が実際に見るページ
embed/index.json
まずはデータ。今回は JSON 形式を用意しました。
{
"version": "1.0",
"provider_name": "Embed Test Page",
"provider_url": "https://embedtest.example.jp/",
"author_name": "アルム=バンド",
"title": "TopPage",
"type": "rich",
"width": 600,
"height": 338,
"html": "<blockquote><a href=\"https://embedtest.example.jp/index.html\">TopPage</a></blockquote>\n<script type=\"text/javascript\">\n<!--//--><![CDATA[//><!--\n\t\t/*! This file is auto-generated */\n\t\t!function(c,d){\"use strict\";var e=!1,n=!1;if(d.querySelector)if(c.addEventListener)e=!0;if(c.wp=c.wp||{},!c.wp.receiveEmbedMessage)if(c.wp.receiveEmbedMessage=function(e){var t=e.data;if(t)if(t.secret||t.message||t.value)if(!/[^a-zA-Z0-9]/.test(t.secret)){for(var r,a,i,s=d.querySelectorAll('iframe[data-secret=\"'+t.secret+'\"]'),n=d.querySelectorAll('blockquote[data-secret=\"'+t.secret+'\"]'),o=0;o<n.length;o++)n[o].style.display=\"none\";for(o=0;o<s.length;o++)if(r=s[o],e.source===r.contentWindow){if(r.removeAttribute(\"style\"),\"height\"===t.message){if(1e3<(i=parseInt(t.value,10)))i=1e3;else if(~~i<200)i=200;r.height=i}if(\"link\"===t.message)if(a=d.createElement(\"a\"),i=d.createElement(\"a\"),a.href=r.getAttribute(\"src\"),i.href=t.value,i.host===a.host)if(d.activeElement===r)c.top.location.href=t.value}}},e)c.addEventListener(\"message\",c.wp.receiveEmbedMessage,!1),d.addEventListener(\"DOMContentLoaded\",t,!1),c.addEventListener(\"load\",t,!1);function t(){if(!n){n=!0;for(var e,t,r=-1!==navigator.appVersion.indexOf(\"MSIE 10\"),a=!!navigator.userAgent.match(/Trident.*rv:11\\./),i=d.querySelectorAll(\"iframe.wp-embedded-content\"),s=0;s<i.length;s++){if(!(e=i[s]).getAttribute(\"data-secret\"))t=Math.random().toString(36).substr(2,10),e.src+=\"#?secret=\"+t,e.setAttribute(\"data-secret\",t);if(r||a)(t=e.cloneNode(!0)).removeAttribute(\"security\"),e.parentNode.replaceChild(t,e)}}}}(window,document);\n//--><!]]>\n</script><iframe sandbox=\"allow-scripts\" security=\"restricted\" src=\"https://embedtest.example.jp/embed/index.html\" width=\"600\" height=\"338\" title=\"“TopPage” — Embed Test Page\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"></iframe>"
}
各種パラメータはもとより、 html
の部分がボリューミーです。ベースは WordPress が出力している JSON の中身をベースにして、サイト名やページ名、URL等のパラメータ部分だけ必要に応じて書き換えたものとなります。
WordPress の場合、 <blockquote>~</blockquote>~~<iframe>~</iframe>
という形式になっていなければならないようです。また、 blockquote
, a
, iframe
以外は使用できない等の制約もあるようです。
index.html
実際にビジターが閲覧するページでは上述のデータへのリンクを貼ります。
<link rel="alternate" type="application/json+oembed" href="https://embedtest.example.jp/embed/index.json" />
今回は JSON だけなので1行ですが、 XML の場合は XML 用の、両方用意した場合は2行になります。
embed/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<link rel="canonical" href="">
<style type="text/css">
/*! This file is auto-generated */
body,html{padding:0;margin:0}body{font-family:sans-serif}.screen-reader-text{border:0;clip:rect(1px,1px,1px,1px);-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;word-wrap:normal!important}.dashicons{display:inline-block;width:20px;height:20px;background-color:transparent;background-repeat:no-repeat;background-size:20px;background-position:center;transition:background .1s ease-in;position:relative;top:5px}.dashicons-no{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M15.55%2013.7l-2.19%202.06-3.42-3.65-3.64%203.43-2.06-2.18%203.64-3.43-3.42-3.64%202.18-2.06%203.43%203.64%203.64-3.42%202.05%202.18-3.64%203.43z%27%20fill%3D%27%23fff%27%2F%3E%3C%2Fsvg%3E")}.dashicons-admin-comments{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M5%202h9q.82%200%201.41.59T16%204v7q0%20.82-.59%201.41T14%2013h-2l-5%205v-5H5q-.82%200-1.41-.59T3%2011V4q0-.82.59-1.41T5%202z%27%20fill%3D%27%2382878c%27%2F%3E%3C%2Fsvg%3E")}.wp-embed-comments a:hover .dashicons-admin-comments{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M5%202h9q.82%200%201.41.59T16%204v7q0%20.82-.59%201.41T14%2013h-2l-5%205v-5H5q-.82%200-1.41-.59T3%2011V4q0-.82.59-1.41T5%202z%27%20fill%3D%27%230073aa%27%2F%3E%3C%2Fsvg%3E")}.dashicons-share{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.5%2012q1.24%200%202.12.88T17.5%2015t-.88%202.12-2.12.88-2.12-.88T11.5%2015q0-.34.09-.69l-4.38-2.3Q6.32%2013%205%2013q-1.24%200-2.12-.88T2%2010t.88-2.12T5%207q1.3%200%202.21.99l4.38-2.3q-.09-.35-.09-.69%200-1.24.88-2.12T14.5%202t2.12.88T17.5%205t-.88%202.12T14.5%208q-1.3%200-2.21-.99l-4.38%202.3Q8%209.66%208%2010t-.09.69l4.38%202.3q.89-.99%202.21-.99z%27%20fill%3D%27%2382878c%27%2F%3E%3C%2Fsvg%3E");display:none}.js .dashicons-share{display:inline-block}.wp-embed-share-dialog-open:hover .dashicons-share{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.5%2012q1.24%200%202.12.88T17.5%2015t-.88%202.12-2.12.88-2.12-.88T11.5%2015q0-.34.09-.69l-4.38-2.3Q6.32%2013%205%2013q-1.24%200-2.12-.88T2%2010t.88-2.12T5%207q1.3%200%202.21.99l4.38-2.3q-.09-.35-.09-.69%200-1.24.88-2.12T14.5%202t2.12.88T17.5%205t-.88%202.12T14.5%208q-1.3%200-2.21-.99l-4.38%202.3Q8%209.66%208%2010t-.09.69l4.38%202.3q.89-.99%202.21-.99z%27%20fill%3D%27%230073aa%27%2F%3E%3C%2Fsvg%3E")}.wp-embed{padding:25px;font-size:14px;font-weight:400;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;line-height:1.5;color:#8c8f94;background:#fff;border:1px solid #dcdcde;box-shadow:0 1px 1px rgba(0,0,0,.05);overflow:auto;zoom:1}.wp-embed a{color:#8c8f94;text-decoration:none}.wp-embed a:hover{text-decoration:underline}.wp-embed-featured-image{margin-bottom:20px}.wp-embed-featured-image img{width:100%;height:auto;border:none}.wp-embed-featured-image.square{float:left;max-width:160px;margin-right:20px}.wp-embed p{margin:0}p.wp-embed-heading{margin:0 0 15px;font-weight:600;font-size:22px;line-height:1.3}.wp-embed-heading a{color:#2c3338}.wp-embed .wp-embed-more{color:#c3c4c7}.wp-embed-footer{display:table;width:100%;margin-top:30px}.wp-embed-site-icon{position:absolute;top:50%;left:0;transform:translateY(-50%);height:25px;width:25px;border:0}.wp-embed-site-title{font-weight:600;line-height:1.78571428}.wp-embed-site-title a{position:relative;display:inline-block;padding-left:35px}.wp-embed-site-title{display:table-cell}.wp-embed-excerpt p{white-space: nowrap;text-overflow: ellipsis;overflow: hidden;}
</style>
</head>
<body>
<div class="wp-embed">
<p class="wp-embed-heading">
<a href="https://embedtest.example.jp/index.html" target="_top">TopPage</a>
</p>
<div class="wp-embed-excerpt">
<p>静的サイトで WordPress の埋め込みに対応できるかどうかのテストです。仕様はある程度 oEmbed に則っている模様です。</p>
</div>
<div class="wp-embed-footer">
<div class="wp-embed-site-title">
<a href="https://embedtest.example.jp/" target="_top">
<span>Embed Test Page</span>
</a>
</div>
</div>
</div>
<script type="text/javascript">
/*! This file is auto-generated */
!function(c,u){"use strict";var r,t,e,n=u.querySelector&&c.addEventListener,b=!1;function f(e,t){c.parent.postMessage({message:e,value:t,secret:r},"*")}function i(){if(!b){b=!0;var e,r=u.querySelector(".wp-embed-share-dialog"),t=u.querySelector(".wp-embed-share-dialog-open"),n=u.querySelector(".wp-embed-share-dialog-close"),i=u.querySelectorAll(".wp-embed-share-input"),a=u.querySelectorAll(".wp-embed-share-tab-button button"),o=u.querySelector(".wp-embed-featured-image img");if(i)for(e=0;e<i.length;e++)i[e].addEventListener("click",function(e){e.target.select()});if(t&&t.addEventListener("click",function(){r.className=r.className.replace("hidden",""),u.querySelector('.wp-embed-share-tab-button [aria-selected="true"]').focus()}),n&&n.addEventListener("click",function(){l()}),a)for(e=0;e<a.length;e++)a[e].addEventListener("click",s),a[e].addEventListener("keydown",d);u.addEventListener("keydown",function(e){var t;27===e.keyCode&&-1===r.className.indexOf("hidden")?l():9===e.keyCode&&(t=e,e=u.querySelector('.wp-embed-share-tab-button [aria-selected="true"]'),n!==t.target||t.shiftKey?e===t.target&&t.shiftKey&&(n.focus(),t.preventDefault()):(e.focus(),t.preventDefault()))},!1),c.self!==c.top&&(f("height",Math.ceil(u.body.getBoundingClientRect().height)),o&&o.addEventListener("load",function(){f("height",Math.ceil(u.body.getBoundingClientRect().height))}),u.addEventListener("click",function(e){var t=((t=e.target).hasAttribute("href")?t:t.parentElement).getAttribute("href");event.altKey||event.ctrlKey||event.metaKey||event.shiftKey||t&&(f("link",t),e.preventDefault())}))}function l(){r.className+=" hidden",u.querySelector(".wp-embed-share-dialog-open").focus()}function s(e){var t=u.querySelector('.wp-embed-share-tab-button [aria-selected="true"]');t.setAttribute("aria-selected","false"),u.querySelector("#"+t.getAttribute("aria-controls")).setAttribute("aria-hidden","true"),e.target.setAttribute("aria-selected","true"),u.querySelector("#"+e.target.getAttribute("aria-controls")).setAttribute("aria-hidden","false")}function d(e){var t,r=e.target,n=r.parentElement.previousElementSibling,i=r.parentElement.nextElementSibling;if(37===e.keyCode)t=n;else{if(39!==e.keyCode)return!1;t=i}(t="rtl"===u.documentElement.getAttribute("dir")?t===n?i:n:t)&&(t=t.firstElementChild,r.setAttribute("tabindex","-1"),r.setAttribute("aria-selected",!1),u.querySelector("#"+r.getAttribute("aria-controls")).setAttribute("aria-hidden","true"),t.setAttribute("tabindex","0"),t.setAttribute("aria-selected","true"),t.focus(),u.querySelector("#"+t.getAttribute("aria-controls")).setAttribute("aria-hidden","false"))}}n&&(!function e(){c.self===c.top||r||(r=c.location.hash.replace(/.*secret=([\d\w]{10}).*/,"$1"),clearTimeout(t),t=setTimeout(function(){e()},100))}(),u.documentElement.className=u.documentElement.className.replace(/\bno-js\b/,"")+" js",u.addEventListener("DOMContentLoaded",i,!1),c.addEventListener("load",i,!1),c.addEventListener("resize",function(){c.self!==c.top&&(clearTimeout(e),e=setTimeout(function(){f("height",Math.ceil(u.body.getBoundingClientRect().height))},100))},!1))}(window,document);
</script>
</body>
</html>
埋め込み時に表示される HTML 。こちらも基本的に WordPress のコードを持ってきて、共有オプションのところで dashicon を使っていたり WordPress 特有のリソースを使用している部分は削除してシンプルにしました。
検証
これらをサーバにアップロードして、 WordPress で動作確認してみます。
記事作成画面で埋め込みブロックを選択します。
URLを貼り付けると、埋め込みに成功しました。大丈夫そう……と思ったのですが。
別の WordPress 環境だと失敗してしまいました。こちらは使用しているテーマや PHP のバージョン等条件が諸々異なるので何とも言えませんが、環境によってできたりできなかったりすることがあるようです。
……うーん、微妙。
検証した結果としては「最低限動くかどうか」というラインになってしまいました。
また、埋め込みに対応するためにはパラメータを記述したデータ (XML or/and JSON) や専用の HTML が必要ということが検証して分かりました。
これを静的サイトの各ページごとに生成するのは大変なので、仮に対応するならば何かしらの仕組みが必要となりそうです。