GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

youtube 埋め込みコード UI 9選 — SNSカードもコピペOK

「youtube 埋め込みコード」の検索結果はiframeタグの取得方法ばかりですが、この記事はその外側のUI計算を9種示します。フレーム幅に合わせて縦の比率が求め直され、表示されてからの経過時間が一定値を超えると軽量カードが自動で完全なフレームに切り替わり、SNS埋め込みのバッジ数字が経過時間に応じて少しずつ増えてい

自動再生 · タイルを押すとその項目へ · すべて1つのzip

デモ

9項目とも「見た目の演出ではなく、幅の値・経過時間・明るさの値・余った幅のサイズ・間隔の回数・角度・押された瞬間のサイズ差という値を毎フレーム求め直して、フレームの高さ・カードの状態・バッジの光と大きさ・背景のぼかし強度・カウンターの数字・グラデーションの角度・カードの高さに反映する」という同じ前提を共有しています。実測値は次のとおりです。

番号 項目 累積面積 強度平均 動いたフレーム
01 YouTubeレスポンシブ埋め込み 26.864% 84.4 23/23
02 YouTubeライトファサード 7.068% 100.6 23/23
03 YouTubeプライバシー強化 1.876% 59.5 23/23
04 縦型ショート埋め込み 25.935% 82.9 23/23
05 TikTok埋め込みモック 13.598% 42.6 23/23
06 Instagram投稿カード 18.658% 25.4 23/23
07 X投稿フレーム 1.878% 59.4 23/23
08 クリックで読み込む 31.389% 70.0 22/23
09 同意プレースホルダー 37.148% 128.4 19/23

「youtube 埋め込み レスポンシブ」を探す人が確かめたいのはまさにこの01の動きで、カードの幅がsin波で変わるたびに高さが求め直され、その実測px値が画面に表示されます。09同意プレースホルダーが累積面積37.148%で最も広く、08クリックで読み込む31.389%・01レスポンシブ26.864%・04縦型25.935%・06Instagram18.658%・05TikTok13.598%・07X投稿1.878%・03プライバシーバッジ1.876%の順です(02ライトファサードは7.068%)。24フレームキャプチャ(デルタ23個)のうち動いたフレームは19(09)〜23(01〜07)で、08は22です。08・09だけが残りより少なく動くのは、実際のリスナーで状態が切り替わって初めてアニメーションが始まる状態遷移型だからで、切り替わる前の静止区間は自然な結果です。03プライバシーバッジと07X投稿の累積面積がそれぞれ1.876%・1.878%と9項目中もっとも小さいのも、バッジの脈動や枠線の明るさ変化のように画面上で実際に占めるピクセルの場所自体が小さい要素だからで、欠陥ではありません。強度平均は25.4(06、最も低い)〜128.4(09、最も高い)の間にあり、9項目とも画面が動き続ける下限値を余裕を持って満たしています。

埋め込みUIの解剖 — 9つの画面が計算するもの

「youtube 埋め込み コード 取得」で検索する人の多くは実際のiframeタグの取得手順を求めていますが、この記事が扱うのはその外側にあるUIの計算層です。9つの画面は、幅・経過時間・明るさ・余白・間隔・角度・サイズ差という数値を毎フレーム読み直し、フレームの寸法やバッジの光り方、背景のぼかし具合に変換し直す設計を共有しています。

何が入力で何が出力か

入力はカードの幅の値、表示されてからの経過時間、明るさを表す正弦波の値、画面幅から引いた余りの余白サイズ、固定間隔で割った経過時間、回転角度、押された・同意した瞬間の高さの差という7種類です。出力はDOMに直接書き込むインラインスタイル(幅・高さ・opacity・transform・filter)か、dataset・aria-*属性のどちらかで、キーフレームアニメーション自体は使いません。

01〜07は自動再生、08・09だけに実際のリスナー

