Excelファイル(.xlsx)読み込み・テーブル表示

ユーティリティ 初級

このコンポーネントについて

Excelファイル(.xlsx)をアップロード前に確認したい場面では、ブラウザ内でシートの内容をプレビューできると安全です。このページでは SheetJS を使い、選択またはドラッグ&ドロップした .xlsx ファイルをサーバーへ送らずに読み込み、HTML tableで表示します。複数のワークシートを含むブックでは、シート名を選んで表示内容を切り替えられます。数式やマクロは実行せず、Excelファイルに保存されている表示値のみを扱います。

  • ブラウザ内での .xlsx 読み込みfile.arrayBuffer() と SheetJS の XLSX.read() で解析。ファイル内容はサーバーへ送信しません
  • シート選択 — 読み込んだブックのシート名から選択肢を作り、任意のシートへすぐに切り替えられます
  • テーブルプレビュー — 先頭行をヘッダー、2行目以降をデータ行として表示。横・縦スクロールと固定ヘッダーに対応します
  • 表示件数の上限 — 10MBまでのファイルを受け付け、各シートは先頭100行までを表示して負荷を抑えます
  • 安全な文字列表示 — ファイル名・シート名・セル内容はすべて textContent で表示し、innerHTML に渡しません

実装のポイント・注意点

ブラウザ版のSheetJSは、ファイル全体をメモリに読み込んでから解析します。そのためページングを付けても読込時のメモリ使用量は減りません。このサンプルでは解析前の10MB制限と、描画を先頭100行に絞ることで、ブラウザが重くなることを防ぎます。

数式はブラウザで再計算せず、ファイルに保存済みの表示値だけを使います。VBAマクロも実行しません。CSVを読み込みたい場合は、専用のCSV読み込み・テーブル表示を、CSVの検証・取込画面が必要な場合はCSVインポート画面を使ってください。

デモ

サンプルソース

3つのファイルを同じフォルダに保存し、index.html をブラウザで開くと動作確認できます。
ファイル名:index.html / style.css / script.js — SheetJSはCDNから読み込むため、インターネット接続が必要です。保存時の文字コードは UTF-8 を指定してください。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Excelファイル読み込み</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div class="xlsx-viewer">
    <label class="xlsx-viewer__area" id="xlsx-upload-area" for="xlsx-input">
      <span class="xlsx-viewer__text">クリックまたはドラッグ&ドロップでExcelファイルを選択</span>
      <span class="xlsx-viewer__sub">.xlsx ファイル・10MBまで(サーバー送信なし)</span>
      <input type="file" id="xlsx-input" class="xlsx-viewer__input" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
    </label>
    <p class="xlsx-viewer__status" id="xlsx-status" role="status" aria-live="polite"></p>
    <p class="xlsx-viewer__error" id="xlsx-error" role="alert" hidden></p>
    <div class="xlsx-viewer__result" id="xlsx-result" hidden>
      <div class="xlsx-viewer__toolbar">
        <p class="xlsx-viewer__file-name" id="xlsx-file-name"></p>
        <label class="xlsx-viewer__sheet-label" for="xlsx-sheet-select">
          表示するシート
          <select id="xlsx-sheet-select" class="xlsx-viewer__sheet-select"></select>
        </label>
      </div>
      <p class="xlsx-viewer__count" id="xlsx-row-count"></p>
      <p class="xlsx-viewer__empty" id="xlsx-empty" hidden>このシートには表示できるデータがありません。</p>
      <div class="xlsx-viewer__scroll" id="xlsx-scroll">
        <table class="xlsx-viewer__table">
          <thead id="xlsx-thead"></thead>
          <tbody id="xlsx-tbody"></tbody>
        </table>
      </div>
    </div>
    <button class="xlsx-viewer__reset" id="xlsx-reset" type="button" hidden>リセット</button>
  </div>

  <script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script>
  <script src="./script.js"></script>
