Open8

おみくじをNuxtで実装する

aissaiss

技術構成

Nuxt3
TailwindCSS

画面構成

1画面でおみくじを引くボタンを押したら結果が出るアプリを作ります。

aissaiss

omikuji ディレクトリを作成

mkdir omikuji

Nuxtプロジェクトを初期化

cd omikuji
npx nuxi init .

yarn install 実施、初期画面表示

yarn install
yarn dev
aissaiss

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
aissaiss
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: [],
}
aissaiss

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';
aissaiss

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"
  }
}
aissaiss

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>
aissaiss

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>