9項目のうち01〜07はrequestAnimationFrameの固定周期による自動再生だけで実演されており、実際のクリック・ドラッグ・ポインターのリスナーは一つも付いていません。08(クリックで読み込む)と09(同意プレースホルダー)の2項目だけに実際のリスナー(08はclickとkeydown、09はclick)が付いていて、押された・同意した瞬間を起点に同じ計算が動き出します。

なぜ計算を一枚の層にまとめるのか

FACADE_DELAYやBG_BLUR_PXのような定数をあちこちに散らすと、1つの値を変えるたびに複数箇所を直す必要が出てきます。applyという一つの関数に計算をまとめておけば、定数を差し替えるだけで画面全体のつじつまが揃います。自分の埋め込みUIを実装するときも、同じように計算をひとつの層に集めておくと後から値を調整しやすくなります。

画面別コード

「sns 埋め込み デザイン」で探すと装飾だけを整えた静止画像が多く出てきますが、この9項目はその装飾の手前に計算の層を1枚重ねます。項目名にあるYouTube・TikTok・Instagram・Xという表記はいずれも見た目の系統を指す説明的な参照で、各社の実際の仕様やAPIバージョンを指すものではありません。実際のiframe src・SNSのSDKスクリプト・外部URLの呼び出しはどこにもなく、動画・プロフィール画像の場所はキャンバスに描いた固定シードのサムネイルと仮のチャンネル名(Channel A)で代用しています。似た計算の考え方は、動画ファイルなしで動くカスタム動画プレーヤーUI9種の再生バー計算にも共通しています。

01YouTubeレスポンシブ埋め込み — 幅に合わせて比率を求め直す動画フレーム

カードの幅をsin波で求め直してフレームに適用すると、CSSのaspect-ratio(16:9)が高さを自動で求め直します。getBoundingClientRectで実測した高さを、計算された幅と一緒に画面へ表示します。

YouTubeレスポンシブ埋め込み — 幅に合わせて比率を求め直す動画フレーム
新しいタブで開く

01 · YouTubeレスポンシブ埋め込み — 幅に合わせて比率を求め直す動画フレーム — 計測 累積面積 26.864% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aspect-ratiogetBoundingClientRectrequestAnimationFrame

getBoundingClientRectがaspect-ratioの実測結果を読み直す

applyはBASE200px(狭い画面では140px)を中心にRANGE56px(狭い画面では36px)の振れ幅でCYCLE_MS2000msの周期の幅を求め直し、その幅をframeElに適用した直後にgetBoundingClientRectで実測した高さを読み直します。aspect-ratioはMDNのaspect-ratioの仕様どおり幅から高さを自動計算しますが、その計算結果をJavaScript側で確かめるには実測が必要です。

  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  var narrow = window.matchMedia('(max-width: 340px)').matches;
  var CYCLE_MS = 2000;
  var BASE = narrow ? 140 : 200;
  var RANGE = narrow ? 36 : 56;

  function apply(t) {
    var w = BASE + RANGE * Math.sin(2 * Math.PI * t / CYCLE_MS);
    frameEl.style.width = w.toFixed(1) + 'px';
    var rect = frameEl.getBoundingClientRect();
    readoutEl.textContent = '16:9 · ' + Math.round(w) + ' × ' + Math.round(rect.height) + 'px';
  }

  apply(0);

  if (reduce) return;

  var start = null;
  function loop(ts) {
    if (start === null) start = ts;
    var t = (ts - start) % CYCLE_MS;
    apply(t);
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);

02YouTubeライトファサード — 経過時間を計り完全なフレームへ切り替わる埋め込み

カードが表示されてから経過した時間をrequestAnimationFrameで求め直し続け、その値がFACADE_DELAY1200msを超えた瞬間だけサムネイルカードを再生フレーム状態へ切り替えます。閾値の前は状態を変えません。

YouTubeライトファサード — 経過時間を計り完全なフレームへ切り替わる埋め込み
新しいタブで開く

