Amazonで商品を見るセール会場へ

Googleスプレッドシート│ドライブ内の画像をサムネ付きギャラリー表示する方法

Googleドライブの画像を、GAS(Google Apps Script)のWebアプリで一覧表示する作例です。

スプレッドシートの名称・タグを使って絞り込み、画像を拡大表示する構成です。

GASはGoogleサービスを操作するスクリプトです。

本人限定の練習用データを対象とし、現行コードの実サービス上での動作確認は未完了です。

  • Googleの標準サービスを利用できる(保存容量・契約プラン・GASの割り当ての範囲内)
  • Webアプリの公開範囲を設定できる(この例は本人限定で試す)
  • GASとHTMLのコードを編集して、項目や表示方法を変更できる
  • スプレッドシート上でデータを編集・管理しやすい
  • Googleドライブ上の画像を参照できる(空き容量と画像の閲覧権限の確認が必要)
Googleドライブ上の画像データを活用して作成したサムネイル付きギャラリーの画面イメージ。
目次

ギャラリー表示機能の概要

掲載画像は既存の画面例で、今回の改訂コードを実行して撮影したものではありません。

先に画像・管理データを用意し、5ファイルの保存後にメニューから画像一覧を取得します。

1. Googleドライブの設定

自分で閲覧できる専用フォルダを作り、まず機密情報を含まない画像2枚で試します。

ギャラリー表示に使用する画像ファイルを格納したGoogleドライブの専用フォルダ。

2. スプレッドシートへのデータ入力

スプレッドシートをデータベースとして利用します。

「管理シート」のA1:G1に、id、file_name、item_name、image_path、tag、rare、typeを順に入力します。

2行目以降へデータを入れ、idは1、2のような重複しない数値にしてください。

image_pathには後述の画像URLを入れます。

tag・rare・typeの複数値は半角カンマで区切り、値そのものにカンマは使いません。

IDや名称、絞り込み用タグなどのメタデータを入力したGoogleスプレッドシートの画面。

「画像パスの取得」という専用シートを用意し、カスタムメニューでファイル名・画像URL・フォルダパスを出力します。

取得したURLとファイル名を管理シートのimage_pathとfile_nameへ転記し、item_nameも入力してください。

取得用シートのA〜C列は再実行で更新するため、手入力データは置かず、先にコピーを保存します。

スプレッドシート側で画像のパス情報を取得・管理するための設定画面。

3. Webアプリ上でのギャラリー表示

GASで構築したWebアプリがスプレッドシートのデータを読み込み、ギャラリー形式で画面に描画します。

スプレッドシートのデータを読み込んで描画されたWebアプリのギャラリー表示画面。

画像カードにカーソルを合わせると詳細情報が表示されます。

ギャラリー上の画像カードにカーソルを合わせて詳細情報が表示された様子。

絞り込み条件を指定して検索ボタンを押すと、該当する画像のみに絞り込まれます。

検索条件を指定して絞り込みを実行したあとのギャラリー表示結果。
スポンサーリンク

サムネイル付きギャラリーの実装手順

スプレッドシートのメニュー「拡張機能」から「Apps Script」を選択し、スクリプトエディタを起動します。

プロジェクトは以下の5つのファイルで構成します。

拡張機能から起動したApps Scriptのエディタと構成するファイル一覧。
main.gsバックエンドの処理実装
index.html画面の基本HTML構造
css.htmlレイアウトやホバー演出のスタイル
js.html表示制御や絞り込み検索のスクリプト
plugin.html外部ライブラリの読み込み設定

以下は実装の詳細なコードと解説です。

※本コードはベースサンプルです。

この例の公開範囲は本人限定です。

自分として実行するWebアプリは、所有者の権限で管理データを読み出します。

管理シートの7列すべてをブラウザーへ返すため、画面の絞り込みでは閲覧権限を制限できません。

管理データに機密情報を入れず、他人へ公開する場合は閲覧者ごとの認可を別途実装してください。

Googleスプレッドシートの関連テクニックは、まとめ記事でも紹介しています。

5つのファイルにコードを記述したあと、ウェブアプリとしてデプロイして動作を確認する手順で進めます。

plugin.htmlの実装

外部ライブラリはCDN(外部のファイル配信サービス)から掲載版を読み込みます。

最新の保守・脆弱性状況と相互互換性は未検証です。

業務利用前に採用版を確認してください。

