前回はアプリについて必要な機能などを抜き出したりしつつ
実装の準備をしました。
前回の記事はコチラ
ここのヘッダーを実際に作っていきます。
環境構築
1:compose.yamlを作成
services:
node:
image: node:22-alpine // Node.js入りの環境(ホストにNodeを入れない)
working_dir: /app
volumes:
- .:/app // このフォルダをコンテナの/appに接続
- node_modules:/app/node_modules // 依存パッケージは分離(速度と安定のため)
ports:
- "5173:5173" // ブラウザ(ホスト)5173 → コンテナ5173
command: sh -c "npm install && npm run dev"
volumes:
node_modules:
server: {
host: '0.0.0.0', // コンテナの外から届くようにする
hmr: { host: 'localhost' }, // 自動更新の通知先
2:Vueプロジェクトの雛形を作成
docker compose run --rm node npm create vue@latest .
そうするとコンソールが動き始めるのでこのように設定
Need to install the following packages:
create-vue@3.22.4
Ok to proceed? (y) # yと入れる
> npx
> create-vue .
# 以下のように選択
┌ Vue.js - The Progressive JavaScript Framework
│
◇ Current directory is not empty. Remove existing files and continue
│ Yes
│
◇ Package name:
│ madori
│
◇ Use TypeScript?
│ No
│
◇ Select features to include in your project: (↑/↓ to navigate, space to select, a to toggle all, enter to confirm)
│ Vitest (unit testing), Linter (error prevention), Prettier (code formatting)
│
◇ Select experimental features to include in your project: (↑/↓ to navigate, space to select, a to toggle all, enter
│ to confirm)
│ none
│
◇ Skip all example code and start with a blank Vue project?
│ Yes
Scaffolding project in /app...
│
└ Done. Now run:
npm install
npm run format
npm run dev
lsにて中身できているか確認
compose.yaml index.html package.json README.md vite.config.js
eslint.config.js jsconfig.json public src vitest.config.js
ヘッダーの本体部分を実装
srcファイルの中には以下のファイルらがあり、
__tests__
App.vue # ここにどんどんVueの本体かいていく
main.js # 起動係
src/main.js(起動係。この3行が「App.vueをid=appに差し込んでね」の意味)
// Vue.js本体からcreateApp()関数をインポート
import { createApp } from 'vue'
// これまでコーディングをしてきたApp.vueファイルをAppとしてインポート
import App from './App.vue'
// 下記に詳細記載
createApp(App).mount('#app')
createApp()はVueアプリケーションを作成する処理でここには
起点となる単一コンポーネントファイル(src)で渡す
mount()はcreateApp()で作ったVueアプリケーションを表示するメソッド
index.htmlのどのタグに表示するのかを引数で指定
この場合は#appとあり、idがappのdivタグ内に
レンダリングされて表示されるという仕組み
src/App.vue
<script setup>
import { ref } from 'vue'
const roomSizes = [
{ id: 1, name: '江戸間6畳(261×352)', width: 261, depth: 352 },
{ id: 2, name: '団地間6畳(255×340)', width: 255, depth: 340 },
{ id: 3, name: '中京間6畳(273×364)', width: 273, depth: 364 },
{ id: 4, name: '京間6畳(287×382)' , width: 287, depth: 382 },
]
const roomWidth = ref('261') // 横幅の初期値を設定 refで追跡可能に
const roomDepth = ref('352') // 奥行の初期値を設定 refで追跡可能に
function selected(event) {
const selectedId = Number(event.target.value) // 文字列で届くので数値に変換
// 配列の中身を1個ずつ取り出して仮に r と呼ぶ
const found = roomSizes.find(r => r.id === selectedId)
if (!found) return
roomWidth.value = found.width
roomDepth.value = found.depth
}
const selectedRoomId = ref(1) // 今選ばれている規格のid。初期値は江戸間
function reset(){
roomWidth.value = '261'
roomDepth.value = '352'
selectedRoomId.value = 1
}
</script>
<template>
<div class="header">
<h1>6畳1Kレイアウトシュミレーター MADORI</h1>
<div class="room_layout">
<p>畳の規格</p>
<div class="room_select">
<select v-model="selectedRoomId" @change="selected">
<option v-for="r in roomSizes" :key="r.id" :value="r.id">
{{ r.name }}
</option>
</select>
</div>
<p>幅</p> <input type="text" v-model="roomWidth">
<p>奥行</p> <input type="text" v-model="roomDepth">
<button @click="reset">リセット</button>
</div>
<p>単位:cm /ドラッグで移動・タップで選択</p>
</div>
</template>
<style>
body { font-family: sans-serif; }
.room_layout {
display:flex; /* 中に入っている要素(子要素)を横並びにする */
align-items: center; /* 縦方向の中央揃え */
gap: 12px; /* 要素同士の隙間を設定*/
}
</style>
App.vueの中身を深掘り
畳の規格 部分のロジック
//script部分
// 選択した時の初期値を決めて 置いておく箱
const roomSizes = [
{ id: 1, name: '江戸間6畳(261×352)', width: 261, depth: 352 },
{ id: 2, name: '団地間6畳(255×340)', width: 255, depth: 340 },
{ id: 3, name: '中京間6畳(273×364)', width: 273, depth: 364 },
{ id: 4, name: '京間6畳(287×382)' , width: 287, depth: 382 },
]
const roomWidth = ref('261') // 横幅の初期値を設定 refで追跡可能に
const roomDepth = ref('352') // 奥行の初期値を設定 refで追跡可能に
const selectedRoomId = ref(1) // 今選ばれている規格のid。初期値は江戸間
// selectedRoomIdが変わるたびに自動で実行される
watch(selectedRoomId, (id) => {
// 配列の中身を1個ずつ取り出して仮に r と呼ぶ
const found = roomSizes.find(r => r.id === id)
if (!found) return
roomWidth.value = found.width
roomDepth.value = found.depth
})
畳の規格部分の template部分
<div class="room_select">
<select v-model="selectedRoomId">
<option v-for="r in roomSizes" :key="r.id" :value="r.id">
{{ r.name }}
</option>
</select>
</div>
watch(監視したいref, (新しい値) => { 反応する処理 }) という形で
「selectedRoomIdを見張っておいて、値が変わるたびに
その新しい値をidという名前で受け取って、中の処理を実行してね」という意味。
プルダウンで「団地間」を選ぶと selectedRoomId.value が 2 に変わり、
その瞬間この関数が自動で呼ばれ、id には 2 が入ってくる
リセットボタンのロジック
リセットボタンを押した時に
「畳の規格」「幅」「奥行」を初期値に戻すためのボタンを実装
// script部分
function reset(){
roomWidth.value = '261'
roomDepth.value = '352'
selectedRoomId.value = 1
}
resetという関数を定義。
その中に初期値である
roomWidth(横幅)→261
roomDepth(奥行き)→352
selectedRoomId(間取り番号)→
id: 1, name: '江戸間6畳(261×352)', width: 261, depth: 352
これらの数値をこれに入れるよという処理
template部分
(略)
<p>畳の規格</p>
<div class="room_select">
⭐️ <select v-model="selectedRoomId">
<option v-for="r in roomSizes" :key="r.id" :value="r.id">
{{ r.name }}
</option>
</select>
</div>
🔴 <p>幅</p> <input type="text" v-model="roomWidth">
🔵 <p>奥行</p> <input type="text" v-model="roomDepth">
🟢 <button @click="reset">リセット</button>
(略)
⭐️のところは「selectedRoomId」「畳の規格」
🔴が「roomWidth」「幅」
🔵が「roomDepth」「奥行」
🟢がリセットボタン
1:🟢のリセットボタンを押す
🟢の @click つまりクリックを監視し、それが実行された際script内の「reset」
が発動
2:⭐️🔴🔵の中に 関数「reset」の中の値が入る
※補足
① roomWidth.value = '261' のような「代入」が行われる(ここまでがJavaScript)
↓
② v-modelで繋がっているやが、その変化を検知して
画面の表示を自動で書き換える(ここがVueの仕事)
JS→画面の一方向(reset関数が値を書き換え→画面に反映)だが、
Vueのv-modelにはその逆方向(画面→JS)もできる
プルダウンでユーザーが「団地間」を選んだときも同じv-model="selectedRoomId"の
繋がりを使って、今度は画面の操作がJS側のselectedRoomId.valueを書き換えるという
逆の流れが起きている