02 · YouTubeライトファサード — 経過時間を計り完全なフレームへ切り替わる埋め込み — 計測 累積面積 7.068% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

datasetaria-livematchMedia

FACADE_DELAYとisFrameが軽量表示から再生フレームへの切り替えを決める

applyはt >= FACADE_DELAYの結果をisFrameという真偽値にし、それをcardEl.dataset.stateへ書き込むことでCSS側の見た目を切り替え、切り替わった後のprogressをTOTAL_SEC12秒の範囲で求め直して再生時間の表示に使います。

  function apply(t) {
    var isFrame = t >= FACADE_DELAY;
    cardEl.dataset.state = isFrame ? 'frame' : 'lite';
    var progress = isFrame ? (t - FACADE_DELAY) / (CYCLE_MS - FACADE_DELAY) : 0;
    progressFillEl.style.transform = 'scaleX(' + progress.toFixed(4) + ')';
    var sec = Math.floor(progress * TOTAL_SEC);
    timeLabelEl.textContent = '0:' + String(sec).padStart(2, '0') + ' / 0:12';
    readoutEl.textContent = '経過 ' + (t / 1000).toFixed(2) + '秒 · 切り替え ' + transitionSec + '秒';
  }

  apply(0);
  if (reduce) return;

  var start = null;
  function loop(ts) {
    if (start === null) start = ts;
    var t = (ts - start) % CYCLE_MS;
    apply(t);
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);

03YouTubeプライバシー強化埋め込み — 明るさを周期的に求め直し脈打つバッジ

フレーム隅の鍵バッジの明るさの値を700ms周期の正弦波で求め直し、その値の上下をバッジの光の不透明度・バッジ自体の大きさに一緒に反映して脈打たせます。

YouTubeプライバシー強化埋め込み — 明るさを周期的に求め直し脈打つバッジ
新しいタブで開く

03 · YouTubeプライバシー強化埋め込み — 明るさを周期的に求め直し脈打つバッジ — 計測 累積面積 1.876% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

aria-liverequestAnimationFramematchMedia

700ms周期のpulseがバッジの不透明度と大きさを同時に動かす

applyはMath.sinで求めたpulseという1つの数値を、光の不透明度0.1+0.45*pulseとバッジのscale``1+0.04*pulseという2つのCSS値に同時に反映し、両者が同じ位相でずれずに動くようにしています。

  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  var CYCLE_MS = 2000;

  function apply(t) {
    var pulse = 0.5 + 0.5 * Math.sin(2 * Math.PI * t / 700);
    badgeGlowEl.style.opacity = (0.1 + 0.45 * pulse).toFixed(3);
    badgeEl.style.transform = 'scale(' + (1 + 0.04 * pulse).toFixed(4) + ')';
    readoutEl.textContent = '明るさ ' + Math.round(pulse * 100) + '%';
  }

  apply(0);
  if (reduce) return;

  var start = null;
  function loop(ts) {
    if (start === null) start = ts;
    var t = (ts - start) % CYCLE_MS;
    apply(t);
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);

04縦型ショート動画埋め込み — 余った幅の分だけ背景のぼかしを求め直すフレーム

画面幅から縦型フレームの幅を引いた余りの余白サイズを計算し、その値を背景のぼかし強度(px)へ求め直してマッピングします。フレーム自体の幅もsin波で求め直します。

縦型ショート動画埋め込み — 余った幅の分だけ背景のぼかしを求め直すフレーム
新しいタブで開く

04 · 縦型ショート動画埋め込み — 余った幅の分だけ背景のぼかしを求め直すフレーム — 計測 累積面積 25.935% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

backdrop-filteraspect-ratiowill-change

leftoverの計算がBG_BLUR_PXを背景のぼかし強度に変える

