
たまにはサイトの裏側の小技を記事にしてみる回。
小噺です。
インプレ記事ではYouTubeを埋め込みしていますが、コアウェブバイタル(サイトの表示速度)に配慮して公式の埋め込みを使用していません。SNSをほぼ使わずに検索流入をメインで想定しているために、ここらへんはとても気を遣うところであったりします。
定番はプラグインでの実装でしょう。
公式埋め込みプレーヤーそっくりのUIで実装してくれますし、APIを叩かせれば動画名やチャンネル名も一緒に表示してくれますね。
でも自作しちゃった。
SEO的に最適化したいと思ったら細かい仕様が気に入らず、結局当サイトでは自作で実装してしまっています。特徴は
- youtube.comもしくはyoutu.beもしくはiframeがある記事だけを対象にする
- URLを入れるだけで自動的に埋め込み化&既存の公式埋め込みも置換
- ページロード時にはサムネイル画像/動画タイトル/チャンネル名だけを読込
- 再生ボタンをクリックしたときに動画を読込
- APIを叩かずに、oEmbedエンドポイントを使用して動画のタイトルとチャンネル名をJSONで取得(30日サーバーでキャッシュ)
- aria-labelでボタンの役割を構造化する
- サムネはytimg.comから取得
- 埋め込みされたサムネに自動で動画名&チャンネル名をalt属性として付与

など。alt付与したかったのがデカかったです。表示速度そのものはたぶんプラグインと同等。Youtube公式の埋め込みと比べてみると、大幅に速度改善できているのが分かると思います。

こちらは自作コード組込時の記事の表示速度。

こちらがYoutube公式の埋め込み時の表示速度。
導入は自己責任でお願いします。サポートも一切しませんのであしからず。
functions.php
直接書いてもいいですが、わたしはyoutube.phpとしてモジュール化して、functionsからrequire しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
<?php // ---------------------------------- // 投稿ページ・Youtube読込速度改善 // ---------------------------------- /* YouTube の <iframe> をクリックでロード型に自動置換 */ add_filter('the_content', function ($content) { $pattern = '~<iframe[^>]+src=["\']([^"\']+youtube(?:-nocookie)?\.com/[^"\']+)["\'][^>]*></iframe>~i'; if (!preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) return $content; // 同一IDは1回だけメタ取得 $meta_cache = []; foreach ($matches as $m) { $src = $m[1]; $id = null; if (preg_match('~/(?:embed/|shorts/)([A-Za-z0-9_-]{6,})~', $src, $x)) { $id = $x[1]; } elseif (preg_match('~[?&]v=([A-Za-z0-9_-]{6,})~', $src, $x)) { $id = $x[1]; } if (!$id) continue; if (!isset($meta_cache[$id])) { // title/author取得 $meta_cache[$id] = get_youtube_meta($id); } $title = $meta_cache[$id]['title'] ?? ''; $author = $meta_cache[$id]['author'] ?? ''; $thumb = "https://i.ytimg.com/vi/{$id}/hqdefault.jpg"; // alt / aria 用テキスト $alt_text = ''; if ($title !== '') { $alt_text = $title . ($author !== '' ? '|' . $author : ''); } // 置換HTML $replacement = '<div class="yt-lite" data-ytid="'.esc_attr($id).'" role="button"' . ' style="position:relative;background:#000;display:block;overflow:hidden;cursor:pointer;aspect-ratio:16/9;border-radius:6px;"'; $replacement .= $alt_text !== '' ? ' aria-label="'.esc_attr($alt_text).'"' : ''; $replacement .= '>'; // サムネ(最背面) $replacement .= '<img class="yt-thumb" src="'.esc_url($thumb).'"' . ' alt="'.esc_attr($alt_text . '|YouTube').'"' . ' loading="lazy" decoding="async"' . ' style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:brightness(.9);z-index:1;display:block;"' . '>'; // 情報オーバーレイ(左上) $replacement .= '<div class="yt-meta"' . ' style="position:absolute;top:8px;left:8px;z-index:2;color:#fff;font-size:.8rem;line-height:1.4;max-width:90%;text-shadow:0 0 4px rgba(0,0,0,.8);pointer-events:none;">'; if ($title !== '') $replacement .= '<div class="yt-title" style="font-weight:700;margin-bottom:2px;">'.esc_html($title).'</div>'; if ($author !== '') $replacement .= '<div class="yt-author" style="font-size:.75rem;opacity:.85;">'.esc_html($author).'</div>'; $replacement .= '</div>'; // 再生ボタン(最前面) $replacement .= '<button class="yt-play"' . ($alt_text !== '' ? ' aria-label="'.esc_attr($alt_text).' を再生"' : ' aria-label="YouTube を再生"') . ' style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:68px;height:48px;border:0;border-radius:14px;background:rgba(0,0,0,.6);cursor:pointer;z-index:3;"' . '>' . '<span aria-hidden="true"' . ' style="display:block;width:0;height:0;margin:0 auto;' . 'border-style:solid;border-width:10px 0 10px 16px;' . 'border-color:transparent transparent transparent #fff;' . 'transform:translateX(2px);"></span>' . '</button>'; // noscript リンク $replacement .= '<noscript>' . '<iframe src="https://www.youtube-nocookie.com/embed/' . esc_attr($id) . '" ' . 'width="560" height="315" frameborder="0" ' . 'style="position:absolute;inset:0;width:100%;height:100%;border:0;" ' . 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" ' . 'allowfullscreen loading="lazy"></iframe>' . '</noscript>'; $replacement .= '</div>'; // 既存の埋め込みをyoutube-wrapごと置換 $wrapped = '~<div[^>]*class=["\'][^"\']*youtube-wrap[^"\']*["\'][^>]*>\s*'.preg_quote($m[0], '~').'\s*</div>~i'; if (preg_match($wrapped, $content)) { $content = preg_replace($wrapped, $replacement, $content, 1); } else { $content = str_replace($m[0], $replacement, $content); } } return $content; }, 9); // JS読込 プレースホルダ/YouTube痕跡があるページだけ enqueue add_action('wp_enqueue_scripts', function () { if (is_admin() || wp_doing_ajax() || is_feed()) return; if (!is_singular()) return; global $post; if (!$post) return; // 置換前の本文に痕跡があるか(URL or iframe) $has_youtube = (strpos($post->post_content, 'youtube.com') !== false) || (strpos($post->post_content, 'youtu.be') !== false) || (strpos($post->post_content, '<iframe') !== false); if (!$has_youtube) return; wp_enqueue_script( 'youtube-lite-external', get_stylesheet_directory_uri() . '/js/youtube-lite.js', [], '1.0.0', true // footer ); }, 20); // defer付与 add_filter('script_loader_tag', function ($tag, $handle) { if ($handle === 'youtube-lite-external' && strpos($tag, 'defer') === false) { $tag = str_replace(' src=', ' defer src=', $tag); } return $tag; }, 10, 2); // ---------------------------------- // YouTube動画タイトルとチャンネル名を取得してキャッシュ(30日) function get_youtube_meta($video_id){ $key = 'yt_meta_' . $video_id; $cached = get_site_transient($key); if ($cached !== false) return $cached; $url = 'https://www.youtube.com/oembed?url=' . rawurlencode('https://www.youtube.com/watch?v=' . $video_id) . '&format=json'; $res = wp_remote_get($url, ['timeout' => 2, 'redirection' => 2]); if (is_wp_error($res)) return ['title'=>'','author'=>'']; $code = wp_remote_retrieve_response_code($res); if ($code !== 200) return ['title'=>'','author'=>'']; $body = json_decode(wp_remote_retrieve_body($res), true); $meta = [ 'title' => $body['title'] ?? '', 'author' => $body['author_name'] ?? '' ]; set_site_transient($key, $meta, 30 * DAY_IN_SECONDS); return $meta; } |
youtube-lite.js
子テーマにjsディレクトリを作ってそこに突っ込んでください。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// クリックされた要素から .yt-lite を遡って取得 document.addEventListener('click', function (e) { var box = e.target.closest('.yt-lite'); if (!box) return; var a = e.target.closest('a'); if (a) e.preventDefault(); if (box.classList.contains('loaded')) return; box.classList.add('loading'); // loading中のサムネ効果 var thumb = box.querySelector('img.yt-thumb'); if (thumb) thumb.style.filter = 'blur(2px) brightness(.7)'; var id = box.getAttribute('data-ytid'); if (!id) return; var ifr = document.createElement('iframe'); ifr.src = 'https://www.youtube-nocookie.com/embed/' + id + '?autoplay=1&rel=0'; ifr.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'; ifr.allowFullscreen = true; ifr.setAttribute('title', 'YouTube video'); ifr.style.position = 'absolute'; ifr.style.inset = '0'; ifr.style.width = '100%'; ifr.style.height = '100%'; ifr.style.border = '0'; ifr.style.zIndex = '4'; // iframeの読み込み完了でフィルタ解除&loadingクラス除去 ifr.addEventListener('load', function () { if (thumb) thumb.style.filter = ''; box.classList.remove('loading'); }, { once: true }); box.appendChild(ifr); box.classList.add('loaded'); }, { passive: true }); |
おわりに
特にオチもないので、よければ1曲聴きながら実際にどういうコードになるのか見てってください。
▼投稿画面の入力