<!-- jQuery full -->
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>

<!-- iziModal -->
<link  rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/izimodal/1.6.0/css/iziModal.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/izimodal/1.6.0/js/iziModal.min.js"></script>

<!-- Underscore / lodash -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js"></script>

<!-- Bootstrap & select -->
<link  rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link  rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.10/dist/css/bootstrap-select.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.13.10/js/bootstrap-select.min.js"></script>

<!-- twbs-pagination -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/twbs-pagination/1.4.2/jquery.twbsPagination.min.js"></script>

css.htmlの実装

デザインとスタイルの設定です。

画像ホバー時にメタ情報を表示するスタイルなどを指定しています。

<style>
/* =====================  共通リセット  ===================== */
* { margin: 0; padding: 0; box-sizing: border-box; }

/* =====================  操作エリア  ===================== */
#header { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.5rem; }
.selectpicker { width: 200px; }

/* ===== 画像グリッド ===== */
#btn-search {
    background-color: #333;
    border-color: #333;
}
.btn.btn-info {
    background-color: #28a745;
    border-color: #28a745;
}

/* ===== 画像グリッド ===== */
#grid{
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 20px;
  list-style: none;
  padding: 0;
}

/* 画面幅に応じて列数を自動調整 */
@media (max-width: 1280px){ #grid{ grid-template-columns: repeat(4, minmax(0,1fr)); } }
@media (max-width: 992px) { #grid{ grid-template-columns: repeat(3, minmax(0,1fr)); } }
@media (max-width: 768px) { #grid{ grid-template-columns: repeat(2, minmax(0,1fr)); } }

/* ===== カード ===== */
#grid .list_item{ position: relative; }

#grid img.card{
  width: 100%;
  height: auto;
  object-fit: contain;
  transition: filter .5s;
}

/* ===== ホバー時のエフェクト ===== */
#grid .list_item:hover img.card{ filter: brightness(50%); }

#grid .list_item:hover::after {
  content: attr(data-alt) '\A' attr(data-name) '\A ID : ' attr(data-id);
  white-space: pre-wrap;
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  width: 90%;
  color: #fff;
  pointer-events: none;
}

/* =====================  モーダル画像  ===================== */
.iziModal{
  position: fixed !important;          /* 念のため固定 */
  left: 50% !important;
  top : 50% !important;
  transform: translate(-50%,-50%) !important;
  margin: 0 !important;
  max-width : 90vw !important;         /* 画面幅 90% まで拡大可 */
  width     : auto   !important;       /* 幅は内容に合わせて伸縮 */
}
.iziModal-content {
  padding: 1em!important;
}
.modal_card {
    width: 100%;
}
</style>

main.gsの実装

スプレッドシートやドライブとの連携を処理するサーバーサイドコードです。

コード内の定数定義に以下の設定値を指定します。

  • 対象スプレッドシートのID
  • 画像を格納しているGoogleドライブフォルダのID
  • 画像パスを出力するシート名
  • メタデータを管理しているシート名

各項目をお手元の環境に合わせて設定してください。

シート上から各機能を呼び出せるよう、カスタムメニューも追加しています。

各種機能の呼び出し用カスタムメニューが追加されたスプレッドシートの画面。

「ギャラリー」シートを作り、デプロイ後に発行された/execで終わるURLをA1へ入力します。

画像はPNG・JPEG・GIF・WebPのみを収集します。

走査はルートを含む10階層・100フォルダ・1000ファイル、対象画像500件までのサンプルです。

これらはコード内の制限で、Google Drive全体の上限ではありません。

公開したWebアプリのURLを管理用シートの所定セルに転記して管理している様子。
/* =====================================================================
 * 定数定義
 * ===================================================================*/
const SS_ID           = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const FOLDER_ID       = 'xxxxxxxxxxxxxxxxxxxxxxxxx';
const SHEET_IMG_PATH  = '画像パスの取得';
const SHEET_DATA_MAIN = '管理シート';

/* =====================================================================
 * 画像ファイル一覧をスプレッドシートへ書き込む
 * ===================================================================*/
function exportDriveImages_() {
  const rootFolder = DriveApp.getFolderById(FOLDER_ID);
  const list = [];

  getFilesRecursively_(rootFolder, rootFolder.getName(), list, {folders: 0, files: 0}, 0);

  if (SHEET_IMG_PATH === SHEET_DATA_MAIN) throw new Error("取得用と管理用のシートは分けてください。");

  const sheet = getSheet_(SHEET_IMG_PATH);
  const neededRows = list.length + 1;
  if (sheet.getMaxRows() < neededRows) sheet.insertRowsAfter(sheet.getMaxRows(), neededRows - sheet.getMaxRows());
  sheet.getRange(1, 1, Math.max(sheet.getLastRow(), 1), 3).clearContent();
  sheet.getRange(1, 1, 1, 3).setValues([['ファイル名', '画像URL', 'フォルダパス']]);
  if (list.length) sheet.getRange(2, 1, list.length, 3).setValues(list.map(row => row.map(value => /^[=+@-]/.test(value) ? "'" + value : value)));
}

function getFilesRecursively_(folder, path, list, state, depth) {
  if (depth >= 10 || ++state.folders > 100) throw new Error('フォルダ走査の上限です。対象を小さくしてください。');
  const files = folder.getFiles();
  while (files.hasNext()) {
    const file = files.next();
    if (++state.files > 1000) throw new Error('ファイル走査は1000件までです。');
    if (!['image/png','image/jpeg','image/gif','image/webp'].includes(file.getMimeType())) continue;
    if (list.length >= 500) throw new Error('対象画像は500件までです。');
    const id = file.getId();
    const thumbUrl = `https://drive.google.com/thumbnail?id=${id}`;
    list.push([file.getName(), thumbUrl, path]);
  }

  const folders = folder.getFolders();
  while (folders.hasNext()) {
    const sub = folders.next();
    const subPath = `${path}/${sub.getName()}`;
    getFilesRecursively_(sub, subPath, list, state, depth + 1);
  }
}

/* =====================================================================
 * Web アプリ公開
 * ===================================================================*/
function doGet () {
  return HtmlService
    .createTemplateFromFile('index')
    .evaluate();
}

/* 新しいタブで Web アプリを開く(メニュー用)*/
function openViewer_ () {
  const url = SpreadsheetApp.getActiveSpreadsheet()
               .getSheetByName('ギャラリー')
               .getRange('A1')
               .getValue();

  if (!/^https:\/\/script\.google\.com\/macros\/s\/[A-Za-z0-9_-]+\/exec$/.test(String(url))) {
    throw new Error('ギャラリー!A1にWebアプリのexec URLを設定してください。');
  }
  const safeUrl = JSON.stringify(String(url));
  const script = `
    <script>
      const opened = window.open(${safeUrl}, '_blank');
      if (opened) opened.focus();
      google.script.host.close();
    </script>`;

  SpreadsheetApp.getUi()
    .showModalDialog(HtmlService.createHtmlOutput(script), 'CardIllustViewer');
}

/* =====================================================================
 * カスタムメニュー
 * ===================================================================*/
function onOpen () {
  SpreadsheetApp.getUi().createMenu('カスタムメニュー')
    .addItem('ギャラリーを表示',      'openViewer_')
    .addItem('画像パスの取得', 'exportDriveImages_')
    .addToUi();
}

/* =====================================================================
 * データ取得 API(HTML から呼び出し)
 * ===================================================================*/
function getSSValues () {
  return JSON.stringify(readSheetObjects_(SHEET_DATA_MAIN));
}

/* =====================================================================
 * 共通ユーティリティ
 * ===================================================================*/
/** シート取得 */
function getSheet_ (name) {
  const ss = SpreadsheetApp.openById(SS_ID);
  const sheet = ss.getSheetByName(name);
  if (!sheet) throw new Error('シートがありません: ' + name);
  return sheet;
}

/** ヘッダー行をキーに行データをオブジェクト配列化 */
function readSheetObjects_ (sheetName) {
  const sheet = getSheet_(sheetName);
  if (sheet.getLastRow() > 501) throw new Error('管理データは500件までです。');
  const expected = ['id','file_name','item_name','image_path','tag','rare','type'];
  const [header, ...rows] = sheet.getRange(1, 1, Math.max(sheet.getLastRow(), 1), 7).getDisplayValues();
  if (header.join('|') !== expected.join('|')) throw new Error('管理シートの7列の見出しを確認してください。');
  return rows.map(row =>
    header.reduce((obj, key, idx) => {
      obj[String(key)] = String(row[idx]);
      return obj;
    }, {}));
}

/** HTML 部品の include_ 用 */
function include_ (file) {
  return HtmlService.createHtmlOutputFromFile(file).getContent();
}

index.htmlの実装

ギャラリー画面を描画するメインのHTMLファイルです。

コード内のシートURL指定箇所は、ご自身の環境に合わせて書き換えてください。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>画像一覧</title>

    <!-- CSS & プラグイン読込  -->
    <?!= include_('plugin') ?>
    <?!= include_('css') ?>
  </head>
  <body>
  <!-- ===== フィルター・操作エリア ===== -->
  <header id="header" class="d-flex flex-wrap gap-2 mb-3">
    <select id="filter"        class="selectpicker" multiple title="フィルター"
            data-actions-box="true" data-selected-text-format="count > 3" size="4"></select>

    <select id="select_chara"  class="selectpicker" multiple title="アイテム名"
            data-actions-box="true" data-selected-text-format="count > 3"></select>

    <select id="select_tag"    class="selectpicker" multiple title="タグ"
            data-live-search="true" data-actions-box="true"
            data-live-search-placeholder="Search" data-selected-text-format="count > 3"></select>

    <button id="btn-search" class="btn btn-success">検索</button>
    <button class="btn btn-info" onclick="window.open('https://docs.google.com/spreadsheets/d/xxxxxxxxx/edit?gid=0#gid=0')">シート</button>
  </header>
  <!-- ================= ページャ ================= -->
  <nav id="pager">
    <ul id="pagenation"></ul>
  </nav>
    <!-- ================= 画像グリッド ================= -->
   <main>
    <ul id="grid"></ul>
  </main>
  
    <!-- ================= 画像モーダル ================= -->
    <div class="iziModal"></div>

    <!-- ================= JavaScript ================= -->
    <?!= include_('js') ?>
</body>
</html>

js.htmlの実装

フロントエンド側の表示制御を行うJavaScriptコードです。

rareとtypeは列ごとに判定し、異なる列の条件は両方を満たす画像に絞ります。

同じ列で複数選んだ場合とタグを複数選んだ場合は、そのどれかに一致する画像が対象です。

<script>
(() => {
  /* ------------------------------------------------------------------
   * 1. 定数・キャッシュ
   * ------------------------------------------------------------------*/
  const PER_PAGE      = 18;

  // jQuery オブジェクトのキャッシュ
  const $grid   = $('#grid');           // 画像グリッド
  const $pager  = $('#pagenation');     // ページャ
  const $filter = $('#filter');
  const $chara  = $('#select_chara');
  const $tags   = $('#select_tag');
  const $modal  = $('.iziModal');

  /* ------------------------------------------------------------------
   * 2. 状態変数
   * ------------------------------------------------------------------*/
  let allData   = [];   // 管理シートの7列(最大500件)
  let listData  = [];   // 画像URLとアイテム名のあるデータ
  let filterKeys = ["rare", "type"];
  let chunks    = [];   // ページング用
  let selFilter = [], selChara = [], selTags = [];

  /* ------------------------------------------------------------------
   * 3. 初期化
   * ------------------------------------------------------------------*/
  $(async () => {
    $('#btn-search').on('click', onSearchClick);

    try { initModal(); await loadSheetData(); await buildUI(); }
    catch (_) { $grid.empty().append($('<li>').text('初期化に失敗しました。ライブラリの読込み、シートの設定、実行権限を確認してください。')); }
  });

  /* ------------------------------------------------------------------
   * 4. モーダル初期化
   * ------------------------------------------------------------------*/
  function initModal () {
    const $modal = $('.iziModal');
    const $grid  = $('#grid');

    $modal.iziModal({
      maxWidth : 0,        // 0 = 制限なし(プラグイン推奨値)
      maxHeight: '90%',    // 高さは 90% に制限
      width    : null,     // 自動
      transitionIn: 'fadeIn',
      onClosed   : () => $modal.iziModal('setContent', '')
    });

    $grid.on('click', 'img.card', function () {
      const full = $(this).data('full');
      const alt  = this.alt;
      const $modal = $('.iziModal');

      const img = new Image();
      img.onload = function () {
        const html = $('<img>', {src: full, alt: alt, class: 'modal_card'})[0].outerHTML;
        $modal.iziModal('setContent', html);
        $modal.iziModal('open');
      };
      img.onerror = () => alert('画像を取得できません。閲覧権限とログイン状態を確認してください。');
      img.src = full;
    });

  }

  /* ------------------------------------------------------------------
   * 5. データ取得
   * ------------------------------------------------------------------*/
    async function loadSheetData () {
      const json = await new Promise((resolve, reject) => {
        google.script.run
          .withSuccessHandler(resolve)
          .withFailureHandler(reject)
          .getSSValues();
      });

      allData = JSON.parse(json);

      listData = allData.filter(d =>
        getThumb(d) && d.item_name
      );
    }

  /* ------------------------------------------------------------------
   * 6. UI 構築
   * ------------------------------------------------------------------*/
  async function buildUI () {
    /* プルダウン ---------------- */
    const charaSet = uniqCompact(propArray(allData, 'item_name'));
    populateSelect($chara, charaSet);

    /* タグプルダウン ------------------ */
    const tagSet = uniqCompact(
      flatMap(listData, d => splitCSV(d.tag))
    );
    populateSelect($tags, tagSet);

    /* 絞り込みキー -------------------- */
    refreshFilterSelect();

    /* 一覧 & ページング --------------- */
    listData = _.sortBy(listData, d => -d.id);
    renderPage(listData);
  }

  /* ------------------------------------------------------------------
   * 7. 検索処理
   * ------------------------------------------------------------------*/
  function onSearchClick () {
    selFilter = $filter.val() || [];
    selChara  = $chara.val()  || [];
    selTags   = $tags.val()   || [];

    const filtered = listData
      .filter(d => !selChara.length || selChara.includes(d.item_name))
      .filter(d => filterRow(d, selFilter))
      .filter(d => tagMatch(d.tag, selTags));

    renderPage(filtered);
  }

  function filterRow (row, selected) {
    if (!selected.length) return true;

    return filterKeys.every(key => {
      const keyOpts = getColumnValues(key);
      const hit = keyOpts.filter(value => selected.includes(JSON.stringify([key, value])));
      if (!hit.length) return true;

      const rowVals = splitCSV(row[key]);
      return rowVals.some(v => hit.includes(v));
    });
  }

  function tagMatch (tagStr, selected) {
    if (!selected.length) return true;
    const tagArr = splitCSV(tagStr);
    return tagArr.some(t => selected.includes(t));
  }

  /* ------------------------------------------------------------------
   * 8. 描画関連
   * ------------------------------------------------------------------*/
  function renderPage (data) {
    const firstPage = setupPagination(data);
    renderGrid(firstPage);
  }

  function renderGrid(data) {
    $grid.empty();
    if (!data.length) {
      $('<li>').text('一致する画像がありません。条件を解除するか、管理シートの画像URLとアイテム名を確認してください。').appendTo($grid);
      return;
    }

    data.forEach(d => {
      const $li = $('<li>', {
        class: 'list_item',
        name : d.file_name,
        alt  : d.item_name,
        'data-id': d.id,
        'data-name': d.file_name,
        'data-alt': d.item_name
      });

      const thumb = getThumb(d); 
      const full  = getFull(d);

      if (!thumb) return;

      $('<img>', {
        class : 'card',
        src   : thumb,
        'data-full': full,
        alt   : d.file_name,
        loading : 'lazy'
      }).appendTo($li);

      $grid.append($li);
    });
  }

  function setupPagination (data) {
    if ($pager.data('twbs-pagination')) $pager.twbsPagination('destroy');

    if (data.length <= PER_PAGE) {
      $pager.hide();
      return data;
    }

    chunks = _.chunk(data, PER_PAGE);
    $pager.twbsPagination({
      totalPages  : chunks.length,
      visiblePages: 5,
      onPageClick : (_, page) => renderGrid(chunks[page-1])
    });
    $pager.show();
    return chunks[0];
  }

  /* ------------------------------------------------------------------
   * 9. フィルターセレクト生成
   * ------------------------------------------------------------------*/

  function refreshFilterSelect () {
    $filter.empty();
    filterKeys.forEach(key => {
      const opts = getColumnValues(key);
      const $group = $('<optgroup>', { label: key });
      opts.forEach(v => $('<option>', { value: JSON.stringify([key, v]), text:v }).appendTo($group));
      $group.appendTo($filter);
    });
    $filter.selectpicker('refresh');
  }

  /* ------------------------------------------------------------------
   * 10. ユーティリティ
   * ------------------------------------------------------------------*/
  const flatMap     = (arr, fn) => [].concat(...arr.map(fn));   // lodash 代替
  const uniqCompact = arr => _.uniq(arr.filter(Boolean));       // 空文字除外+重複排除
  const propArray   = (arr, key) => arr.map(o => o[key]);       // プロパティ抽出
  const toBool = v => String(v).toLowerCase() === 'true';
  const splitCSV = str => String(str || '').split(',').map(v => v.trim()).filter(Boolean);
  function imageUrl(d, size) {
    try {
      const url = new URL(d.image_path);
      if (url.origin !== 'https://drive.google.com' || url.pathname !== '/thumbnail') return '';
      if (!/^[A-Za-z0-9_-]+$/.test(url.searchParams.get('id') || '')) return '';
      const clean = new URL('https://drive.google.com/thumbnail');
      clean.searchParams.set('id', url.searchParams.get('id'));
      clean.searchParams.set('sz', size);
      return clean.href;
    } catch (_) { return ''; }
  }
  const getThumb = d => imageUrl(d, 'w400');
  const getFull = d => imageUrl(d, 'w1920');

  function populateSelect ($sel, items) {
    $sel.empty();
    items.forEach(v => $('<option>', { value:v, text:v }).appendTo($sel));
    $sel.selectpicker('refresh');
  }

  function getColumnValues (key) {
    return uniqCompact(
      flatMap(listData, d => splitCSV(d[key]))
    );
  }

})();
</script>

動作確認とデプロイ

5ファイルを保存したらスプレッドシートを開き直し、「カスタムメニュー」が表示されるか確認します。

公式のカスタムメニュー資料では、ファイルを開く際のonOpenでメニューを追加します。

「画像パスの取得」を実行し、必要な権限を確認して承認してください。

取得用シートのファイル名と画像URLを管理シートへ転記し、item_nameも入力します。

その後「新しいデプロイ」でWebアプリを選び、実行者を自分、アクセスできるユーザーを自分のみにします。

本人限定にできない環境では公開を進めないでください。

発行されたURLを「ギャラリー」シートのA1に入れ、メニューまたはURLから開きます。

コード変更後はデプロイのバージョンも更新してください。

まず2枚の画像と名称がそろい、画像を押すと拡大表示されるか確認します。

タグ1つで検索し、一致する画像だけになることと、条件を外すと2枚へ戻ることを確かめてください。

ページ分割は19件以上の練習データで、前後のページへ移動できるか確認します。

Webアプリとしてデプロイし、画像一覧や絞り込み検索の動作を確認しているブラウザ画面。

本例の drive.google.com/thumbnail は恒久的な画像配信用APIとしての動作を保証できません。

画像が表示されない場合はログイン状態・閲覧権限・ブラウザの制限を確認し、表示のためだけに画像を全公開しないでください。

Drive APIのthumbnailLinkも短命で認証条件があり、そのままのWebアプリ埋め込みは推奨されていません。

Driveの公式仕様とGASの実行権限を確認してください。

2026年10月25日に公式資料とコードを照合し、絞り込み関数をローカルで検証しました。

GAS実行・認証画像の表示・CDNの相互動作・画面操作は今回も未検証です。

画像が0件の場合は、管理シートの7列の見出し、画像URL、アイテム名、検索条件を確認してください。

取得用A〜C列の更新は一括で元に戻る処理ではなく、途中失敗で部分的に書き換わる場合があります。

失敗時は追加実行を止め、保存したコピーと比較して取得用シートを復旧してください。

メニューから開けない場合は、A1のURLをブラウザーのアドレス欄へ直接貼って切り分けます。

コードとギャラリーのプレビューを並べて作業する

GASのエディタとWebアプリの動作確認画面を同時に表示しづらい場合は、ブラウザの分割表示やサブモニターの導入が便利です。

運営メディア「デザノマ」のコードとプレビューを並べるモニターの選び方に関する記事も参考にしてみてください。

関連コードは、GitHubのMOTOKI-LLC/cg-method-codeにもあります。

更新時期が異なるため、この手順では本文の5ファイルを一式で使用してください。

GASでウェブアプリケーションを作る記事は、ほかにもあります。

次に学ぶ・作業環境を選ぶ

学習を続けたい方や、作業環境を整えたい方は、目的に合うガイドをご覧ください。

スプレッドシート・GASのおすすめ書籍

作業環境の作り方

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次