ぽっぺんしゃんにょろりんこ

匿名・非追跡型アダルト動画検索エンジンの設計ノート

PHPとjQueryで作る、動画サムネから動画詳細(メインエリア)を非同期で表示するUIの話

こんにちは、にょろりんこの備忘録技術ブログです。

Webサイトで「一覧ページのサムネイルをクリックしたら、詳細情報がその場で表示される」──そんな直感的なUIを見たことがあると思います。ページ遷移なしに内容が切り替わるこの仕組み、実は PHPjQueryだけでも十分実現可能です。

本記事では、あなたを追跡しないアダルト動画検索エンジン「SaePorns」で実装している構成をもとに、クリックされた動画IDをもとに非同期で詳細情報を取得し、メイン表示エリアを更新するまでの一連の流れを解説します。

窓際に座るティアラ

ReactやVueのようなフレームワークを使わなくても、軽量なSPA的UIは十分に作れる、それを体感してもらえる内容です。

初心者~中級者の方にもわかるよう、JavaScriptjQuery)とPHPの連携部分を丁寧に分解していきます。

まずは、サムネイル動画にIDを持たせる

// video-list.js
container.append(`
<div class="sub-contents">
  <a href="${link}" class="thumbnail-link" data-id="${ud.ID}">
    <div class="sub-img-container">
      <img class="sub-img" src="/proxy.php?image=${encodeURIComponent(ud.image)}&nocache=${Date.now()}">
      <div class="sub-duration">${fmtDur}</div>
    </div>
    <div class="sub-text-container">
      <div class="sub-title">${ud.title}</div>
      <div class="sub-date">${fmtDate}</div>
    </div>
  </a>
</div>
`);

まず、「動画一覧の取得と描画を担当するスクリプト」は「video-list.js」として、他の処理と責任を切り分けています。こうすることで保守性が高まり、将来的な拡張や再利用にも対応しやすくなります。

一覧表示される各動画のサムネイルは、JavaScriptで .sub-container に動的に生成されます。その中で、各 <a> 要素に data-id 属性として動画IDを埋め込んでいます。

この data-id は、後でクリックイベントを使って「どの動画がクリックされたか」を判別するためのキーとなります。また、クリック時にはこのIDをもとに詳細情報を非同期で取得したり、CTR(クリック数)を記録したりと、さまざまな処理の出発点になります。

特にSaePornsのようなSPA型のUIでは、「どの動画が選ばれたのか」を明示的に管理する必要があり、このように data-id を仕込んでおくことで、シンプルかつ堅牢なロジックが組みやすくなります。

CTR周りに関する詳細は以前の記事でも紹介しています。

www.n-rinko.comクリックイベントで data-id を取得する

前章で data-id を各サムネイルに埋め込んだことで、「どの動画がクリックされたか」を識別する準備が整いました。

次に行うのは、JavaScriptでクリックイベントを検知し、対応する data-id を取り出す処理です。

SaePornsでは、以下のように jQuery のイベントデリゲーションを使って、クリックされた .thumbnail-link 要素から data-id を取得しています。

// video-detail.js

$(document).on('click', '.thumbnail-link', function (e) {
  e.preventDefault(); // デフォルトの遷移を止める

  const videoId = $(this).data('id');// ← data-id を取得

  if (videoId) {
    window.app.loadVideo(videoId); // その場で即時詳細表示
  }
});

この処理によって、ユーザーがどのサムネイルをクリックしたのかを即座に検知し、動画IDを取得することができます。これで、クリックされた動画の詳細を非同期で読み込むための準備も整いました。

次は、取得した videoId を使ってサーバーから詳細情報を取得し、メイン表示エリアに描画する処理を見ていきます。

// video-detail.js