▼実際に表示される内容
|
1 2 3 4 5 6 7 8 |
<h2><span class="ez-toc-section" id="%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB"></span>おわりに<span class="ez-toc-section-end"></span></h2> <p>特にオチもないので、よければ1曲聴きながら実際にどういうコードになるのか見てってください。</p> <div class="yt-lite" data-ytid="FExBwfQHXlE" role="button" style="position:relative;background:#000;display:block;overflow:hidden;cursor:pointer;aspect-ratio:16/9;border-radius:6px;" aria-label="Feels So Good|Chuck Mangione - Topic"><img class="yt-thumb" src="https://i.ytimg.com/vi/FExBwfQHXlE/hqdefault.jpg" alt="Feels So Good|Chuck Mangione - Topic|YouTube" loading="lazy" decoding="async" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:brightness(.9);z-index:1;display:block;"></p> <div class="yt-meta" style="position:absolute;top:8px;left:8px;z-index:2;color:#fff;font-size:.8rem;line-height:1.4;max-width:90%;text-shadow:0 0 4px rgba(0,0,0,.8);pointer-events:none;"> <div class="yt-title" style="font-weight:700;margin-bottom:2px;">Feels So Good</div> <div class="yt-author" style="font-size:.75rem;opacity:.85;">Chuck Mangione – Topic</div> </div> <p><button class="yt-play" aria-label="Feels So Good|Chuck Mangione - Topic を再生" style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:68px;height:48px;border:0;border-radius:14px;background:rgba(0,0,0,.6);cursor:pointer;z-index:3;"><span aria-hidden="true" style="display:block;width:0;height:0;margin:0 auto;border-style:solid;border-width:10px 0 10px 16px;border-color:transparent transparent transparent #fff;transform:translateX(2px);"></span></button><noscript><iframe src="https://www.youtube-nocookie.com/embed/FExBwfQHXlE" width="560" height="315" frameborder="0" style="position:absolute;inset:0;width:100%;height:100%;border:0;" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy"></iframe></noscript></div> |
コメント
トラックバックは利用できません。
コメント (0)










この記事へのコメントはありません。