帳票印刷プレビュー画面(@media print)

応用例 中級

この画面パターンについて

一覧データを window.print() でそのままきれいな帳票として出力する画面のパターンです。 PDF生成ライブラリを使わず、@media print の出し分け・@page のA4設定・break-inside による改ページ制御だけで、配布・保管に耐えるレイアウトを実現します。 印刷用CSSは体系的に学ぶ機会が少ない一方、業務アプリでは「この一覧、印刷したい」という要望が必ず出る分野です。

こんな場面で使えます

  • 月次レポート・台帳 — 一覧をそのままA4で配布・保管する
  • 参加者名簿・チェックリスト — 紙に印刷して持ち出して使う
  • 作業指示書・控えの出力 — ブラウザだけで印刷物を用意する

この画面で使っているUIコンポーネント

#パーツこの画面での役割
1画面用ツールバー(.no-print印刷ボタン等・印刷時は消える
2帳票ヘッダー帳票タイトル・出力日・出力者
3集計サマリー行合計件数・区分別の小計
4明細テーブル30行・ストライプ・状態バッジ
5@media print 出し分け画面用と印刷用の表示切り替え
6改ページ制御break-inside: avoid で行の分断防止
7@page 設定A4縦・余白15mmの用紙設定
8印刷配色調整バッジ記号+枠線でモノクロでも判別可

実装のポイント・注意点

画面と印刷の出し分けは .no-print(印刷で消す)と .print-only(印刷時のみ出す)の2クラスに集約します。 改ページは tr { break-inside: avoid; } で行の途中分断を防ぎますが、ブラウザ互換のため旧プロパティ page-break-inside: avoid も併記するのが実務の定石です。

最大の落とし穴は「ブラウザは既定で背景色を印刷しない」ことで、print-color-adjust: exact で背景を残す指定をしつつ、これはユーザーの印刷設定次第で無効になるため、色に頼らず枠線+記号でも判別できるバッジ設計を併用します。 出力日時は beforeprint イベントで印刷直前に更新し、thead が各ページに繰り返し印字されるtableの標準動作も活用します。

8個のUIコンポーネントをHTML・CSS・バニラJavaScriptのみで組み合わせており、 フレームワーク不要で画面ごとコピペして使えます。

動作サンプル

帳票印刷プレビュー画面(@media print)のデモ画面 動作サンプルを別ウィンドウで確認 ↗

試してみる:

  • 「🖨 印刷する」でブラウザの印刷プレビューを開き、ツールバーが消えて帳票だけになることを確認
  • 印刷プレビューで2ページ目にもテーブルヘッダーが繰り返し印字されることを確認
  • 「印刷用表示を画面で確認」トグルで、ツールバーが消えた印刷時のレイアウトを画面で確認(バッジは記号付きで色がなくても区別可)

そのほかの操作も自由に試してみてください。

サンプルソース

3つのファイルを同じフォルダに保存し、ブラウザで index.html を開くと動作確認できます。
ファイル名:index.html / style.css / script.js
保存時の文字コードは UTF-8 を指定してください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>帳票印刷プレビュー画面 サンプル</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>

<div class="report-screen">
  <!-- 画面専用ツールバー(印刷時は非表示) -->
  <div class="toolbar no-print">
    <h1 class="toolbar-title">アイテム一覧帳票</h1>
    <div class="toolbar-actions">
      <label class="preview-toggle">
        <input type="checkbox" id="previewToggle"> 印刷用表示を画面で確認
      </label>
      <button type="button" class="btn-primary" id="printBtn">🖨 印刷する</button>
    </div>
  </div>
  <p class="toolbar-note no-print">
    実際の印刷プレビューはブラウザの印刷ダイアログ(Ctrl+P)で確認できます。
  </p>

  <!-- 帳票本体(A4用紙風の枠。印刷時は枠なしで全幅) -->
  <div class="report-sheet">
    <header class="report-header">
      <h2 class="report-title">アイテム一覧表</h2>
      <p class="report-meta">出力日時:<span id="printDate"></span> 出力者:サンプル 太郎</p>
    </header>

    <p class="report-summary" id="reportSummary"><!-- JSで生成 --></p>

    <table class="report-table">
      <thead>
        <tr><th>#</th><th>名前</th><th>区分</th><th>ステータス</th><th>更新日</th></tr>
      </thead>
      <tbody id="reportBody"><!-- JSで生成 --></tbody>
    </table>

    <footer class="report-footer print-only">
      <p>Sample App — アイテム一覧表</p>
    </footer>
  </div>
</div>

<script src="./script.js"></script>
</body>
</html>
/* ===== 帳票印刷プレビュー画面 — style.css ===== */
*, *::before, *::after { box-sizing: border-box; }

:root {
  --color-primary:    #2B7FE8;
  --color-text:       #1E293B;
  --color-muted:      #64748B;
  --color-border:     #D0D7E0;
  --color-bg:         #F4F6F9;
  --color-sheet:      #FFFFFF;
  --color-th-bg:      #F1F5F9;
  --color-stripe:     #F7FAFC;
  /* A4縦の幅 ≒ 210mm × 96dpi ÷ 25.4 の概算値(画面の「用紙」表現用の目安) */
  --sheet-width:      794px;
}

body {
  margin: 0;
  font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
}

/* ===== 画面用ツールバー(印刷時は消える .no-print) ===== */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  max-width: var(--sheet-width);
  margin: 24px auto 0;
  padding: 0 4px;
}