applyはscssのカスタムプロパティから読み直したBASE_FW・AMP_FWでフレーム幅fwを求め、stageEl.clientWidthからその実測幅を引いたleftoverを、BG_BLUR_PX8pxを基準にしたblurという1つの値へ変換し直します。

  var CYCLE_MS = 2000;
  var BG_BLUR_PX = 8;
  // 幅の基準値・振幅はscssのカスタムプロパティからフレームごとに読み直す — 340pxのメディアクエリが値を下げるが、
  // 開始時の幅で一度だけ取得すると、ビューポートが縮んでも大きい値が残り縦横比の検証が崩れる(04実測2026-09-19: 134×238px → 289px)。
  var frameWrapEl = frameEl.parentElement;

  function apply(t) {
    var frameWrapStyle = getComputedStyle(frameWrapEl);
    var BASE_FW = parseFloat(frameWrapStyle.getPropertyValue('--base-fw')) || 118;
    var AMP_FW = parseFloat(frameWrapStyle.getPropertyValue('--amp-fw')) || 16;
    var fw = BASE_FW + AMP_FW * Math.sin(2 * Math.PI * t / CYCLE_MS);
    frameEl.style.width = fw.toFixed(1) + 'px';
    // 計算層: 余ったステージ幅 → 背景のぼかし強度。フレームが大きくなるほど背後の背景はぼかしが弱くなる。
    var leftover = stageEl.clientWidth - frameEl.getBoundingClientRect().width;
    var blur = BG_BLUR_PX + leftover / 14;
    bgEl.style.filter = 'blur(' + blur.toFixed(1) + 'px)';
    readoutEl.textContent = '余白 ' + Math.round(leftover) + 'px · ぼかし ' + blur.toFixed(1) + 'px';
  }

  if (reduce) {
    apply(0);
    return;
  }

05TikTok埋め込みモック — 経過時間の分だけいいね数を求め直すカード

カードが表示されてから経過した時間を固定間隔(LIKE_INTERVAL)で割り、その間隔を過ぎるたびにいいね数をLIKE_STEPだけ足した値を求め直して表示します。

TikTok埋め込みモック — 経過時間の分だけいいね数を求め直すカード
新しいタブで開く

05 · TikTok埋め込みモック — 経過時間の分だけいいね数を求め直すカード — 計測 累積面積 13.598% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

writing-modeobject-fitwhite-space

LIKE_INTERVALで割った商がいいね数を階段状に増やす

applyはMath.floor(t / LIKE_INTERVAL)という商をLIKE_STEP3倍してLIKE_BASE1247に足すことで、連続したトゥイーンではなく400ms間隔で3ずつ増える階段状のカウンターを作ります。

  var CYCLE_MS = 2000;
  var LIKE_INTERVAL = 400;
  var LIKE_STEP = 3;
  var LIKE_BASE = 1247;

  function apply(t) {
    // 計算層: 間隔ごとにいいね数を求め直す — トゥイーンではなく階段状のカウンター
    var likes = LIKE_BASE + Math.floor(t / LIKE_INTERVAL) * LIKE_STEP;
    var since = t % LIKE_INTERVAL;
    var pop = since < 180 ? 1 + 0.28 * (1 - since / 180) : 1;
    heartWrapEl.style.transform = 'scale(' + pop.toFixed(3) + ')';
    likeCountEl.textContent = likes.toLocaleString('en-US');
    readoutEl.textContent = '間隔 ' + (LIKE_INTERVAL / 1000).toFixed(1) + '秒 · いいね ' + likes.toLocaleString('en-US');

    // ダブルタップハート — 同じ間隔の中で0→1へ大きくなりながら薄くなる(サムネイル上の大きい層)
    var prog = since / LIKE_INTERVAL;
    var burstScale = 0.3 + prog * 2.3;
    var burstOpacity = Math.max(0, 0.85 * (1 - prog * 1.15));
    burstEl.style.transform = 'translate(-50%, -50%) scale(' + burstScale.toFixed(3) + ')';
    burstEl.style.opacity = burstOpacity.toFixed(3);

    // いいねが増える瞬間、サムネイル全体が短く明るくなる(フラッシュ) — ハート層と同じsinceを共有
    var flashOpacity = since < 160 ? 0.4 * (1 - since / 160) : 0;
    flashEl.style.opacity = flashOpacity.toFixed(3);
  }

