画像をドラッグ&ドロップで並べ替えて、自分だけの「Tier表(ランク表)」を作れたら便利だと思いませんか?
本記事では、HTMLとJavaScriptだけで作れる「ブラウザ完結型のTier表作成ツール」の作り方をご紹介します。
このツールは次のような特徴があります。
- インストール不要、ブラウザだけで動作
- 画像をアップロード→並べ替え→PNGとして保存が可能
- ドラッグ&ドロップ対応(Sortable.js利用)で直感的に操作できる
- 画像はサーバーに送信しないため、安全に利用可能
完成版のコードはコピペで動作するので、初心者の方でも安心して試せます。
さらに、コードを読み解けば、Sortable.jsを使ったドラッグ&ドロップの基本も学べるので、学習素材としても最適です。
今回作るツールの概要
今回作成するのは、画像をTier(ランク)ごとに分類して並べ替えられるWebツールです。
Tier表とは、例えばゲームキャラクターやアイテムをランク付けして一覧化する際に使われる表のこと。
SNSやブログ、動画コンテンツなどでも人気の形式です。
作成するツールは次のような機能を持ちます。
- 画像のアップロード(PNG / JPEG対応、最大2MB・最大辺2000pxまで)
- ドラッグ&ドロップで自由に並べ替え
- 不要な画像の削除
- Tier表を1枚のPNG画像として書き出し
動作イメージ
- 画像をドラッグ&ドロップまたは「ファイルを選択」から読み込みます
- 作成したTier行に画像を配置
- 順番を変えたり、Tier間で移動させたりします
- 完成したら「PNG書き出し」ボタンで保存
【実際のTier表作成ツールの動作を試してみたい方はこちらからどうぞ】▼
Tier表はゲームのキャラクターランキングだけじゃなく、日常の分類や整理にも応用できるんですよ!

