
【JavaScript入門】画面レイアウト(index.htmlとstyle.css)
ここでは、画像付きミニブログ SPA「みんなのつぶやき」の 画面レイアウト を担う index.html
と style.css
を実装します。
主役となるのは
- 画像アップロード&プレビュー
- エフェクト操作ボタン
- コメント入力・投稿
- タイムライン(投稿一覧)
という 4 つの UI ブロックです。フロントエンドは pure HTML/CSS/JavaScript で完結し、バックエンド(index.js
)は今回は触れません。まずはフォルダ構成を再確認しておきましょう。
node-js/
├─ app/
│ ├─ index.html ← ここで作成
│ ├─ style.css ← ここで作成
│ └─ js/ ← JS モジュール群
│ ├─ common.js
│ ├─ storage.js
│ ├─ imageArea.js
│ ├─ imageEffects.js
│ ├─ controlArea.js
│ ├─ commentArea.js
│ ├─ sendArea.js
│ ├─ dateFormat.js
│ └─ postListArea.js
├─ index.js ← API/サーバ側
├─ upload_images/ ← 画像保存場所
└─ post-dat.txt ← 投稿データ(JSON など)

HTML/CSS の主な要素と役割
種別 | セレクタ / タグ | 画面上の部品 | 概要 |
---|---|---|---|
レイアウト | header | ページ最上部のタイトル帯 | タイトル文字とアイコンを中央配置 |
main#wrap | 4 つのカードを縦に並べるラッパ | flex +gap で余白管理 | |
画像エリア | section#imageArea | ドラッグ&ドロップ領域 + Canvas | 画像未選択時は #noView を表示 |
canvas#view | アップロード画像のプレビュー | JS 側で display:block に切替 | |
操作ボタン | section#controlArea | セピア/グレースケール等 6 ボタン | grid で自動段組み |
button | 各種アクション | transition で hover 演出 | |
コメント | section#commentArea | テキストエリア | 入力文字数を #commentStatus に反映 |
投稿 | section#sendArea | 投稿ボタン | 押下で送信処理 |
タイムライン | section#postListArea | 投稿一覧 | JS が .postListItem を追加 |
色・装飾 | :root 変数 | --primary , --radius など | 全体の配色/角丸を一元管理 |
index.html(コメント付き)
<!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>
<!-- ===== ヘッダー: タイトル帯 ===== -->
<header>
<!-- h1 はページ名。絵文字📸はカメラアイコン -->
<h1>📸 みんなのつぶやき</h1>
</header>
<!-- ===== メインコンテンツラッパ ===== -->
<main id="wrap">
<!-- === 1. 画像アップロード + プレビュー === -->
<section id="imageArea" class="card">
<!-- 画像が未選択のときだけ見せるガイド -->
<div id="noView">
<span>ここに画像をドラッグ&ドロップ<br>またはクリックして選択</span>
</div>
<!-- 選択後に JS が描画する Canvas -->
<canvas id="view" width="800" height="420"></canvas>
<!-- 実際のファイル選択 input。label を使わずエリアクリックを
トリガにするため JS から `.click()` で呼び出す -->
<input type="file" id="file" accept=".jpg,.jpeg,.png,.gif" />
</section>
<!-- === 2. エフェクト操作ボタン === -->
<section id="controlArea" class="card btn-grid">
<!-- 各ボタンは imageEffects.js がクリックイベントを束ねる -->
<button id="efSepia">セピア</button> <!-- 色味をセピア化 -->
<button id="efGray">グレースケール</button><!-- モノクロ化 -->
<button id="efBlur">ぼかし</button> <!-- ガウスぼかし -->
<button id="efBack">元に戻す</button> <!-- エフェクト解除 -->
<button id="efSave">保存</button> <!-- 加工後画像を保存 -->
<button id="efDel">削除</button> <!-- アップロード取り消し -->
</section>
<!-- === 3. コメント入力 === -->
<section id="commentArea" class="card">
<label>コメント <span id="commentStatus"></span></label>
<!-- data-* 属性は commentArea.js が初期値・最大長を参照 -->
<textarea id="comment"
data-default="こんにちは!"
data-lenmax="100"></textarea>
</section>
<!-- === 4. 投稿ボタン === -->
<section id="sendArea" class="card">
<button id="send">投稿する</button>
</section>
<!-- === 5. タイムライン === -->
<section id="postListArea" class="card"></section>
</main>
<!-- ===== JavaScript モジュール群 =====
※依存関係が無いようグローバル関数化済み -->
<script src="js/common.js"></script> <!-- 汎用関数 -->
<script src="js/storage.js"></script> <!-- LocalStorage ラッパ -->
<script src="js/imageArea.js"></script> <!-- アップロード処理 -->
<script src="js/imageEffects.js"></script> <!-- Canvas エフェクト -->
<script src="js/controlArea.js"></script> <!-- ボタン制御 -->
<script src="js/commentArea.js"></script> <!-- 文字数カウント等 -->
<script src="js/sendArea.js"></script> <!-- 送信処理 -->
<script src="js/dateFormat.js"></script> <!-- 日付フォーマッタ -->
<script src="js/postListArea.js"></script> <!-- タイムライン描画 -->
</body>
</html>
画面部品とタグの対応
- 画像未選択ガイド (
#noView
)
破線枠+中央の<span>
が “ドロップしてください” の案内。 - プレビュー (
#view
)
JS 側でdisplay:block
に切り替え。アップロード後は#noView
をdisplay:none
に。 - 操作ボタン群 (
#controlArea
の<button>
)
6 個のボタンをgrid
で自動折り返し。スマホでも 2–3 列に配置。 - コメント入力 (
#comment
)
入力長を監視し、残り字数を#commentStatus
に反映。 - 投稿タイムライン (
#postListArea
)
JS が.postListItem
を append。内部に画像 (.plImage
)・本文 (.plBody
)・日時 (.plDate
)・いいねボタン (.likeBtn
) を生成。
style.css(コメント付き)
/* ===== 共通カラーパレット ===== */
:root{
--bg-main:#f1f5f9; /* ページ背景 (淡いグレー) */
--card-bg:#ffffff; /* カード背景 (白) */
--primary:#00aaff; /* アクション用ブルー */
--danger :#ff4444; /* 警告用レッド */
--text :#333333; /* 標準文字色 */
--radius :.65rem; /* 角丸半径 */
}
/* ===== リセット & ベース ===== */
*{box-sizing:border-box;margin:0;padding:0;}
body{
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
background:var(--bg-main);
color:var(--text);
line-height:1.6;
}
header{
background:var(--primary); /* 見出し帯をブランドカラーで塗る */
color:#fff;
text-align:center;
padding:.8rem;
}
h1{font-size:1.4rem;letter-spacing:.05em;}
/* ===== レイアウト ===== */
#wrap{
max-width:720px; /* スマホ~タブレット幅で中央寄せ */
margin:0 auto;
padding:1rem;
display:flex;
flex-direction:column;
gap:1rem; /* 各カード間の余白 */
}
.card{
background:var(--card-bg);
border-radius:var(--radius);
padding:1rem;
box-shadow:0 4px 8px rgb(0 0 0 / .05);
}
.btn-grid{
display:grid;
gap:.5rem;
grid-template-columns:repeat(auto-fill,minmax(6rem,1fr));
}
/* ===== 画像プレビュー領域 ===== */
#noView{
position:relative;
display:flex;
justify-content:center;
align-items:center;
text-align:center;
color:#666;
height:0;
padding-top:52.5%; /* 16:9 比率をパディングで確保 */
border:2px dashed #ccc; /* ドラッグ可の示唆に破線 */
border-radius:var(--radius);
background:#fafafa;
}
#noView span{
position:absolute;
top:50%;left:50%;
transform:translate(-50%,-50%);
}
#view{
display:none; /* 初期は隠す → JS で表示 */
width:100%;
border-radius:var(--radius);
}
#file{display:none;} /* input は画面に出さない */
/* ===== ボタン ===== */
button{
all:unset; /* ブラウザ既定スタイルを除去 */
background:var(--primary);
color:#fff;
text-align:center;
padding:.75rem .5rem;
border-radius:var(--radius);
font-weight:bold;
cursor:pointer;
user-select:none;
transition:opacity .2s;
}
button:hover{opacity:.85;} /* うっすら暗く */
/* ===== コメント ===== */
textarea{
width:100%;height:5em;
padding:.5rem;
border:1px solid #ccc;
border-radius:var(--radius);
resize:vertical;
font-family:inherit;
}
.inputStatus{font-size:.85rem;color:#666;}
.inputStatusWarn{color:var(--danger);} /* 文字数超過時 */
/* ===== タイムライン ===== */
.postListItem{margin-bottom:1rem;} /* 投稿カード間隔 */
.plImage img{
width:100%;
border-radius:var(--radius);
}
.plBody{padding:.5rem 1rem;}
.plDate{
font-size:.8rem;
color:#666;
text-align:right;
}
.likeBtn{
all:unset;
display:inline-flex;
align-items:center;
gap:.2rem;
cursor:pointer;
font-size:1rem;
color:#ff6584; /* 通常のハート色 */
}
.likeBtn.liked{
color:#ff0033; /* いいね済みで彩度アップ */
font-weight:bold;
}
どこを装飾しているか
- :root の CSS 変数 – 色や角丸を一元管理。将来テーマ変更が楽。
- #wrap – 最大幅を 720 px に制限し中央寄せ。モバイルでも余白確保。
- .card – すべての UI ブロックを「カード」化して影と角丸を付与。
- #noView / #view – 未選択ガイドとプレビュー Canvas の表示切替。
padding-top:52.5%
で 16:9 アスペクトを保つテクニックはレスポンシブ対応の肝。 - .btn-grid – grid-template-columns: repeat(auto-fill, minmax(6rem,1fr)) でボタンが画面幅に合わせて自動で折り返す。
- .likeBtn – inline-flex でハート+数字を水平配置し、状態に応じて色&太字切替。
主要タグ/CSS のポイントまとめ
キー | ポイント |
---|---|
<canvas id="view"> | 画像プレビューを JavaScript で直接描画。固定サイズではなく幅 100% に縮放。 |
padding-top:52.5% | 高さ可変の 16:9 枠を純 CSS で実現(padding trick)。 |
grid-template-columns:repeat(auto-fill,minmax(6rem,1fr)) | 可変列グリッドでボタンをレスポンシブに並べ替え。 |
CSS 変数 (--primary, --radius など) | 色・角丸・影を一箇所で管理し 保守性を高める。 |
.likeBtn.liked | classList.toggle() だけで 状態変化演出(色+太字)が完結。 |
次回は ページ全体で共通利用するユーティリティ (common.js
) と日付フォーマッタ (dateFormat.js
) の内部構造と実装ポイントを解説していきます。