06Instagram投稿埋め込みカード — 角度を求め直し続ける読み込みグラデーション

画像の場所のグラデーション角度(angle)を経過時間に比例して求め直し続け、読み込みが終わっていないかのように滑らかに流れるハイライトを一緒に描きます。

Instagram投稿埋め込みカード — 角度を求め直し続ける読み込みグラデーション
新しいタブで開く

06 · Instagram投稿埋め込みカード — 角度を求め直し続ける読み込みグラデーション — 計測 累積面積 18.658% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

linear-gradientsetPropertyborder-radius

angleとsweepが読み込み中グラデーションとハイライトを同時に回す

applyはt / CYCLE_MSという経過時間の比率をangleという0〜360度の値に変換して--spinへ注入し、同じ比率から求めたsweepでハイライトの横位置も一緒にずらします。

  var readoutEl = document.getElementById('readout');
  if (!avatarEl || !slotEl || !shimmerEl || !readoutEl) return;
  var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  drawSeedThumb(avatarEl, 106);

  var CYCLE_MS = 2000;

  function apply(t) {
    // 計算層: 経過時間の比率 → グラデーションの回転角(0~360度)
    var angle = Math.floor((t / CYCLE_MS) * 360);
    slotEl.style.setProperty('--spin', angle + 'deg');

    var prog = t / CYCLE_MS;
    var sweep = -30 + prog * 160;
    shimmerEl.style.transform = 'translateX(' + sweep.toFixed(1) + '%)';

    readoutEl.textContent = '角度 ' + angle + '°';
  }

  if (reduce) {
    apply(0);
    return;
  }

07X投稿埋め込みフレーム — 枠線の明るさを周期的に求め直し光るカード

カードの枠線の明るさの値を900ms周期の正弦波で求め直し、その値の上下の分だけ枠線がほのかに光っては落ち着くことを繰り返します。

X投稿埋め込みフレーム — 枠線の明るさを周期的に求め直し光るカード
新しいタブで開く

07 · X投稿埋め込みフレーム — 枠線の明るさを周期的に求め直し光るカード — 計測 累積面積 1.878% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFramesetPropertyaria-live

GLOW_MSの正弦波がCSS変数--glowへ枠線の明るさを注入する

applyはGLOW_MS900ms周期のMath.sinでglowを求め、それをcardElの--glowというCSS変数にsetPropertyで注入するだけで、枠線の光り方の計算とCSS側の描画を分けています。

  drawSeedThumb(avatarEl, 107);

  // 計算層: 枠線の明るさをサイン波で毎フレーム再計算し--glowへ注入、opacityで脈動させる
  function apply(t) {
    var glow = 0.5 + 0.5 * Math.sin(2 * Math.PI * t / GLOW_MS);
    cardEl.style.setProperty('--glow', glow.toFixed(3));
    glowValueEl.textContent = Math.round(glow * 100) + '%';
  }

  if (reduce) {
    apply(0);
    return;
  }

  var start = null;
  function loop(ts) {
    if (start === null) start = ts;
    var t = (ts - start) % CYCLE_MS;
    apply(t);
    requestAnimationFrame(loop);
  }

08クリックで読み込む埋め込み — サイズ差を計算し滑らかに広がるプレースホルダー

実際のクリックまたはキーボード(Enter/Space)入力を受け取り、押された状態に切り替わった瞬間のプレースホルダーの高さと実際のフレームの高さ(FRAME_H168px、狭い画面128px)の差を計算し、EXPAND_MS500msの間で補間してカードを広げます。