</body>
</html>
* { box-sizing: border-box; }
body { margin: 0; padding: 24px; color: #1a2332; background: #f7f9fc; font-family: sans-serif; }
.xlsx-viewer { width: min(100%, 480px); margin: 0 auto; }
.xlsx-viewer__area { display: flex; flex-direction: column; align-items: center; gap: 8px; min-height: 280px; padding: 32px 20px; color: #2b7fe8; background: #fff; border: 2px dashed #a9c8ef; border-radius: 12px; cursor: pointer; text-align: center; }
.xlsx-viewer__area:hover, .xlsx-viewer__area.is-dragover { background: #eef6ff; border-color: #2b7fe8; }
.xlsx-viewer__area.is-loading { opacity: .65; pointer-events: none; }
.xlsx-viewer__text { color: #1a2332; font-weight: 700; }
.xlsx-viewer__sub, .xlsx-viewer__status, .xlsx-viewer__count { color: #5a6a7a; font-size: 13px; }
.xlsx-viewer__input { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.xlsx-viewer__error { margin: 12px 0 0; padding: 12px; color: #b42318; background: #fef3f2; border: 1px solid #fecdca; border-radius: 8px; font-size: 14px; }
.xlsx-viewer__toolbar { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-top: 20px; flex-wrap: wrap; }
.xlsx-viewer__file-name { margin: 0; font-weight: 700; overflow-wrap: anywhere; }
.xlsx-viewer__sheet-label { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 700; }
.xlsx-viewer__sheet-select { max-width: 260px; padding: 7px 10px; border: 1px solid #b8c4d1; border-radius: 6px; background: #fff; font: inherit; }
.xlsx-viewer__scroll { max-height: 400px; overflow: auto; border: 1px solid #dce3eb; border-radius: 8px; }
.xlsx-viewer__table { width: max-content; min-width: 100%; border-collapse: collapse; background: #fff; font-size: 14px; }
.xlsx-viewer__table th, .xlsx-viewer__table td { max-width: 280px; padding: 10px 12px; border-bottom: 1px solid #e7edf3; overflow-wrap: anywhere; text-align: left; vertical-align: top; }
.xlsx-viewer__table th { position: sticky; top: 0; z-index: 1; color: #fff; background: #2b7fe8; }
.xlsx-viewer__table tbody tr:nth-child(even) { background: #f8fafc; }
.xlsx-viewer__empty { padding: 24px; color: #5a6a7a; background: #f8fafc; border: 1px solid #dce3eb; border-radius: 8px; text-align: center; }
.xlsx-viewer__reset { display: block; margin: 16px 0 0 auto; padding: 8px 16px; color: #5a6a7a; background: #fff; border: 1px solid #b8c4d1; border-radius: 6px; cursor: pointer; font: inherit; }
@media (max-width: 480px) { body { padding: 16px; } .xlsx-viewer__toolbar, .xlsx-viewer__sheet-label { align-items: stretch; flex-direction: column; } .xlsx-viewer__sheet-select { max-width: none; } }
(function () {
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
  var MAX_PREVIEW_ROWS = 100;
  var workbook = null;
  var input = document.getElementById('xlsx-input');
  var area = document.getElementById('xlsx-upload-area');
  var status = document.getElementById('xlsx-status');
  var error = document.getElementById('xlsx-error');
  var result = document.getElementById('xlsx-result');
  var fileName = document.getElementById('xlsx-file-name');
  var sheetSelect = document.getElementById('xlsx-sheet-select');
  var rowCount = document.getElementById('xlsx-row-count');
  var empty = document.getElementById('xlsx-empty');
  var scroll = document.getElementById('xlsx-scroll');
  var thead = document.getElementById('xlsx-thead');
  var tbody = document.getElementById('xlsx-tbody');
  var reset = document.getElementById('xlsx-reset');

  input.addEventListener('change', function () { if (input.files[0]) handleFile(input.files[0]); });
  sheetSelect.addEventListener('change', function () { renderSheet(sheetSelect.value); });
  reset.addEventListener('click', resetDemo);
  ['dragenter', 'dragover'].forEach(function (eventName) {
    area.addEventListener(eventName, function (event) { event.preventDefault(); area.classList.add('is-dragover'); });
  });
  ['dragleave', 'drop'].forEach(function (eventName) {
    area.addEventListener(eventName, function (event) { event.preventDefault(); area.classList.remove('is-dragover'); });
  });
  area.addEventListener('drop', function (event) { var file = event.dataTransfer.files[0]; if (file) handleFile(file); });

  async function handleFile(file) {
    clearResult();
    if (!/\.xlsx$/i.test(file.name)) return showError('.xlsx ファイルを選択してください。');
    if (file.size > MAX_FILE_SIZE) return showError('10MB以下の .xlsx ファイルを選択してください。');
    setLoading(true);
    status.textContent = '読み込んでいます…';
    try {
      workbook = XLSX.read(await file.arrayBuffer(), { dense: true });
      if (!workbook.SheetNames.length) throw new Error('sheet not found');
      fileName.textContent = file.name + '(' + workbook.SheetNames.length + 'シート)';
      workbook.SheetNames.forEach(function (name) { var option = document.createElement('option'); option.value = name; option.textContent = name; sheetSelect.appendChild(option); });
      result.hidden = false;
      reset.hidden = false;
      status.textContent = '読み込みました。表示するシートを選べます。';
      renderSheet(workbook.SheetNames[0]);
    } catch (e) {
      workbook = null;
      showError('ファイルを読み込めませんでした。破損またはパスワード保護されていないか確認してください。');
    } finally {
      setLoading(false);
    }
  }

  function renderSheet(sheetName) {
    var rows = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], { header: 1, defval: '', raw: false, blankrows: false });
    thead.replaceChildren(); tbody.replaceChildren(); scroll.hidden = !rows.length; empty.hidden = Boolean(rows.length);
    if (!rows.length) { rowCount.textContent = ''; return; }
    var columnCount = rows.reduce(function (max, row) { return Math.max(max, row.length); }, 0);
    var headerRow = document.createElement('tr');
    for (var i = 0; i < columnCount; i += 1) { var th = document.createElement('th'); th.textContent = rows[0][i] == null ? '' : rows[0][i]; headerRow.appendChild(th); }
    thead.appendChild(headerRow);
    var dataRows = rows.slice(1); var fragment = document.createDocumentFragment();
    dataRows.slice(0, MAX_PREVIEW_ROWS).forEach(function (row) { var tr = document.createElement('tr'); for (var i = 0; i < columnCount; i += 1) { var td = document.createElement('td'); td.textContent = row[i] == null ? '' : row[i]; tr.appendChild(td); } fragment.appendChild(tr); });
    tbody.appendChild(fragment);
    rowCount.textContent = dataRows.length > MAX_PREVIEW_ROWS ? dataRows.length + '件中 ' + MAX_PREVIEW_ROWS + '件を表示' : dataRows.length + '件';
  }

  function clearResult() { workbook = null; error.hidden = true; error.textContent = ''; result.hidden = true; reset.hidden = true; fileName.textContent = ''; rowCount.textContent = ''; sheetSelect.replaceChildren(); thead.replaceChildren(); tbody.replaceChildren(); }
  function showError(message) { error.textContent = message; error.hidden = false; status.textContent = ''; }
  function setLoading(isLoading) { input.disabled = isLoading; area.classList.toggle('is-loading', isLoading); area.setAttribute('aria-busy', String(isLoading)); }
  function resetDemo() { input.value = ''; clearResult(); status.textContent = ''; setLoading(false); }
}());

AI用プロンプト

⚠️ 数式を再計算したり、VBAマクロを実行したりする用途には使わないでください。ブラウザ内のプレビュー専用です。

💡 既存の取込画面に組み込む場合は、プロンプト末尾に「読込後の検証・送信処理は既存の関数へつなげてください」と追記するとスムーズです。

# Excelファイル(.xlsx)読み込み・テーブル表示 作成依頼

## 概要
SheetJS Community Edition 0.20.3 を使い、.xlsx ファイルをブラウザ内だけで読み込み、シートを選んでテーブル表示するUIを実装してください。ファイルはサーバーへ送信しません。

## 要件
- .xlsx ファイルのみ受け付け、クリックとドラッグ&ドロップの両方に対応すること
- 10MBを超えるファイルは解析前にエラー表示すること
- `file.arrayBuffer()` で一度だけ読み込み、`XLSX.read(arrayBuffer, { dense: true })` で解析すること
- 先頭シートを表示し、シート名のselectで表示を切り替えられること
- シート切り替えではファイルを再読込・再解析しないこと
- 先頭行をヘッダー、2行目以降をデータ行として表示すること
- 各シートは先頭100データ行だけを表示し、101行以上は「N件中 100件を表示」と案内すること
- ページングは実装しないこと
- テーブルを横・縦スクロール対応にし、ヘッダー行をstickyで固定すること
- .xlsx 以外、10MB超、空シート、解析できないファイルには分かりやすいメッセージを表示すること
- 数式の評価やVBAマクロの実行は行わず、保存済みの表示値だけをプレビューすること
- ファイル名・シート名・セル内容は `textContent` で表示し、`innerHTML` に渡さないこと
- リセットでワークブック参照と表示内容をクリアすること

## 技術仕様
- HTML / CSS / バニラJavaScript
- SheetJS Community Edition 0.20.3 を `https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js` から読み込む
- レスポンシブ対応:必要

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