こんにちは。やまぐちなな(@nana_winter_web )です。
UWebにカード型リンクを実装したのですが、なかなか高性能に作れたと思うのでコードをシェアしたいと思います。
カード型リンクは表示速度が遅くなりがちですが、非同期通信を使うことでユーザーに表示速度の遅さを感じさせにくくなりました。
コード
使うファイルはfunctions.phpと、CSS、card.phpファイルです。
card.phpは今回作るファイルです。
jQueryを使うので記事を読み込むより前にjQuery本体を読み込んでいる必要があります。
functions.php
ショートコードにしたかったのでfunctions.phpに下記のコードを追加します。
function card_link($atts) {
extract(shortcode_atts(array(
'url' => '',
'title' => '',
), $atts));
$id = preg_replace('/[^0-9a-zA-Z_-]/', '', $url);
return '
<script>
jQuery(function ($) {
$.ajax({
url: "'. get_theme_file_uri('/card.php'). '", // この後で追加するファイルまでのパス
type: "GET",
data: {
url: "'.$url.'"
},
success: function (data) {
if (data !== "") {
$("#'.$id.'").replaceWith(data);
}
}
});
});
</script>
<a href="'.$url.'" target="_blank" rel="noopener noreferrer" id="'.$id.'">'.$title.'</a>';
}
add_shortcode('card', 'card_link');
念のためにjQueryと書いているだけなので、$でも動くと思います。 このコードは、非同期通信が開始する前と終了後のことしかしてくれないので、テーマ内にcard.phpというファイルに非同期で通信する内容を書きます。
card.php
今回はテーマフォルダーの直下にcard.phpという名前で作りましたが、違うところに違う名前でも良いです。 その時はfunctions.phpのファイルまでのパスとファイル名を変更してください。
<?php
// 念のための変数の準備と初期化
$reuslt = array();
$html = '';
$ogp = array();
// htmlを取得
$ch = curl_init(); // cURLのセッションを初期化
curl_setopt($ch, CURLOPT_URL, $_GET['url']); // 取得するURLを指定
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返り値を文字列にする
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 'Location:'ヘッダーの内容をたどる
curl_setopt($ch, CURLOPT_MAXREDIRS, 10); // リダイレクト先を追いかける最大値
$html = curl_exec($ch); // cURLセッションの実行(HTMLを取得)
curl_close($ch); // cURLセッションを閉じる
// ogp情報を抜き出す
preg_match_all( '/\s*meta\s*property="?og:([^"]+)"?\s*content="?([^"]+)"?\s*\/?>/i', $html, $ogp );
// ogpが空の場合はtitleタグの情報を抜き出す
$ogp = array_filter($ogp,'array_filter');
if(empty($ogp)) {
preg_match_all( '/<(title)>([^"]+)<\/title>/i', $html, $ogp );
}
// 必要な情報のみにする
for( $i=0; $i < count($ogp[1]); $i++ ) {
$result[strtolower($ogp[1][$i])]=$ogp[2][$i];
}
// 変数resultが存在しているかで処理を分ける
if(!isset($result)) {
$card='';
} else if(empty($result['image']) || !isset($result['image'])) {
$img='<div class="post-card__figure"></div>' ;
} else {
$img='<figure class="post-card__figure"><img src="' .$result['image'].'" class="post-card__img"></figure>';
}
if(isset($result)) {
$card = '<div class="post-card"><a href="'.$_GET['url'].'" target="_blank" rel="noopener noreferrer"
class="post-card__link">
<div class="post-card__text">
<p class="post-card__title">'.$result['title'].'</p>
<p class="post-card__button">続きを読む<i class="fas fa-external-link-alt link__icon"></i></p>
</div>'.$img.'
</a></div>';
}
echo $card;
?>
CSS
シンプルで良ければCSSも準備してあります。
.post-card__link {
position: relative;
display: block;
padding: 0.5rem;
text-decoration: none;
color: #333;
border: 2px solid #f2f0f0;
-webkit-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
.post-card__link:hover, .post-card__link:active, .post-card__link:focus {
opacity: 0.7;
}
.post-card__link:hover .post-card__button, .post-card__link:active .post-card__button, .post-card__link:focus .post-card__button {
text-decoration: none;
}
.post-card__text {
width: 63%;
margin-left: auto;
}
@media screen and (min-width: 768px) {
.post-card__text {
width: 70%;
}
}
.post-card__button {
text-align: right;
text-decoration: underline;
color: #04c;
}
.post-card__button:after {
content: ">";
}
.post-card__figure {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
width: 35%;
height: 100%;
margin-bottom: 0;
background: #efefef;
}
@media screen and (min-width: 768px) {
.post-card__figure {
width: 25%;
}
}
.post-card__img {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
-o-object-fit: cover;
object-fit: cover;
}
.post-card__img _:-ms-fullscreen, :root .post-card__img {
width: auto;
}
使い方
使い方は簡単で、下記のコードの日本語の部分をリンク先に合わせて変更するだけです。
[card url="表示したいサイトのURL" title="カード型リンクになる前に表示する文字"]
こんな感じで表示されます。
仕様
タイトル、画像はOGPから取得、OGPがなければ、titleタグの情報を取得しています。
どちらも取得できなかった場合、URL自体が存在しない場合は空文字を返すようにしているので、その場合はカード型リンクになりません。
URLが存在して、ページが存在しない場合は404ページのデータが表示されます。
metaタグのproperty属性とcontent属性の順序が反対の場合は内容が取得できません。
レアなパターンかと思いますが、titleタグに属性やスペースが入っている場合も内容が取得できません。
その場合でも、ショートコードを使うときにtitleを指定しておけば、リンク先がなんのサイトかわからないということはありません。
取得できないパターンがいくつかあるので、修正できるところは修正していきたいです。
解説
各ファイルでやっていることの簡単な解説も書きました。
functions.phpでやっていること
function card_link($atts) {
extract(shortcode_atts(array(
'url' => '',
'title' => '',
), $atts));
// idに使用するために、URLから記号を削除
$id = preg_replace('/[^0-9a-zA-Z_-]/', '', $url);
return '
<script>
// 省略...
</script>
<div class="button__wrapper button__wrapper_center" id="'.$id.'"><a
href="'.$url.'" target="_blank"
rel="noopener noreferrer" class="button
button__main">'.$title.'</a></div>';
}
add_shortcode('card', 'card_link');
returnのところで、非同期通信用のコードと、通信が終わるまでの間とJavaScriptがオフだった場合に表示するタグを返します。
通信が成功すれば、ここで出力したscriptタグ以外のタグは置き換えられます。
複数のカード型リンクがあったときのために、idを指定しているのですが、記号があると上手くいかないので、URLから記号を除いたものを用意しています。
functions.phpの中のJavaScriptでやっていること
jQuery(function ($) {
$.ajax({
url: "'. get_theme_file_uri('/card.php'). '", // この後で追加するファイルまでのパス
type: "GET",
data: {
url: "'.$url.'"
},
success: function (data) {
if (data !== "") {
$("#'.$id.'").replaceWith(data);
}
}
});
});
JavaScriptには処理をするファイル(今回の場合はcard.php)と、メソッド、処理をするファイルに送るデータ(URL)、成功したときの処理を指定しています。
メソッドは今回、情報の取得が目的なのでGetにしていますが、Postでも動きます。
また通信に成功していても、情報が取得できない条件の場合は空文字を返すようにしています。 その場合は、functions.phpで出力したタグが置き換えられると困るので、条件分岐をしています。
card.phpでやっていること
$ch = curl_init(); // cURLのセッションを初期化
curl_setopt($ch, CURLOPT_URL, $_GET['url']); // 取得するURLを指定
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返り値を文字列にする
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 'Location:'ヘッダーの内容をたどる
curl_setopt($ch, CURLOPT_MAXREDIRS, 10); // リダイレクト先を追いかける最大値
$html = curl_exec($ch); // cURLセッションの実行(HTMLを取得)
curl_close($ch); // cURLセッションを閉じる
HTMLの取得はget_file_contents
よりもcURLの方が早いそうなので、そちらを利用しています。
上記のコード4行目で指定したURLが存在しなかった場合や、転送設定されていた場合に、転送先も追いかけるように設定しています。
ただ、リダイレクト先をいつまでも追いかけられると困るので、その下の行でリダイレクト先を追いかける最大値を指定しています。
まとめ
自分で記事のタイトルや、画像のURLを調べるのが面倒だなと思ったので何とか自動で出力しようかと思ったのですが、PHPにやらせると遅くなりがちでした。
なかなか良い解決法を見つけたのではないかなと思います。