クリックで読み込む埋め込み — サイズ差を計算し滑らかに広がるプレースホルダー
新しいタブで開く

08 · クリックで読み込む埋め込み — サイズ差を計算し滑らかに広がるプレースホルダー — 計測 累積面積 31.389% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFrameoffsetHeightaria-pressed

deltaとeaseOutCubicがプレースホルダーの高さをFRAME_Hへ補間する

applyはPRESS_AT900msを過ぎるか実際のクリックで押された瞬間の実測高さphHを基準に、FRAME_Hとの差deltaをeaseOutCubicで滑らかに補間し、途中から実フレームをフェードで重ねます。

  // 計算層: プレースホルダーの実測高さ(phH)からFRAME_HまでeaseOutCubicで補間し
  // カード高さを計算する。半分を超えると実フレームがフェードで現れ、
  // 展開が終わった後は残り時間の間プログレスバーが満ちていく。
  function apply(t) {
    var h;
    var hintOpacity = 0.86 + 0.14 * Math.sin(2 * Math.PI * t / HINT_MS);
    if (!pressed) {
      h = placeholderEl.offsetHeight;
      cardEl.style.height = h + 'px';
      hintArrowEl.style.opacity = hintOpacity.toFixed(3);
    } else {
      var elapsed = t - pressTime;
      if (elapsed < 0) elapsed += CYCLE_MS;
      var delta = FRAME_H - phH;
      var p = easeOutCubic(Math.min(1, elapsed / EXPAND_MS));
      h = phH + delta * p;
      cardEl.style.height = h.toFixed(1) + 'px';
      var frameOpacity = p <= 0.55 ? 0 : Math.min(1, (p - 0.55) / 0.45);
      frameEl.style.opacity = frameOpacity.toFixed(3);
      var sweepSpan = CYCLE_MS - PRESS_AT - EXPAND_MS;
      var sweepP = Math.max(0, Math.min(1, (elapsed - EXPAND_MS) / sweepSpan));
      progressBarEl.style.transform = 'scaleX(' + sweepP.toFixed(3) + ')';
    }
    heightValueEl.textContent = '高さ ' + Math.round(h) + 'px';
  }

09同意前埋め込みプレースホルダー — 案内文の高さを計算して折りたたむカード

実際のクリック入力で同意が示された瞬間の案内文欄の高さ(noticeH)を計算し、CONSENT_FOLD_MS200msの間で案内欄を折りたたみ、同じ高さだけ下のフレームを広げます。

同意前埋め込みプレースホルダー — 案内文の高さを計算して折りたたむカード
新しいタブで開く

09 · 同意前埋め込みプレースホルダー — 案内文の高さを計算して折りたたむカード — 計測 累積面積 37.148% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFrameoffsetHeightaria-expanded

noticeHの実測値がCONSENT_FOLD_MSの間で案内欄を折りたたむ

enterConsentは同意された瞬間にnoticeEl.offsetHeightをnoticeHとして一度だけ実測し、applyはその値をeaseOutCubicの進行度pに掛けて案内欄の高さを減らしながら、FRAME_BASE_H96px(狭い画面56px)を基準にフレームの高さへ同じ分を足します。

  // 計算層: 同意した瞬間の案内ブロックの実測高さ(noticeH)を折りたたみ進行度(p)に合わせ
  // そのままフレーム高さの増加分へ渡す — 案内が折りたたまれる分だけフレームが育つ。
  function apply(t) {
    var dotOpacity = 0.86 + 0.14 * Math.sin(2 * Math.PI * t / DOT_MS);
    if (!consented) {
      statusDotEl.style.opacity = dotOpacity.toFixed(3);
      return;
    }
    var elapsed = t - consentTime;
    if (elapsed < 0) elapsed += CYCLE_MS;
    var p = easeOutCubic(Math.min(1, elapsed / CONSENT_FOLD_MS));
    noticeEl.style.height = (noticeH * (1 - p)).toFixed(1) + 'px';
    frameEl.style.height = (FRAME_BASE_H + noticeH * p).toFixed(1) + 'px';
    var sweepSpan = CYCLE_MS - CONSENT_AT - CONSENT_FOLD_MS;
    var sweepP = Math.max(0, Math.min(1, (elapsed - CONSENT_FOLD_MS) / sweepSpan));
    progressBarEl.style.transform = 'scaleX(' + sweepP.toFixed(3) + ')';
  }