必要な準備
今回のTier表作成ツールは、HTML・CSS・JavaScript の3つのファイルで構成します。
さらに、ドラッグ&ドロップによる並び替えを簡単に実装するために Sortable.js というライブラリを使用します。
このライブラリを使うことで、複雑な処理を一から書かずに、短時間で安定した動作のドラッグ&ドロップ機能を作ることができます。
必要なもの
- パソコンとブラウザ
作成や動作確認はローカル環境(自分のPC)で行います。 - テキストエディタ
Visual Studio Code など、メモ帳でもOKです。 - Sortable.js のCDNリンク
以下のようにHTMLファイルから読み込みます。
1 |
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script> |
ポイント
ライブラリは自分でダウンロードして使うこともできますが、今回は手軽な CDN(インターネット経由で読み込む方法) を使います。
インストール不要なので、HTMLファイルを作ってブラウザで開くだけで動作します。
コード全体(コピペOK版)
以下のコードをすべてコピーし、お使いのPCで新しいテキストファイルに貼り付けてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Tier表作成ツール</title> <style> /* ページの基本スタイル */ :root { --bg: #f6f7f9; --card: #fff; --line: #e5e7eb; --text: #1f2937; --muted: #6b7280; --primary: #2563eb; --ok: #10b981; --shadow: 0 6px 18px rgba(0, 0, 0, 0.07); } * { box-sizing: border-box; } body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Meiryo, sans-serif; background: var(--bg); color: var(--text); } .wrap { max-width: 1100px; margin: 24px auto; padding: 0 16px; } h1 { margin: 0; font-size: clamp(18px, 2.6vw, 26px); } .lead { margin: 8px 0 16px; color: var(--muted); } .grid { display: grid; grid-template-columns: 320px 1fr; gap: 16px; } @media (max-width: 920px) { .grid { grid-template-columns: 1fr; } } .card { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 14px; } .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .btn { height: 34px; padding: 6px 12px; border: 1px solid var(--line); border-radius: 8px; background: #f4f4f5; cursor: pointer; font-weight: 600; } .btn:hover { background: #eee; } .btn.primary { background: var(--primary); border-color: var(--primary); color: #fff; } .btn.ok { background: var(--ok); border-color: var(--ok); color: #fff; } input[type="text"] { height: 34px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; } .topbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; } .actions { display: flex; gap: 8px; align-items: center; } .pool, .tier-row { display: flex; flex-wrap: wrap; gap: 10px; min-height: 120px; padding: 10px; border: 1px dashed var(--line); background: #fff; } .pool { border-radius: 12px; } .thumb { width: 90px; height: 90px; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); background: #fafafa; position: relative; } .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; } .thumb .x { position: absolute; top: 4px; right: 4px; background: #fff; border: 1px solid var(--line); border-radius: 6px; width: 22px; height: 22px; display: grid; place-items: center; cursor: pointer; } .thumb .x:hover { background: #fee2e2; } .export-wrap { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; } .export-head { padding: 10px; background: #fafafa; border-bottom: 1px solid var(--line); } .tiers { display: flex; flex-direction: column; } .tier { display: grid; grid-template-columns: 100px 1fr; align-items: stretch; border-bottom: 1px solid var(--line); } .tier:first-child { border-top-left-radius: 12px; border-top-right-radius: 12px; overflow: hidden; } .tier:last-child { border-bottom-left-radius: 12px; border-bottom-right-radius: 12px; overflow: hidden; } .tier-label { display: flex; align-items: center; justify-content: center; font-weight: 800; border-right: 1px solid var(--line); min-height: 60px; color: #111; padding: 0 8px; position: relative; } .hint { color: var(--muted); font-size: 13px; } .danger-text { color: #b91c1c; font-size: 13px; } .hidden { display: none; } /* Sortable.js 用のスタイル */ .sortable-ghost { opacity: 0.3; } </style> </head> <body> <div class="wrap"> <div class="topbar"> <h1>Tier表作成ツール</h1> <div class="actions"> <button id="exportPng" class="btn ok">PNG書き出し</button> </div> </div> <p class="lead">画像をドラッグ&ドロップで並べ替え。</p> <div class="grid"> <section class="card" aria-label="操作"> <h2 style="margin:0 0 8px; font-size:18px">画像の読み込み</h2> <div class="row"> <input id="file" type="file" accept="image/png, image/jpeg" multiple class="hidden" /> <button id="pick" class="btn">ファイルを選択</button> <span class="hint">PNG / JPEG(各 2MB・最大 2000×2000px)</span> </div> <p id="msg" class="danger-text" aria-live="polite"></p> <div style="margin-top:14px;"> <p class="hint">未配置プール</p> <div id="pool" class="pool" aria-label="未配置プール"></div> </div> </section> <section class="card export-wrap" aria-label="プレビュー"> <div class="export-head row"> <strong>Tier表</strong><span style="flex:1"></span><span class="hint">画像をドラッグして並び替え</span> </div> <div id="board" style="padding:12px"> <div class="tiers" id="tiers"> <div class="tier" data-id="tier1"> <div class="tier-label" style="background-color: #fca5a5;">S</div> <div class="tier-row"></div> </div> <div class="tier" data-id="tier2"> <div class="tier-label" style="background-color: #fdba74;">A</div> <div class="tier-row"></div> </div> <div class="tier" data-id="tier3"> <div class="tier-label" style="background-color: #fcd34d;">B</div> <div class="tier-row"></div> </div> <div class="tier" data-id="tier4"> <div class="tier-label" style="background-color: #86efac;">C</div> <div class="tier-row"></div> </div> <div class="tier" data-id="tier5"> <div class="tier-label" style="background-color: #93c5fd;">D</div> <div class="tier-row"></div> </div> </div> </div> </section> </div> </div> <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script> <script> // 主要なDOM要素の取得 const $ = s => document.querySelector(s); const $$ = s => Array.from(document.querySelectorAll(s)); const poolEl = $('#pool'); const tiersEl = $('#tiers'); const pickFileBtn = $('#pick'); const fileInput = $('#file'); const exportPngBtn = $('#exportPng'); const msgEl = $('#msg'); // 定数 const THUMB_SIZE = 90; const MAX_FILE_SIZE = 2 * 1024 * 1024; const MAX_DIMENSION = 2000; let items = []; // 画像データを保持する配列 // === ヘルパー関数 === function showMessage(text) { msgEl.textContent = text; setTimeout(() => msgEl.textContent = '', 5000); } // ユニークIDを生成 function uid() { return Math.random().toString(36).slice(2, 10); } // ファイルからDataURL(画像のデータ)を生成 function fileToDataURL(file) { return new Promise((resolve, reject) => { const fr = new FileReader(); fr.onload = () => resolve(fr.result); fr.onerror = reject; fr.readAsDataURL(file); }); } // 画像のサムネイルを生成 async function makeThumbDataURL(file) { if (file.size > MAX_FILE_SIZE) throw new Error('ファイルサイズが大きすぎます(最大2MB)。'); const dataUrl = await fileToDataURL(file); const img = new Image(); img.src = dataUrl; await new Promise(resolve => img.onload = resolve); if (Math.max(img.width, img.height) > MAX_DIMENSION) throw new Error('画像の辺が大きすぎます(最大2000px)。'); const cnv = document.createElement('canvas'); const ctx = cnv.getContext('2d'); cnv.width = cnv.height = THUMB_SIZE; const m = Math.min(img.width, img.height); const sx = (img.width - m) / 2; const sy = (img.height - m) / 2; ctx.drawImage(img, sx, sy, m, m, 0, 0, THUMB_SIZE, THUMB_SIZE); return cnv.toDataURL('image/png'); } // サムネイルDOM要素の生成 function createThumbElement(item) { const div = document.createElement('div'); div.className = 'thumb'; div.dataset.id = item.id; const img = document.createElement('img'); img.alt = 'アイテム'; img.loading = 'lazy'; img.src = item.dataUrl; const del = document.createElement('button'); del.className = 'x'; del.type = 'button'; del.setAttribute('aria-label', '削除'); del.textContent = '×'; del.addEventListener('click', () => removeItem(item.id)); div.append(img, del); return div; } // === メインロジック === // ドラッグ&ドロップ機能の初期化 function initDnD() { const sortableContainers = [poolEl, ...$$('.tier-row')]; sortableContainers.forEach(el => { Sortable.create(el, { group: 'tiers', animation: 150, }); }); } // ファイルの読み込み async function importFiles(files) { if (!files || files.length === 0) return; for (const file of files) { try { const dataUrl = await makeThumbDataURL(file); const item = { id: uid(), dataUrl }; items.push(item); poolEl.appendChild(createThumbElement(item)); } catch (e) { showMessage(e.message); } } } // 画像の削除 function removeItem(id) { items = items.filter(x => x.id !== id); const el = document.querySelector(`.thumb[data-id="${id}"]`); if (el && el.parentNode) { el.parentNode.removeChild(el); } } // === イベントリスナーの設定 === document.addEventListener('DOMContentLoaded', () => { // 初期化 initDnD(); // 「ファイルを選択」ボタン pickFileBtn.addEventListener('click', () => fileInput.click()); // ファイル入力欄の変更 fileInput.addEventListener('change', e => { importFiles(Array.from(e.target.files || [])); e.target.value = ''; // 同じファイルを続けてアップロードできるように }); // ドラッグ&ドロップによるファイル読み込み poolEl.addEventListener('dragover', e => e.preventDefault()); poolEl.addEventListener('drop', e => { e.preventDefault(); const dt = e.dataTransfer; if (dt && dt.files && dt.files.length > 0) { importFiles(Array.from(dt.files)); } }); // PNG書き出しボタン exportPngBtn.addEventListener('click', async () => { try { // 未配置プールを非表示にしてから書き出し poolEl.style.display = 'none'; const canvas = await html2canvas($('#board'), { backgroundColor: '#fff', scale: Math.min(2, window.devicePixelRatio || 1) }); const link = document.createElement('a'); link.href = canvas.toDataURL('image/png'); link.download = 'tierlist.png'; link.click(); } catch (e) { showMessage(`PNG書き出しに失敗しました: ${e.message}`); } finally { poolEl.style.display = 'flex'; } }); }); </script> </body> </html> |
実行手順
- 上記コードをすべてコピーします。
- お使いのPCで、新しいテキストファイルを作成します。
- コピーしたコードをテキストファイルに貼り付け、
tier_maker.html
という名前で保存します。 - 保存したファイルを、Google ChromeやSafariなどのブラウザで開きます。
これで、ブラウザ上にTier表作成ツールが表示され、すぐに使い始めることができます。
ポイント解説
- HTMLとJavaScriptの一体化:
HTML、CSS、JavaScriptをすべて1つのファイルにまとめています。 - 外部ライブラリの利用:
ドラッグ&ドロップ機能には「Sortable.js」を、PNG書き出し機能には「html2canvas」を使用しています。これらのライブラリはCDN(Content Delivery Network)から読み込んでいるため、別途ダウンロードする必要はありません。 makeThumbDataURL
関数:
ファイルを読み込む際に、元の画像サイズに関わらず、表示用の小さなサムネイル(90x90px)を作成しています。これにより、メモリの使用量を抑え、動作を軽くしています。initDnD
関数:Sortable.create()
という命令で、どの要素をドラッグ&ドロップ可能にするかを設定しています。
コピペで動くコードの中身を理解しよう
ここでは、先ほど紹介した完成コードがどのように動いているのかを、HTML・CSS・JavaScript の3つのパートに分けて解説します。
コードの仕組みを理解すれば、あとから自分好みにカスタマイズするのも簡単になります。
1. HTML部分:ツールの土台となる構造
HTMLは、ウェブページの骨組みを作るための言語です。
今回のコードでは、以下のような要素が主要なパーツになっています。
<input type="file">
「ファイルを選択」ボタンを押してPCの画像を読み込むための要素です。class="hidden"
として非表示にし、JavaScriptから操作できるようにしています。<div id="pool">
画像をTier表に配置する前に置いておく「未配置プール」エリアです。<div id="tiers">
Tier表全体を囲むコンテナです。この中に複数のTier行(S、A、B…)が入ります。<div class="tier">
Tier表の1行(例:Sランク)を表します。data-id
という属性で、行ごとに識別IDを設定しています。<div class="tier-row">
Tier行の右側にある、画像を配置する領域です。
この部分がドラッグ&ドロップ可能になります。<button id="exportPng">
完成したTier表をPNG画像としてダウンロードするためのボタンです。
2. CSS部分:見た目を整えるスタイル
CSSは、HTMLで作った骨組みにデザインや装飾を加えるための言語です。
body
ページ全体の背景色やフォントを設定します。.card
,.wrap
コンテンツを中央に配置し、背景や影をつけて見やすくします。.pool
,.tier-row
画像を横に並べるため、display: flex;
を設定しています。.thumb
サムネイル画像のサイズや角丸を設定しています。.tier
Tier行のレイアウトをdisplay: grid;
で定義し、左側のラベルと右側の画像エリアを2列に分けます。.sortable-ghost
ドラッグ中の画像が半透明になるよう、Sortable.jsが自動で付与するクラスにスタイルを指定しています。
3. JavaScript部分:ツールを動かす命令
JavaScriptは、ユーザーの操作に応じて動作を変えるための言語です。
このコードでは、主に次の3つの機能を実装しています。
(1) 画像のアップロード機能
makeThumbDataURL
関数
読み込んだ画像を表示用の小さなサムネイル画像に変換します。createThumbElement
関数
サムネイル画像をHTML要素に変換し、未配置プールに追加します。
これにより、PCから読み込んだ画像が「未配置プール」に並びます。
(2) ドラッグ&ドロップ機能(Sortable.js)
initDnD
関数内でSortable.create()
を使って、未配置プールと各Tier行をドラッグ&ドロップ可能にします。group: 'tiers'
という設定により、異なるエリア間で画像を移動できます。
(3) PNG出力処理
exportPngBtn
(PNG書き出しボタン)が押されると、html2canvas
というライブラリで<div id="board">
の内容を画像化します。- 生成した画像データを
tierlist.png
という名前でダウンロードします。 - ダウンロード時は未配置プールを一時的に非表示にし、Tier表部分だけを画像化します。
さらにカスタマイズしてみよう!
基本のコードを理解したら、次は自分好みにカスタマイズしてみましょう。ここでは、簡単にできるカスタマイズ例をいくつかご紹介します。
Tier行のラベルや色を変更する
HTMLの<body>
内にある<div id="tiers">
の中を編集することで、Tierのラベルや初期の色を変更できます。
変更後(例:S+、A+にする場合):
1 2 3 4 5 6 7 8 |
<div class="tier" data-id="tier1"> <div class="tier-label" style="background-color: #ff69b4;">S+</div> <div class="tier-row"></div> </div> <div class="tier" data-id="tier2"> <div class="tier-label" style="background-color: #ff8c00;">A+</div> <div class="tier-row"></div> </div> |
background-color
に好みの色コードを設定し、<div>
内の文字を自由に編集してみてください。
画像のサムネイルサイズを変える
JavaScript部分のTHUMB_SIZE
という定数を変更することで、サムネイルの大きさを調整できます。
変更後(例:サムネイルを大きくする場合):
1 |
const THUMB_SIZE = 120; // 90を120に変更 |
この値を変更すると、サムネイルの見た目と、画像を処理する際のサイズの両方が変わります。
ドラッグ&ドロップのアニメーション速度を変える
JavaScriptのinitDnD
関数にあるSortable.create()
内のanimation
の値を変更すると、画像をドラッグしたときのアニメーション速度が変わります。
変更後(例:より速くする場合):
1 2 3 4 |
Sortable.create(el, { group: 'tiers', animation: 50, // 値を小さくすると速くなる }); |
おすすめの学習リソース|ドラッグ&ドロップやWebツール制作をもっと学びたい方へ
今回紹介したTier表作成ツールは、HTML・CSS・JavaScript と、外部ライブラリ(Sortable.js・html2canvas)を組み合わせて作成しました。
この仕組みを理解し応用できるようになると、TODOリストや画像ギャラリー、カードゲーム風UI など、さまざまなWebアプリが作れるようになります。
ここでは、さらに学習を進めたい方におすすめのリソースをご紹介します。
書籍で体系的に学ぶ
『確かな力が身につくJavaScript超入門』(SBクリエイティブ)
JavaScriptを基礎から丁寧に学べる定番入門書。
実践的なサンプルも豊富で、今回のような小さなWebアプリが自作できるようになります。
「何から始めればいいか分からない」方の最初の一冊に最適です。
『スラスラわかるHTML&CSSのきほん』(SBクリエイティブ)
Web制作初心者におすすめのHTML/CSS入門書。
タグやスタイルの基本から、レイアウト・装飾の実践テクニックまでやさしく解説。
「見た目をもっとキレイにしたい」方にピッタリです。
おすすめのオンライン講座
Udemy|世界最大級のオンライン学習プラットフォーム
世界中で利用されるオンライン学習サイト。
HTML、CSS、JavaScriptの入門から応用まで、高評価の講座が数百種類揃っています。
初心者でも動画を見ながら手を動かせるので、挫折しにくいのが魅力です。
まとめ
今回は、ドラッグ&ドロップでTier表を作れるブラウザ完結型Webツール の作り方をご紹介しました。
HTML・CSS・JavaScriptの基本に加え、Sortable.js を使うことで、直感的な画像並べ替え機能を実装できました。
さらに html2canvas を利用して、完成したTier表をそのままPNG画像として保存する仕組みも実現しました。
今回のポイントをおさらいすると…
- HTML でツールの骨組みを作る
- CSS で見やすくデザインを整える
- JavaScript + Sortable.js でドラッグ&ドロップ機能を実装
- html2canvas でTier表を画像として出力
この仕組みを理解すれば、Tier表だけでなく、タスク管理アプリ・カード型UI・並び替えクイズ など、応用できる場面はたくさんあります。
あとはあなたのアイデア次第!ぜひ今回のサンプルをベースに、オリジナルのWebツール制作に挑戦してみてください。
今回のコードをコピーして動かすだけでも勉強になりますが、コードを少しずつ改造していくと、もっと楽しくなりますよ!

関連記事
【実際のTier表作成ツールの動作を試してみたい方はこちらからどうぞ】▼