Open8
おみくじをNuxtで実装する
技術構成
Nuxt3
TailwindCSS
画面構成
1画面でおみくじを引くボタンを押したら結果が出るアプリを作ります。
omikuji ディレクトリを作成
mkdir omikuji
Nuxtプロジェクトを初期化
cd omikuji
npx nuxi init .
yarn install 実施、初期画面表示
yarn install
yarn dev
voltaでバージョンを管理
node --version
volta pin node@20.17.0
yarn --version
volta pin yarn@4.3.1
-Dでpackage.jsonのdevDependenciesにtailwindcss postcss autoprefixerを追加
yarn add -D tailwindcss postcss autoprefixer
yarn tailwindcss init
tailwind.config.jsができる
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./nuxt.config.{js,ts}",
"./app.vue",
],
theme: {
extend: {},
},
plugins: [],
}
nuxt.config.ts
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: { enabled: true },
css: ['~/assets/css/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
})
assets/css/main.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
package.json
{
"name": "omikuji-app",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"nuxt": "^3.5.0",
"tailwindcss": "^3.3.2",
"postcss": "^8.4.23",
"autoprefixer": "^10.4.14"
},
"volta": {
"node": "18.16.0",
"yarn": "1.22.19"
}
}
components/Omikuji.vue
<template>
<div class="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<h1 class="text-4xl font-bold mb-8">おみくじ</h1>
<div class="bg-white p-8 rounded-lg shadow-md">
<p class="text-2xl mb-4">{{ result }}</p>
<button
@click="drawOmikuji"
class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded"
>
おみくじを引く
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const fortunes = ['大吉', '中吉', '小吉', '吉', '末吉', '凶', '大凶']
const result = ref('おみくじを引いてください')
const drawOmikuji = () => {
result.value = fortunes[Math.floor(Math.random() * fortunes.length)]
}
</script>
app.vue
<template>
<div>
<Omikuji />
</div>
</template>
StopWatch
<template>
<div class="bg-gray-100 flex flex-col justify-content-center items-center h-screen">
<div style="width: 500px" class="bg-white p-4 rounded-lg shadow-lg mt-10">
<h1 class="font-bold text-4xl text-center my-4 ">STOPWATCH</h1>
<div class="font-mono text-center text-6xl font-bold">{{ hours }}:{{ minutes }}:{{ seconds }}.{{ milliseconds }}</div>
<div class="text-center">
<button @click="startTimer" class="bg-blue-500 text-white px-4 py-2 rounded-lg mt-4 mr-4">{{ mainButtonText }}</button>
<button @click="reset" class="bg-red-500 text-white px-4 py-2 rounded-lg mt-4">Reset</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const mainButtonText = ref('開始')
const isRunning = ref(false)
let intervalId = null
const startTime = ref(0)
const elapsedTime = ref(0)
const hours = computed(() => padTime(Math.floor(elapsedTime.value / 3600000)))
const minutes = computed(() => padTime(Math.floor((elapsedTime.value % 3600000) / 60000)))
const seconds = computed(() => padTime(Math.floor((elapsedTime.value % 60000) / 1000)))
const milliseconds = computed(() => padTime(Math.floor(elapsedTime.value % 1000), 3))
function padTime(value, length = 2) {
return value.toString().padStart(length, '0')
}
const startTimer = () => {
if (isRunning.value){
clearInterval(intervalId)
mainButtonText.value = '開始'
isRunning.value = false
}else {
const now = Date.now()
startTime.value = now - elapsedTime.value
isRunning.value = true
mainButtonText.value = '停止'
intervalId = setInterval(() => {
elapsedTime.value = Date.now() - startTime.value
}, 16)
}
}
const reset = () => {
clearInterval(intervalId)
isRunning.value = false
elapsedTime.value = 0
mainButtonText.value = '開始'
}
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap');
.font-mono {
font-family: 'Roboto Mono', monospace;
}
</style>