どこに置くか — ブログ本文と商品詳細ページ

ブログ記事の本文に動画を1本挟むだけなら、01のレスポンシブ埋め込みをそのまま使うことが多い。本文の幅が画面サイズで変わる構成では、フレーム自身が幅の変化のたびに高さを求め直さないと、狭い画面で映像が切れて見える。

ECサイトの商品詳細ページでレビュー欄の横に縦型動画(04)を置く場合は事情が違う。商品画像ギャラリーと横幅を分け合うため余った幅が画面ごとに変わり、その余った幅を背景のぼかし強度に変える計算がないと、縦型フレームだけが余白に浮いて見えてしまう。

同意バナーを先に出す方針のチームなら、09の同意前プレースホルダーをそのバナーのロジックにそのままつなげておけば、訪問者が同意ボタンを押すまで実際の埋め込みは画面に現れない。

低速回線や同意の取り消しでどこが壊れるか

低速回線でもライトファサードの切り替えタイミングは変わらない

02ライトファサードのFACADE_DELAYは表示されてからの経過時間だけを基準にしており、実際のネットワーク速度や本物の動画の読み込み完了は計算に入っていません。低速回線で実際の再生開始が遅れる場面を再現したい場合、この計算層だけでは足りず、実測の読み込み完了イベントを別に待つ処理が必要になります。

連続クリックしてもクリックロードの状態は一度しか切り替わらない

08クリックで読み込むのenterPressedは!pressedの条件付きでしか呼ばれないため、押された状態になった後にプレースホルダーを何度クリックしてもphHは上書きされず、広がるアニメーションが最初からやり直されることもありません。ただしCYCLE_MS2000msの周期が一巡してloopのtが0へ戻るときはresetToIdle()が呼ばれ、pressedがfalseへ初期化されて次の周期で同じ展開が最初から実演されます。しきい値を超えた瞬間に状態が切り替わるという考え方は、ドロップゾーンUI9種の状態遷移とも共通しています。

同意を取り消す入力自体がこの実装にはない

09同意プレースホルダーのconsentBtnElにはconsentOverrideをtrueにするクリックリスナーしか付いておらず、同意を取り消す専用の入力はこの9項目のどこにも実装されていません。ただしloopのtが周期の先頭へ戻るときはresetToIdle()が呼ばれ、consentedがfalseへ初期化されて案内欄がもう一度現れます。これは周期の巻き戻しによる自動初期化であって取り消し操作ではないため、実際の同意管理UIで一度同意した状態を維持するには、この初期化を飛ばす処理を別に用意する必要があります。

数値が画面を変える場所 — 比率・経過時間・明るさ

幅の比率がフレームの高さを動かす場所

01レスポンシブ埋め込みでは、フレーム幅という1つの数値がaspect-ratioによって高さという別の数値へ自動で計算し直され、その実測結果をgetBoundingClientRectがもう一度読み直して画面に表示します。幅を直接高さに書き込むのではなく、ブラウザの計算結果を確かめ直すという1段階を挟んでいる点がポイントです。

経過時間の閾値が状態を切り替える場所

02ライトファサードと08クリックで読み込むでは、経過時間という連続した数値がFACADE_DELAYやPRESS_ATという閾値をまたいだ瞬間だけ、カードの状態という離散した値を1回だけ切り替えます。連続した数値の変化が、状態という不連続な値を動かすという関係です。

