ファイル一覧からPDFプレビュー(PDF Inline Viewer)— iframeでインライン表示
このコンポーネントについて
ドキュメント管理画面や稟議申請システムでは、ファイル一覧から選択したPDFをその場でプレビューするUIが欠かせません。このページでは、左ペインのファイル一覧をクリックすると右ペインの <iframe> にPDFがインライン表示される実装例を紹介します。ブラウザ組み込みのPDFビューワーを利用するため、外部ライブラリ・CDN不要で完結します。
- 左右2ペインレイアウト — 左側にファイル一覧、右側にPDFプレビューエリアを配置。スマホ幅では縦積みに切り替わる
- クリックでiframe切り替え — ファイル名をクリックすると
<iframe>のsrc属性を差し替えてPDFを表示する - 選択状態のハイライト — 選択中のファイルをCSSで強調表示する
- 未選択時のプレースホルダー — 初期状態に「ファイルを選択してください」を表示する
- CDN不要・バニラJSのみ — ブラウザ標準のPDFレンダリングを利用するため外部依存ゼロ
実装のポイント・注意点
PDFの表示には <iframe src="..."> を使います。ブラウザの組み込みPDFビューワーがレンダリングを担うため、JavaScriptでの描画処理は不要です。ファイルのクリック時に iframe.src を切り替えるだけで動作します。
ただし <iframe> によるPDF表示にはいくつか制約があります。まず、モバイル環境(特にiOS Safari)ではiframe内でPDFが表示されずダウンロードが始まることがあります。スマートフォン向けにはPDFをダウンロードリンクで提供する設計を検討してください。次に、ページ数などのメタ情報はJavaScriptから取得できません。ページ数表示が必要な場合はPDF.jsなどのライブラリが別途必要です。
セキュリティ面では、<iframe> に表示できるのは同一オリジン(同じドメイン)のファイルが基本です。異なるドメインのPDFはCORSの制限で表示できない場合があります。また、src 属性にセットするパスはHTMLにハードコードされた静的値のみを使い、ユーザー入力を直接渡さないよう注意してください。
左ペインの .pdf-item 要素には border-left: 3px solid transparent を通常時から設定しています。選択時に border-left-color を切り替えるだけにすることで、ボーダーの有無によるレイアウトシフト(ガタつき)を防いでいます。
HTML・CSS・バニラJavaScriptのみで実装しており、フレームワーク不要でコピペすぐに動きます。
デモ
ファイル一覧
-
見積書_サンプル.pdf
-
議事録_サンプル.pdf
-
仕様書_サンプル.pdf
ファイルを選択してください
サンプルソース
3つのファイルを同じフォルダに保存し、ブラウザで index.html を開くと動作確認できます。
ファイル名:index.html / style.css / script.js
加えて、同フォルダ内に data/ フォルダを作成し、以下の名前でPDFファイルを配置してください。
data/sample-estimate.pdf / data/sample-minutes.pdf / data/sample-spec.pdf
このサイトで使用しているサンプルPDFは
見積書 /
議事録 /
仕様書
からダウンロードできます。手持ちのPDFでも動作します(HTMLの data-path にファイル名を合わせてください)。
保存時の文字コードは UTF-8 を指定してください(Shift-JISだと日本語が文字化けします)。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PDFプレビュー サンプル</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="pdf-viewer">
<!-- 左ペイン:ファイルリスト -->
<div class="pdf-file-list">
<p class="pdf-list-heading">ファイル一覧</p>
<ul class="pdf-list">
<li class="pdf-item" data-path="./data/sample-estimate.pdf" onclick="selectFile(this)">
<span class="pdf-icon">PDF</span>
<div class="pdf-item-info">
<span class="pdf-item-name">見積書_サンプル.pdf</span>
<span class="pdf-item-meta">4 KB · 2024-01-15</span>
</div>
</li>
<li class="pdf-item" data-path="./data/sample-minutes.pdf" onclick="selectFile(this)">
<span class="pdf-icon">PDF</span>
<div class="pdf-item-info">
<span class="pdf-item-name">議事録_サンプル.pdf</span>
<span class="pdf-item-meta">2 KB · 2024-01-20</span>
</div>
</li>
<li class="pdf-item" data-path="./data/sample-spec.pdf" onclick="selectFile(this)">
<span class="pdf-icon">PDF</span>
<div class="pdf-item-info">
<span class="pdf-item-name">仕様書_サンプル.pdf</span>
<span class="pdf-item-meta">2 KB · 2024-01-25</span>
</div>
</li>
</ul>
</div>
<!-- 右ペイン:プレビューエリア -->
<div class="pdf-preview-area">
<div class="pdf-placeholder" id="pdfPlaceholder">
<p>ファイルを選択してください</p>
</div>
<iframe class="pdf-frame" id="pdfFrame" title="PDFプレビュー"></iframe>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
:root {
--pdf-accent: #2B7FE8;
--pdf-border: #D0D7E0;
--pdf-text: #1A2332;
--pdf-text-muted: #8898A4;
--pdf-bg-hover: #F4F6F9;
--pdf-bg-active: #EBF3FD;
--pdf-bg-preview: #F8F9FA;
}
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: sans-serif;
padding: 24px;
background: #f0f2f5;
color: var(--pdf-text);
}
/* ===== 2ペインレイアウト ===== */
.pdf-viewer {
display: flex;
height: 560px;
border: 1px solid var(--pdf-border);
border-radius: 8px;
overflow: hidden;
background: #fff;
max-width: 900px;
margin: 0 auto;
}
/* ===== 左ペイン:ファイルリスト ===== */
.pdf-file-list {
width: 256px;
flex-shrink: 0;
border-right: 1px solid var(--pdf-border);
overflow-y: auto;
}
.pdf-list-heading {
margin: 0;
padding: 10px 14px;
font-size: 11px;
font-weight: 700;
color: var(--pdf-text-muted);
background: #FAFBFC;
border-bottom: 1px solid var(--pdf-border);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.pdf-list {
list-style: none;
margin: 0;
padding: 0;
}
.pdf-item {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
cursor: pointer;
border-bottom: 1px solid #EDF0F4;
border-left: 3px solid transparent; /* ガタつき防止:常に3px分確保 */
transition: background 0.12s;
}
.pdf-item:hover { background: var(--pdf-bg-hover); }
.pdf-item.is-active {
background: var(--pdf-bg-active);
border-left-color: var(--pdf-accent);
}
/* PDFバッジ */
.pdf-icon {
flex-shrink: 0;
background: #E53E3E;
color: #fff;
font-size: 9px;
font-weight: 700;
padding: 2px 5px;
border-radius: 3px;
letter-spacing: 0.02em;
line-height: 1.4;
}
.pdf-item-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.pdf-item-name {
font-size: 13px;
font-weight: 500;
color: var(--pdf-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pdf-item-meta {
font-size: 11px;
color: var(--pdf-text-muted);
}
/* ===== 右ペイン:プレビューエリア ===== */
.pdf-preview-area {
flex: 1;
position: relative;
background: var(--pdf-bg-preview);
min-width: 0;
}
.pdf-placeholder {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
pointer-events: none;
}
.pdf-placeholder p {
color: var(--pdf-text-muted);
font-size: 14px;
margin: 0;
}
.pdf-frame {
width: 100%;
height: 100%;
border: none;
display: none;
}
/* ===== レスポンシブ ===== */
@media (max-width: 640px) {
.pdf-viewer {
flex-direction: column;
height: auto;
}
.pdf-file-list {
width: 100%;
height: 200px;
border-right: none;
border-bottom: 1px solid var(--pdf-border);
}
.pdf-preview-area {
height: 420px;
}
}
// ファイルアイテムのクリックで呼び出す
function selectFile(el) {
var frame = document.getElementById('pdfFrame');
var placeholder = document.getElementById('pdfPlaceholder');
// 既に選択中の同じファイルなら何もしない
if (el.classList.contains('is-active')) return;
// 選択状態をリセットして対象に付与
document.querySelectorAll('.pdf-item').forEach(function (item) {
item.classList.remove('is-active');
});
el.classList.add('is-active');
// iframeにPDFを表示する
frame.src = el.dataset.path;
placeholder.style.display = 'none';
frame.style.display = 'block';
}
AI用プロンプト
以下のプロンプトをコピーしてAIに渡すと、同様のコンポーネントを生成できます。
ChatGPTやClaudeにこのプロンプトを渡すと、同様のコンポーネントをゼロから生成・カスタマイズできます。ファイル件数の変更や列の追加など、要件を追記して使うのがおすすめです。
※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。
💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。
# ファイル一覧からPDFプレビュー 作成依頼
## 概要
ファイル一覧をクリックすると横のプレビューエリアにPDFがインライン表示される
左右2ペインのUIを実装してください。
## 要件
- 左ペインにファイル一覧(ファイル名・サイズ・日付)を表示すること
- ファイルアイテムをクリックすると右ペインの iframe に対応するPDFが表示されること
- 選択中のファイルをハイライト(背景色・左ボーダー)で強調すること
- 初期表示時は右ペインに「ファイルを選択してください」のプレースホルダーを表示すること
- スマートフォン幅(640px以下)では縦積みレイアウトに切り替えること
- 外部ライブラリは使用しないこと
## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- PDFの表示には <iframe> を使用し、ブラウザ組み込みのPDFビューワーに任せること
- レスポンシブ対応:必要(640px以下で縦積み)
## 動作詳細
ファイル一覧は data-path 属性に PDF ファイルのパスを持つ <li> 要素のリストとする。
クリック時に selectFile(el) を呼び出し、以下を実行する:
1. 全 li から .is-active クラスを外す
2. クリックされた li に .is-active クラスを付与する
3. el.dataset.path の値を iframe の src にセットする
4. プレースホルダーを非表示にし、iframe を表示する
ファイルリストのサンプルデータ(3件):
- 見積書_サンプル.pdf / 4 KB / 2024-01-15
- 議事録_サンプル.pdf / 2 KB / 2024-01-20
- 仕様書_サンプル.pdf / 2 KB / 2024-01-25
注意事項:
- border-left のオン/オフによるレイアウトシフトを避けるため、通常時も border-left: 3px solid transparent を設定すること
- モバイル(iOS Safari等)では iframe 内でPDFが表示されずダウンロードになる場合がある
## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。