1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS】相互評価システムを作る (3) デザイン編:スマホでも快適なレスポンシブCSS

Last updated at Posted at 2025-12-05

これまでの記事で、サーバーサイド(GAS)とフロントエンド(HTML/JS)のロジックが完成しました。 最後は、デザイン(CSS) です。
今回は、CSSフレームワーク(Bootstrapなど)を使わずに、たった1つのCSSファイル で、PCでもスマホでも快適に動作するレスポンシブデザインを実装します。

🎨 デザインのこだわりポイント

  • スマホ対応(レスポンシブ): PCでは「表形式」、スマホでは「カード形式」にレイアウトが激変します
  • Googleライクな配色: Googleの標準的な青(#4285F4)やグレーを使い、違和感のないUIにします
  • 視覚的なフィードバック: エラー時の赤枠表示や、ローディング中の砂時計アニメーションなど、状態をわかりやすく伝えます

📱 テクニック解説:テーブルを「カード」に変身させる

このアプリの最大の特長は、

タグの表示崩れを防ぐテクニック です。 通常、スマホで横長のテーブルを表示すると画面からはみ出してしまいます。
そこで、CSSのメディアクエリ(@media)を使って、スマホ幅(600px以下)のときだけ テーブルの構成要素をすべてブロック要素(display: block) に強制変更します。
/* スマホ表示 (600px以下) */
@media screen and (max-width: 600px) {
  /* 1. テーブルのセルなどを強制的にブロック要素(積み木状態)にする */
  .form-table, tbody, tr, td {
    display: block;
    width: 100%;
  }

  /* 2. ヘッダー(PC用の項目名)は隠す */
  .form-table thead {
    display: none;
  }

  /* 3. 行(tr)をカードのように見せる */
  .form-table tr {
    margin-bottom: 20px;
    border: 1px solid #ddd;
    box-shadow: 0 2px 4px rgba(0,0,0,0.05); /* 影をつける */
  }
}

これにより、HTML構造を変えることなく、CSSだけで「PC:横並びの表」「スマホ:縦積みのカード」という2つの顔を持たせることができます。

👆 操作性の向上(タッチターゲット)

スマホでの回答しやすさを考慮し、ラジオボタンやタップ領域を拡大しています。

/* スマホ時はラジオボタンを1.4倍に拡大 */
input[type="radio"] {
  transform: scale(1.4); 
}

/* ラベル全体をクリック可能にして、押し間違いを防ぐ */
.radio-label {
  padding: 12px 0;
  width: 100%;
  height: 100%;
}

📝 ソースコード全文 (css.html)

GASではCSSファイルを直接読み込めないため、便宜上、拡張子を.html とし、ファイル名は css.html としてください。CSSはstyleタグで囲んでください。
css.html
<style>
  /* =========================================
     1. リセット & 基本設定
     ========================================= */
  *, *::before, *::after {
    box-sizing: border-box;
  }

  body {
    font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
    color: #333;
    line-height: 1.6;
    margin: 0;
    background-color: #f0f0f0;
  }

  main {
    display: flex;
    flex-direction: column;
    max-width: 1080px;
    min-height: calc(100vh - 32px);
    margin: 16px auto;
    padding: 16px;
    background: #fff;
    box-shadow: 0 0 10px rgba(0,0,0,0.05);
  }

  /* =========================================
     2. ヘッダーブロック
     ========================================= */
  .header-block {
    background: #fff;
    position: sticky;
    margin-bottom: 16px;
    top: 0;
    z-index: 100;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  }

  .header-inner {
    padding: 8px 16px;
    background-color: #f0f8ff; /* aliceblue */
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    border-bottom: 1px solid #ddd;
  }

  .header-inner h1 {
    margin: 0;
    font-size: 1.2rem;
    border-left: 5px solid #4285F4;
    padding-left: 10px;
  }

  .header-right {
    text-align: right;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 5px;
  }

  .user-info {
    font-size: 0.9rem;
    color: #555;
    font-weight: bold;
  }

  /* =========================================
     3. コンテンツブロック & 見出し
     ========================================= */
  .content-block {
    flex: 1;
    padding: 0;
    animation: fadeIn 0.3s ease;
  }
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

  h2 {
    font-size: 1.1rem;
    font-weight: bold;
    border-bottom: 2px solid #4285F4;
    padding-bottom: 5px;
    margin: 0 0 16px 0;
  }

  h3 {
    font-size: 1rem;
    margin: 0 0 8px 0;
    padding-left: 8px;
    border-left: 4px solid #ccc;
    color: #444;
  }

  hr {
    border: 0;
    border-top: 1px solid #eee;
    margin: 30px 0;
  }

  /* =========================================
     4. テーブルスタイル (PCベース)
     ========================================= */
  table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 16px;
    font-size: 0.95rem;
  }

  th, td {
    padding: 8px 12px; /* 高さを揃える */
    text-align: left;
    border-bottom: 1px solid #eee;
    vertical-align: middle;
  }

  th {
    background-color: #f0f8ff;
    font-size: 0.9em;
    font-weight: bold;
    white-space: normal;
    width: 30%;
  }

  tr:nth-child(even) {
    background-color: #fafafa;
  }
  
  tbody tr:hover {
    background-color: #f8f9fa;
  }

  /* 列幅調整(上書き用クラス) */
  .column-title { width: auto; }
  .column-question { width: 50%; min-width: 150px; font-size:0.9em;}
  .column-option { text-align: center; width: 60px; }
  
  .cell-center { text-align: center; }
  .row-self { background-color: #eee; color: #777; }

  /* クリック可能な行の設定 */
  .clickable-row {
    cursor: pointer;
    transition: background-color 0.2s;
  }
  .clickable-row:hover {
    background-color: #e8f0fe !important; /* 薄い青 */
  }

  /* --- エラー行のスタイル --- */
  tr.error-row,
  tr.error-row:nth-child(even),
  tr.error-row:hover {
    background-color: #ffe6e6 !important; /* 薄い赤 */
    transition: background-color 0.2s;
  }

  /* =========================================
     5. フォーム要素
     ========================================= */
  .long-item { display: inline-block; }
  .short-item { display: none; }

  textarea {
    display: block;
    width: 100%;
    min-height: 80px;
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    resize: vertical;
    font-family: inherit;
  }

  .radio-label {
    cursor: pointer;
    display: block;
    width: 100%;
    padding: 0;
  }
  
  input[type="radio"] {
    transform: scale(1.2);
    cursor: pointer;
    margin-right: 5px;
  }

  /* スマホ用ラベル(PCでは非表示) */
  .mobile-label {
    display: none;
    margin-left: 8px;
    font-size: 0.95rem;
    color: #333;
  }

  /* =========================================
     6. ボタン
     ========================================= */
  button {
    cursor: pointer;
    font-family: inherit;
    font-size: 0.95rem;
  }

  /* 通常ボタン */
  button:not(.link-style-button) {
    padding: 8px 16px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background-color: #fff;
    transition: background-color 0.2s;
  }
  button:not(.link-style-button):hover {
    background-color: #f0f0f0;
  }

  /* プライマリボタン */
  .primary-button {
    background-color: #4285F4 !important;
    color: white;
    border: none !important;
    font-weight: bold;
  }
  .primary-button:hover {
    background-color: #3367d6 !important;
  }

  /* リンク風テキスト・ボタン */
  .link-style-button, .link-style-text {
    background: none;
    border: none;
    padding: 0;
    color: #1a73e8;
    font-weight: bold;
    text-decoration: underline;
    cursor: pointer;
  }
  .link-style-button:hover {
    color: #174ea6;
  }
  .status-done {
    color: #555 !important;
    text-decoration: underline;
  }

  /* 無効化されたボタンのスタイル */
  button:disabled {
    background-color: #ccc !important;
    color: #666 !important;
    cursor: not-allowed !important;
    border: 1px solid #ccc !important;
  }

  /* =========================================
     7. レイアウト・結果表示
     ========================================= */
  .flex-space-between {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
  }
  
  .action-bar {
    margin-bottom: 15px;
  }

  .result-section-divider {
    margin-top: 30px;
    margin-bottom: 15px;
    padding: 8px 10px;
    background-color: #f1f3f4;
    border-radius: 4px;
  }

  .chart-wrapper {
    width: 100%;
    height: auto;
    min-height: 200px;
    border: 1px solid #eee;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .comment-wrapper {
    margin-bottom: 25px;
  }
  .comment-wrapper ul {
    padding-left: 20px;
    margin: 0;
  }
  .no-answer-text, .no-data-text {
    color: #888;
    font-size: 0.9rem;
    margin-left: 10px;
  }
  .text-alert { color: #d93025; }

  /* JS制御用 */
  .is-hidden { display: none !important; }
  .error-alert { color: #d93025; font-weight: bold; padding: 10px; }

  /* =========================================
     8. モーダル & アニメーション
     ========================================= */
  
  /* --- Loading Dialog (<dialog>タグ用) --- */
  dialog.loading-dialog {
    border: none;
    border-radius: 8px;
    padding: 40px 40px 10px 40px;
    background: white;
    box-shadow: 0 4px 15px rgba(0,0,0,0.2);
    text-align: center;
    min-width: 200px;
  }

  dialog::backdrop {
    background: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(2px);
  }

  .spinner {
    width: 40px; height: 40px; margin: 0 auto 15px;
    border: 4px solid #f3f3f3; 
    border-top: 4px solid #4285F4; 
    border-radius: 50%;
    animation: spin 1.2s linear infinite;
  }
  @keyframes spin {to {rotate: 1turn;}}

  /* --- Common Dialog (共通ダイアログ用) --- */
  .dialog-overlay {
    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
    background-color: rgba(0,0,0,0.5);
    z-index: 2000; /* ヘッダーより前面に */
    display: flex; justify-content: center; align-items: center;
    opacity: 0; animation: fadeInOverlay 0.3s forwards;
  }

  .dialog-box {
    background: white; width: 90%; max-width: 400px;
    border-radius: 8px; overflow: hidden;
    box-shadow: 0 10px 25px rgba(0,0,0,0.3);
    transform: translateY(-20px); animation: slideDownBox 0.3s forwards;
    display: flex; flex-direction: column;
  }

  .dialog-header { 
    padding: 15px 20px; 
    display: flex; align-items: center; 
    color: white; 
    font-weight: bold;
  }
  
  .dialog-icon { margin-right: 10px; font-size: 1.4em; }
  .dialog-header h3 { 
    margin: 0; 
    font-size: 18px; 
    border: none; padding: 0; color: white; /* h3のデフォルト上書き */
  }

  .dialog-content { 
    padding: 20px 25px; 
    font-size: 15px; 
    line-height: 1.6; 
    color: #333; 
    background: #fff;
  }

  .dialog-footer { 
    padding: 10px 15px 15px; 
    text-align: right; 
    background: #f9f9f9;
    border-top: 1px solid #eee;
  }
  
  .dialog-footer button {
    padding: 8px 24px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;
    background-color: #e0e0e0; color: #333; transition: 0.2s;
    font-weight: bold;
  }
  .dialog-footer button:hover { background-color: #d0d0d0; }

  /* --- タイプ別カラー設定 --- */
  
  /* Info: 青系 */
  //.dialog-box.type-info .dialog-header { background-color: #4285F4; }
  .dialog-box.type-info .dialog-header { background-color: #366fce; }
  .dialog-box.type-info .dialog-footer button { color: #4285F4; background-color: #fff; border: 1px solid #4285F4; }
  .dialog-box.type-info .dialog-footer button:hover { background-color: #e8f0fe; }

  /* Warning: 黄色・オレンジ系 */
  //.dialog-box.type-warning .dialog-header { background-color: #fbbc05; color: #333; } /* テキスト黒の方が見やすい */
  .dialog-box.type-warning .dialog-header { background-color: #eaea00; color: #333; } /* テキスト黒の方が見やすい */
  .dialog-box.type-warning .dialog-header h3 { color: #333; }
  .dialog-box.type-warning .dialog-footer button { color: #b08c00; background-color: #fff; border: 1px solid #fbbc05; }
  .dialog-box.type-warning .dialog-footer button:hover { background-color: #fff8e1; }

  /* Error: 赤系 */
  //.dialog-box.type-error .dialog-header { background-color: #ea4335; }
  .dialog-box.type-error .dialog-header { background-color: #e34c15; }
  .dialog-box.type-error .dialog-footer button { color: #ea4335; background-color: #fff; border: 1px solid #ea4335; }
  .dialog-box.type-error .dialog-footer button:hover { background-color: #fce8e6; }

  /* アニメーション定義 */
  @keyframes fadeInOverlay { to { opacity: 1; } }
  @keyframes slideDownBox { to { transform: translateY(0); } }


  /* =========================================
     9. レスポンシブ (スマホ: 960px以下)
     ========================================= */
  @media screen and (max-width: 1080px) {
    main {margin: 0;}
  }

  @media screen and (max-width: 980px) {
    .long-item { display: none; }
    .short-item { display: inline-block; }
  }

  @media screen and (max-width: 600px) {
    main {
      margin: 0;
    }

    /* ヘッダーの縦並び */
    .header-inner {
      flex-direction: column;
      align-items: flex-start;
    }
    .header-right {
      width: 100%;
      flex-direction: row;
      justify-content: space-between;
      margin-top: 10px;
    }

    .content-block {
      padding: 0;
    }
    /* ボタンエリアの調整 */
    .flex-space-between {
      flex-direction: column-reverse;
      gap: 10px;
    }
    .flex-space-between button {
      width: 100%;
      padding: 12px 16px;
    }
    
    /* 基本のテーブルセルには8pxの余白を与える(評価対象一覧用) */
    td, th { padding: 8px; }

    /* --- 入力フォームのカード化 --- */
    
    /* カード型にするテーブル内だけは余白をリセットする */
    .form-table td, .form-table th {
      padding: 0;
    }

    /* 1. ヘッダー非表示 */
    .form-table thead {
      display: none;
    }

    /* 2. ブロック要素化して縦積み */
    .form-table, .form-table tbody, .form-table tr, .form-table td {
      display: block;
      width: 100%;
    }

    /* 3. 質問ごとのカード化 */
    .form-table tr {
      margin-bottom: 20px;
      border: 1px solid #ddd;
      border-radius: 8px;
      background-color: #fff;
      box-shadow: 0 2px 4px rgba(0,0,0,0.05);
      padding: 0;
      overflow: hidden;
    }

    /* 4. 質問文のデザイン(ヘッダー) */
    .td-question-text {
      font-weight: bold;
      background-color: #f0f8ff;
      border-bottom: 1px solid #eee;
      margin: 0;
      padding: 12px 15px !important; /* 詳細度で負けないよう念のため !important */
    }

    /* 5. ラジオボタンセルのデザイン(中身) */
    .td-radio {
      border-bottom: 1px solid #f5f5f5;
      text-align: left;
      padding: 0 10px !important;
    }
    .td-radio:last-child {
      border-bottom: none;
    }

    /* 6. タップ領域の拡大 */
    .radio-label {
      display: flex;
      align-items: center;
      padding: 12px 0;
      width: 100%;
      height: 100%;
      cursor: pointer;
    }
    
    input[type="radio"] {
      transform: scale(1.4);
      margin-right: 0px;
    }

    /* 7. スマホ用ラベル表示 */
    .mobile-label {
      display: inline-block;
    }

    /* 8. テキストエリア調整 */
    .td-textarea {
      border: none;
      padding: 10px 15px 15px 15px !important;
    }
    .td-textarea textarea {
      border: 1px solid #eee;
    }

    /* 9. スマホ表示時のエラー行調整 */
    .form-table tr.error-row,
    .form-table tr.error-row:nth-child(even) {
      border: 2px solid #d93025 !important; /* 赤い枠線 */
    }
    
    /* カードのヘッダー(質問文)の青色を赤色で上書き */
    .form-table tr.error-row .td-question-text {
      background-color: #fadbd8 !important;
      border-bottom: 1px solid #f5c6cb !important;
    }
  }
</style>

🚀 すべてのファイルを結合してデプロイ

これで以下の3つのファイルが揃いました。

  • main.gs: サーバーサイド(スプレッドシート操作、API)
  • index.html: フロントエンド(骨組み、JSロジック)
  • css.html: デザイン

デプロイ手順

スクリプトエディタのファイルリストにこれら3つがあることを確認します。
画面右上の 「デプロイ」 > 「新しいデプロイ」 をクリック。

  • 「ウェブアプリ」 を選択
  • 説明: (任意)
  • 次のユーザーとして実行: 自分
  • アクセスできるユーザー:組織内 Session.getActiveUser().getEmail()は組織内のメールアドレスしか取得できない
  • 「デプロイ」 ボタンをクリック
    初めてデプロイするときに承認のダイアログが表示されます。アクセスを承認してください。
    発行されたURLにスマホからアクセスしてみてください。 サクサク動く「相互評価アプリ」が完成しているはずです!

🎉 まとめ

全3回にわたり、GASで作るWebアプリ開発をご紹介しました。 特別な開発環境を用意しなくても、ブラウザとGoogleアカウントさえあれば、本格的な業務アプリを作れるのがGASの魅力です。

ぜひ、このコードをベースに「匿名評価モード」や「Slack通知機能」など、自分好みにカスタマイズして遊んでみてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?