余白のサイズが背景のぼかしを動かす場所

04縦型ショート動画埋め込みでは、画面幅からフレーム幅を引いたleftoverという1つの数値が、BG_BLUR_PXを基準にしたblurというCSSのぼかし強度へ求め直されます。余白が大きいほどぼかしが強くなるという、片方向の対応関係です。

3つのバリエーションとreduced-motionの扱い

紙のポスターに9枚の見本を並べるのと違い、この9種は同じ見た目のまま定数を1つ変えるだけで体感の軽さが変わります。9種の埋め込みUIはすべてphy8esssで開くzipにまとめられており、vanillaフォルダには9項目それぞれのindex.htmlが、reactフォルダには同じ計算をコンポーネント化したtsxファイルが入っています。

名前 値 感じ方
ライトファサードの切り替え遅延 02 FACADE_DELAY 1200ms → 3000ms 遅延を1200msに短くすると軽量カードから実フレームのような見た目へ切り替わるのが速すぎて効果を体感しづらく、3000msに延ばすと偽のサムネイル状態が長く保たれ読み込みが遅く見えます
同意オーバーレイの折りたたみ時間 09 CONSENT_FOLD_MS 200ms → 500ms 200msに短くすると案内文が消えた瞬間にフレームが現れ2つの状態がぶつ切りに見え、500msに延ばすと折りたたむ過程自体が目に入り切り替えが滑らかに感じられます
縦型フレーム背景のぼかし強度 04 BG_BLUR_PX 8px → 24px 8pxと弱くすると左右余白の背景がくっきり見えて縦型カードが浮いて見え、24pxと強く上げると背景がほぼ潰れて縦型フレームに視線が自然と集まります

prefers-reduced-motionをどう扱うか

9項目ともwindow.matchMedia('(prefers-reduced-motion: reduce)')の結果をreduceという変数に持ち、真であればapply(0)を1回呼ぶだけで自動再生のrAFループ自体は回しません。08・09は元々実際のリスナーで動く状態遷移型なので、reduceが真でも押された・同意した瞬間の計算そのものは消えず、繰り返しの脈動や周期的な再計算だけが止まります。

コピー&ペーストで動く理由

9項目とも定数・関数・クラス名を1つのindex.htmlの中だけで完結させ、外部のライブラリやCDNは一切呼び出しません。フォルダを開いてブラウザに読み込ませるだけで、追加のビルド手順なしに同じ計算がそのまま動き出します。自分の埋め込みUIを実装する際も、名前付き定数の値だけ差し替えれば同じ計算の層をそのまま流用できます。

埋め込みを組み込む前によくある質問

この9種は実際のYouTube・SNSと通信していますか

いいえ。9項目とも実際のiframe src・SNSのSDKスクリプト・外部URLの呼び出しはどこにもなく、動画・プロフィール画像の場所はキャンバスに描いた固定シードのサムネイルと仮のチャンネル名(Channel A)で代用しています。クライアント側の計算だけで画面が動きます。

「sns 埋め込み html」のような実装をこの9種の中でそのまま使えますか

この9種はSNSの実際の埋め込みhtml・SDKを再現したものではなく、幅・経過時間・明るさといった値がUIをどう動かすかを学ぶための計算シミュレーションです。html・css・jsの3つのタブに分けたコードをそのまま自分のページに貼り付けて、見た目の計算だけを流用する使い方に向いています。

08・09にリスナーがあるのに、なぜ01〜07には付いていませんか

01〜07は幅の変化や経過時間、明るさの周期といった「時間が経てば自動で変わる」性質を実演するための項目で、requestAnimationFrameの固定周期だけで動きを見せる設計です。08・09は逆に「実際に押された・同意した」という利用者の操作を起点に状態が変わる項目なので、その起点を再現するために実際のリスナーが必要でした。

もっと読む

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。