window.app.loadVideo = function(videoId) {
  $.ajax({
    url: '/shinphp535.php',
    type: 'POST',
    dataType: 'json',
    data: { ID_v: videoId }
  }).done(function(data) {
    const main = $('.main-container').empty();
    if (!data.length) return;
    const v = data[0];

この loadVideo 関数では、クリックされた動画のIDを受け取り、その詳細情報をサーバーから取得して .main-container に描画する役割を担っています。

まず、$.ajax() を使って /shinphp535.php に対して POST リクエストを送ります。ここでは、事前にクリックイベントから取得した videoId を ID_v という名前で送信しています。サーバー側ではこのIDをもとに、対応する動画のデータをデータベースから取得し、JSON形式で返すようになっています。

リクエストが成功すると、「.done() 」に渡されたコールバック関数が呼ばれます。その中でまず行っているのが、「.empty()」で「.main-container」の内容を一旦空にする処理です。これは、すでに何かしらの詳細表示があった場合に備えて、一度中身をリセットしてから新しい動画情報を挿入するための準備です。

その次に data の中身を確認し、もし要素が1件もなければ return で処理を中断します。

通常は1件だけのデータが返ってくる想定なので、const v = data[0]; とすることで、対象動画の詳細情報(タイトル・サムネイル・URL・再生時間など)を v という変数にまとめて格納しています。

このあと、v の中身をもとに、iframeを使って動画を埋め込むか、画像リンクを表示するかの分岐処理へと進みます。ここまでの流れで、「クリック → データ取得 → 表示の準備」までが非同期で完了する構成となっています。

取得した動画データをHTMLとして描画する

前章で、サーバーから取得した動画の詳細データを v に格納しました。ここからは、いよいよそのデータをもとに、実際のHTMLを生成し、ユーザーに動画詳細を表示する部分に入ります。

ちなみにSaePornsでは、動画が「iframe埋め込み」に対応しているかどうかによって、表示方法を2種類に分けています。

  • v.embedflag が 1 の場合 → iframe で動画を直接埋め込み
  • それ以外の場合 → サムネイル画像と外部リンクを表示

この分岐は、次のようなコードで行われています。

// video-detail.js より抜粋(表示分岐処理)

if (v.embedflag == 1 && v.embed_url) {
  const eurl = v.embed_url.includes('?')
    ? v.embed_url + '&controls=1&playsinline=1'
    : v.embed_url + '?controls=1&playsinline=1';

  main.append(`
<div data-id="${v.id}" class="main-contents">
  <div class="main-image-container">
    <iframe src="${eurl}" frameborder="0" allowfullscreen class="main-iframeElement"
            allow="autoplay; fullscreen; picture-in-picture"></iframe>
  </div>
  <div class="main-text-container">
    <div class="main-title"><a href="${v.url}" target="_blank">${v.title}</a></div>
  </div>
  <div class="main-uploaddate">${fmtDate}</div>
</div>
  `);
} else {
  main.append(`
<div data-id="${v.id}" class="main-contents">
  <div class="main-image-container">
    <a href="${v.url}" target="_blank">
      <img src="/proxy.php?image=${encodeURIComponent(v.image)}&nocache=${Date.now()}"
           alt="${v.title}" class="main-imgElement">
    </a>
  </div>
  <div class="main-text-container">
    <div class="main-title"><a href="${v.url}" target="_blank">${v.title}</a></div>
  </div>
  <div class="main-uploaddate">${fmtDate}</div>
</div>
  `);
}

このように、embedflag が立っていて embed_url がある場合には、動画をiframeで直接その場に埋め込む形で表示します(サムネイルクリックで動画が開始されるYouTubeなどでよく見る仕様)。

一方、それ以外の場合は、画像(サムネイル)とリンクを表示する通常形式になります。

また、main.append(...) によって生成されたHTMLは、.main-container に追加され、ページ遷移なしで詳細情報が表示される仕組みになっています。

この柔軟な表示分岐により、埋め込み可能な動画と、外部サイトにリンクすべき動画とをシームレスに扱えるようになっています。

まとめ

ここまで、動画サムネイルをクリックしたときに、その場で詳細情報を非同期で取得し、即時に表示するまでの一連の流れを見てきました。

  1. 一覧描画を担う video-list.js で data-id を各サムネイルに埋め込み
  2. クリックイベントでその data-id を取得
  3. video-detail.js の loadVideo() 関数を通じて動画の詳細情報を非同期で取得
  4. そして iframe もしくは画像リンクとしてメインエリアに描画

という、非常にシンプルながら実用的な構成です。ReactやVueのようなフレームワークを使わなくても、PHPjQueryだけで軽量なSPA的UIは十分に作れる──それを体感してもらえる構成になっているはずです。

現在のSaePornsでは、ここに構造化データの動的生成(JSON-LD)なども加えて、SEOの強化も行っていますが、今回のテーマはあくまでメインエリアの描画なので、ここは後ほど別記事で解説していく予定です。

ソースを実際に見たい方は、SaePornsの検索結果ページを開いて、F12で動作を追ってみてください。

本記事のコードが実際に動いているプロダクト、あなたを追跡しないアダルト動画の検索エンジン、SaePornsはこちら。※18歳未満の方はご利用いただけません。

sae-porns.org