.toolbar-title {
  font-size: 20px;
  margin: 0;
}

.toolbar-actions {
  display: flex;
  align-items: center;
  gap: 16px;
}

.preview-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--color-muted);
  cursor: pointer;
}

.btn-primary {
  height: 40px;
  padding: 0 20px;
  font-size: 14px;
  color: #fff;
  background: var(--color-primary);
  border: 1.5px solid var(--color-primary);
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s;
}

.btn-primary:hover { background: #1F6BD0; }

.toolbar-note {
  max-width: var(--sheet-width);
  margin: 8px auto 0;
  padding: 0 4px;
  font-size: 12px;
  color: var(--color-muted);
}

/* ===== 帳票本体(A4用紙風の枠) ===== */
/* 画面では「用紙」を白背景+影で表現する。印刷時は @media print で枠・影を消す */
.report-sheet {
  max-width: var(--sheet-width);
  margin: 16px auto 48px;
  padding: 32px 36px;
  background: var(--color-sheet);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

/* ===== 帳票ヘッダー・サマリー ===== */
.report-header {
  border-bottom: 2px solid var(--color-text);
  padding-bottom: 12px;
  margin-bottom: 16px;
}

.report-title {
  font-size: 22px;
  margin: 0 0 6px;
}

.report-meta {
  font-size: 13px;
  color: var(--color-muted);
  margin: 0;
}

.report-summary {
  font-size: 14px;
  margin: 0 0 16px;
  padding: 10px 14px;
  background: var(--color-th-bg);
  border-radius: 4px;
}

/* ===== 明細テーブル ===== */
.report-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.report-table th,
.report-table td {
  padding: 8px 12px;
  text-align: left;
  border: 1px solid var(--color-border);
}

.report-table thead th {
  background: var(--color-th-bg);
  color: var(--color-muted);
  font-size: 12px;
  white-space: nowrap;
}

/* ストライプ(偶数行)。印刷で背景が消えても罫線で可読性を保つ */
.report-table tbody tr:nth-child(even) {
  background: var(--color-stripe);
}

.report-table td:first-child {
  text-align: center;
  white-space: nowrap;
}

/* ===== ステータスバッジ(記号+枠線でモノクロでも判別可能) ===== */
/* 色だけに頼らず「●有効 / ■停止 / ▲保留」の記号と枠線を併用する。
   印刷で背景色が消えても区別できる設計が本事例の主眼 */
.status-badge {
  display: inline-block;
  padding: 2px 10px;
  font-size: 12px;
  border: 1px solid currentColor;
  border-radius: 4px;
  white-space: nowrap;
  /* 印刷でバッジの背景色を残す指定(ユーザーの印刷設定で無効になる場合あり) */
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

.badge-active    { color: #166534; background: #DCFCE7; }
.badge-suspended { color: #475569; background: #E2E8F0; }
.badge-pending   { color: #854D0E; background: #FEF9C3; }

/* ===== フッター(印刷時のみ表示 .print-only) ===== */
.report-footer {
  display: none;
  margin-top: 20px;
  padding-top: 10px;
  border-top: 1px solid var(--color-border);
  font-size: 11px;
  color: var(--color-muted);
  text-align: center;
}

/* ===== 印刷用表示の画面プレビュー(デモ専用・簡易確認用) ===== */
/* トグルONで body に .print-preview を付け、印刷時の主要な見え方
   (ツールバー非表示・モノクロバッジ)を画面でも擬似的に確認する。
   @page や改ページは画面では再現できないため、正確な確認は Ctrl+P で行う */

/* タイトル・印刷ボタン・注記は隠す。ただしトグル自体は残さないと
   プレビューを解除できなくなるため、トグルだけは画面に残す */
body.print-preview .toolbar-title,
body.print-preview #printBtn,
body.print-preview .toolbar-note {
  display: none;
}

/* 解除用にトグルを右上へ固定表示する(チェックを外すと元の画面に戻る) */
body.print-preview .toolbar {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 10;
  width: auto;
  max-width: none;
  margin: 0;
  padding: 8px 14px;
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

body.print-preview .report-sheet {
  box-shadow: none;
  border-color: var(--color-border);
}

/* ===== @media print(本事例の主役) ===== */
@media print {
  body { background: #fff; }

  /* 画面専用要素は消し、印刷専用要素を出す */
  .no-print { display: none !important; }
  .print-only { display: block; }

  /* 用紙風の枠・影・余白を消して用紙いっぱいに広げる */
  .report-sheet {
    max-width: none;
    margin: 0;
    padding: 0;
    border: none;
    box-shadow: none;
    border-radius: 0;
  }

  /* A4縦・余白15mmの用紙設定 */
  @page {
    size: A4;
    margin: 15mm;
  }

  /* 行・サマリーが改ページで途中分断されないようにする。
     break-inside(新仕様)と page-break-inside(旧仕様)を併記するのが実務の定石 */
  tr, .report-summary {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  /* バッジの背景色を印刷でも残す(インク節約のため既定では消えるため) */
  .status-badge {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}
/* =====================================================
   帳票印刷プレビュー画面のスクリプト

   仕組み:明細データ(配列)から行を描画し、件数は配列から
   集計してサマリーに出す(データと表示を二重管理しない)。
   印刷は window.print() を呼ぶだけ。@media print 側の
   印刷用CSSがレイアウトの出し分け・改ページ制御を担う。

   出力日時は beforeprint で印刷直前に更新する。
   行の生成は createElement + textContent(XSS対策)。
   ===================================================== */

// ===== 設定値 =====
// ステータス値 → 表示名・記号・バッジ色クラスの対応表
// 記号は色が出ないモノクロ印刷でも区別できるようにするためのもの
var STATUS_MAP = {
  active:    { label: '有効', mark: '●', className: 'badge-active' },
  suspended: { label: '停止', mark: '■', className: 'badge-suspended' },
  pending:   { label: '保留', mark: '▲', className: 'badge-pending' }
};

// ===== 明細データ(30件・印刷で2ページに分かれる分量) =====
var items = [
  { no: 1,  name: 'サンプルアイテムA',  category: 'タイプ1', status: 'active',    updatedAt: '2026-06-01' },
  { no: 2,  name: 'サンプルアイテムB',  category: 'タイプ2', status: 'suspended', updatedAt: '2026-05-28' },
  { no: 3,  name: 'サンプルアイテムC',  category: 'タイプ3', status: 'pending',   updatedAt: '2026-05-30' },
  { no: 4,  name: 'サンプルアイテムD',  category: 'タイプ1', status: 'active',    updatedAt: '2026-04-15' },
  { no: 5,  name: 'サンプルアイテムE',  category: 'タイプ2', status: 'pending',   updatedAt: '2026-06-03' },
  { no: 6,  name: 'サンプルアイテムF',  category: 'タイプ3', status: 'active',    updatedAt: '2026-03-22' },
  { no: 7,  name: 'サンプルアイテムG',  category: 'タイプ1', status: 'pending',   updatedAt: '2026-05-11' },
  { no: 8,  name: 'サンプルアイテムH',  category: 'タイプ2', status: 'active',    updatedAt: '2026-04-02' },
  { no: 9,  name: 'サンプルアイテムI',  category: 'タイプ3', status: 'suspended', updatedAt: '2026-06-07' },
  { no: 10, name: 'サンプルアイテムJ',  category: 'タイプ1', status: 'active',    updatedAt: '2026-03-18' },
  { no: 11, name: 'サンプルアイテムK',  category: 'タイプ2', status: 'suspended', updatedAt: '2026-05-06' },
  { no: 12, name: 'サンプルアイテムL',  category: 'タイプ3', status: 'pending',   updatedAt: '2026-04-25' },
  { no: 13, name: 'サンプルアイテムM',  category: 'タイプ1', status: 'active',    updatedAt: '2026-06-09' },
  { no: 14, name: 'サンプルアイテムN',  category: 'タイプ2', status: 'pending',   updatedAt: '2026-03-30' },
  { no: 15, name: 'サンプルアイテムO',  category: 'タイプ3', status: 'active',    updatedAt: '2026-05-19' },
  { no: 16, name: 'サンプルアイテムP',  category: 'タイプ1', status: 'pending',   updatedAt: '2026-04-08' },
  { no: 17, name: 'サンプルアイテムQ',  category: 'タイプ2', status: 'active',    updatedAt: '2026-06-05' },
  { no: 18, name: 'サンプルアイテムR',  category: 'タイプ3', status: 'suspended', updatedAt: '2026-03-12' },
  { no: 19, name: 'サンプルアイテムS',  category: 'タイプ1', status: 'active',    updatedAt: '2026-05-23' },
  { no: 20, name: 'サンプルアイテムT',  category: 'タイプ2', status: 'suspended', updatedAt: '2026-04-19' },
  { no: 21, name: 'サンプルアイテムU',  category: 'タイプ3', status: 'active',    updatedAt: '2026-06-02' },
  { no: 22, name: 'サンプルアイテムV',  category: 'タイプ1', status: 'suspended', updatedAt: '2026-03-25' },
  { no: 23, name: 'サンプルアイテムW',  category: 'タイプ2', status: 'pending',   updatedAt: '2026-05-15' },
  { no: 24, name: 'サンプルアイテムX',  category: 'タイプ3', status: 'active',    updatedAt: '2026-04-28' },
  { no: 25, name: 'サンプルアイテムY',  category: 'タイプ1', status: 'pending',   updatedAt: '2026-06-08' },
  { no: 26, name: 'サンプルアイテムZ',  category: 'タイプ2', status: 'active',    updatedAt: '2026-03-09' },
  { no: 27, name: 'サンプルアイテムAA', category: 'タイプ3', status: 'suspended', updatedAt: '2026-05-02' },
  { no: 28, name: 'サンプルアイテムAB', category: 'タイプ1', status: 'active',    updatedAt: '2026-04-12' },
  { no: 29, name: 'サンプルアイテムAC', category: 'タイプ2', status: 'pending',   updatedAt: '2026-06-04' },
  { no: 30, name: 'サンプルアイテムAD', category: 'タイプ3', status: 'active',    updatedAt: '2026-03-16' }
];

// ===== DOM要素 =====
var reportBody    = document.getElementById('reportBody');
var reportSummary = document.getElementById('reportSummary');
var printDate     = document.getElementById('printDate');
var printBtn      = document.getElementById('printBtn');
var previewToggle = document.getElementById('previewToggle');

// ===== 描画 =====
// 明細テーブルを配列から描く
function renderRows() {
  items.forEach(function (item) {
    var tr = document.createElement('tr');
    tr.appendChild(createCell(String(item.no)));
    tr.appendChild(createCell(item.name));
    tr.appendChild(createCell(item.category));
    tr.appendChild(createStatusCell(item.status));
    tr.appendChild(createCell(item.updatedAt));
    reportBody.appendChild(tr);
  });
}

// テキストセルを生成する
function createCell(text) {
  var td = document.createElement('td');
  td.textContent = text;
  return td;
}

// ステータスバッジ入りのセルを生成する(記号+ラベル)
function createStatusCell(status) {
  var td = document.createElement('td');
  var info = STATUS_MAP[status];
  var badge = document.createElement('span');
  badge.className = 'status-badge ' + info.className;
  badge.textContent = info.mark + info.label;
  td.appendChild(badge);
  return td;
}

// 区分別の件数を配列から集計してサマリー行に出す(表示と二重管理しない)
function renderSummary() {
  var counts = items.reduce(function (acc, item) {
    acc[item.category] = (acc[item.category] || 0) + 1;
    return acc;
  }, {});
  reportSummary.textContent =
    '合計 ' + items.length + '件' +
    '(タイプ1:' + (counts['タイプ1'] || 0) + '件' +
    ' / タイプ2:' + (counts['タイプ2'] || 0) + '件' +
    ' / タイプ3:' + (counts['タイプ3'] || 0) + '件)';
}

// ===== 出力日時 =====
// 「2026-06-10 14:30 出力」形式の現在時刻を帳票ヘッダーに入れる
function updatePrintDate() {
  var now = new Date();
  var ymd = now.getFullYear() + '-' +
    pad(now.getMonth() + 1) + '-' + pad(now.getDate());
  var hm = pad(now.getHours()) + ':' + pad(now.getMinutes());
  printDate.textContent = ymd + ' ' + hm + ' 出力';
}

// 1桁の数値を0埋めして2桁にする
function pad(n) {
  return (n < 10 ? '0' : '') + n;
}

// ===== イベント =====
// 「印刷する」 → ブラウザの印刷ダイアログを開く
printBtn.addEventListener('click', function () {
  window.print();
});

// 印刷直前に出力日時を現在時刻へ更新する(印刷の瞬間の時刻を残すため)
window.addEventListener('beforeprint', updatePrintDate);

// 「印刷用表示を画面で確認」トグル → body に .print-preview を付け外し
previewToggle.addEventListener('change', function () {
  document.body.classList.toggle('print-preview', previewToggle.checked);
});

// ===== 初期化 =====
renderRows();
renderSummary();
updatePrintDate();  // beforeprint 非対応環境のフォールバックとして初期表示でもセット

AI用プロンプト

以下のプロンプトをコピーしてAIに渡すと、同様の画面パターンを生成できます。

ChatGPTやClaudeにこのプロンプトを渡すと、同様の画面をゼロから生成・カスタマイズできます。列の追加や明細データの差し替えなど、要件を追記して使うのがおすすめです。

※ このプロンプトを使ってもデモとまったく同じ動作にならない場合があります。AIの解釈や生成タイミングによって差が出ることをご了承ください。

💡 jQuery・Vue・React など特定のライブラリで実装したい場合は、プロンプトの末尾に「〇〇を使って実装してください」と追記してください。

# 帳票印刷プレビュー画面 作成依頼

## 概要
一覧データを window.print によるブラウザ印刷できれいな帳票として出力できる画面を
作成してください。PDF生成ライブラリは使わず、@media print の印刷用CSSで実現します。

## 要件
- 画面上部に操作ツールバー(タイトル・印刷ボタン)を置き、印刷時には表示しない(.no-printクラス)
- 帳票本体は A4幅を想定した白い用紙風の枠で画面表示し、印刷時は枠・影なしで用紙いっぱいに広げる
- 帳票はヘッダー(帳票タイトル・出力日時・出力者)+サマリー行(合計件数・区分別の件数)+
  明細テーブル(#・名前・区分・ステータス・更新日の5列・30行)で構成する
- ステータスは「有効・停止・保留」のバッジで表示する。色だけに頼らず記号や枠線も併用し、
  モノクロ印刷でも区別できるようにする
- 印刷用CSSの要件:
  - @page { size: A4; margin: 15mm; } を設定する
  - 明細行が改ページで途中分断されないよう break-inside: avoid を指定する
    (互換用に page-break-inside: avoid も併記する)
  - thead が2ページ目以降にも繰り返し印字されることを確認する
  - バッジの背景色が印刷で消えないよう print-color-adjust: exact を指定する
- 「印刷する」ボタンで window.print() を呼ぶ
- beforeprint イベントで帳票の出力日時を現在時刻に更新する
- デモ用に「印刷用表示を画面で確認」トグルを置き、ONで印刷時に近い見た目
  (ツールバー非表示など)を画面上でも確認できるようにする(このトグル自体も印刷しない)

## 技術仕様
- HTML / CSS / バニラJavaScript で実装
- 外部ライブラリ:なし
- 明細データはJavaScript内の配列で保持し、サマリーの件数は配列から集計する
- レスポンシブ対応:画面表示は必要、印刷レイアウトはA4縦を前提とする

## 動作詳細
- 画面用スタイルと @media print ブロックを明確に分けて記述し、
  印刷で何が変わるのかコメントで説明する
- 明細行の生成は createElement と textContent を使い、innerHTML に変数を結合しない

## 出力形式
HTML・CSS・JavaScriptを分けて出力してください。
各ファイルは単独でコピー&ペーストして使えるよう記述してください。