0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

未経験がOpenWeatherMapを使って天気アプリを作ってNetlifyにデプロイする

Last updated at Posted at 2020-12-30

今回VueでAPI使う場合の勉強を兼ねてOpenWeatherMapを使って天気アプリを作ってNetlifyにデプロイしたので記録しておきます。

#APIキーを取得する
元々楽天会員なのでRakuten RapidAPIから取得しました。

#dockerを導入する
dockerを勉強中の身なので導入してみました。

Dockerfile
FROM node:14.15.1

WORKDIR /app

ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

RUN apt-get update -y && \
    apt-get upgrade -y && \
    yarn global add @vue/cli@4.5.9

ADD . /app
docker-compose.yml
version: "3"

services:
  web:
    container_name: web
    build: ./web/
    image: web
    volumes:
      - ./web:/app
    ports:
      - 8080:8080
    privileged: true

    command: "yarn run serve"

#vue-cliでvueをインストールします。
webフォルダを作成してその中にインストールしました。

天気情報を取得する

WeatherHome.vue
<template>
  <section id="weatherHome">
    <input
      v-model="getWeatherQuery"
      placeholder="調べたい場所を入力してください"
    />
    <button @click="getWeather">天気を調べる</button>
    <div v-if="hasGetWeatherData" id="weatherCard">
      <h1 class="weatherCardHeader">{{ getWeatherResult.name }}</h1>
      <div class="weatherCardDescription">
        {{ getWeatherInfo.description }}
      </div>
      <div class="weatherCardWeatherData">
        <img :src="getWeatherImageUrl" class="weatherCardImage" />
        <p class="weatherCardTemp">
          <v-icon name="thermometer-half" scale="2" />
          {{ getWeatherResult.main.temp }}</p>
      </div>
      <div class="weatherCardWeatherOtherData">
        <div class="weatherCardWindSpeed">
          <v-icon name="wind" scale="2" />
          {{ getWeatherResult.wind.speed + "m/s" }}
        </div>
        <div class="weatherCardHumidity">
          <v-icon name="tint" scale="2" />
          {{ getWeatherResult.main.humidity + "%" }}
        </div>
      </div>
      風速:{{ getWeatherResult.wind.speed }} 湿度:{{
        getWeatherResult.main.humidity
      }}
    </div>
  </section>
</template>

<script>
import axios from "axios";
export default {
  name: "WeatherHome",
  data() {
    return {
      getWeatherQuery: "",
      getWeatherResult: {},
      getWeatherInfo: {},
      getWeatherImageUrl: "",
      hasGetWeatherData: false,
    };
  },
  methods: {
    getWeather() {
      let apiURL = "https://community-open-weather-map.p.rapidapi.com/weather";
      axios
        .get(apiURL, {
          headers: {
            "content-type": "application/octet-stream",
            "x-rapidapi-host": process.env.VUE_APP_OPEN_WEATHER_MAP_API_HOST,
            "x-rapidapi-key": process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
            useQueryString: true,
          },
          params: {
            id: "2172797",
            lang: "ja",
            units: "metric",
            q: this.getWeatherQuery,
          },
        })
        .then((response) => {
          this.getWeatherResult = response.data;
          this.getWeatherInfo = Object.assign(...this.getWeatherResult.weather);
          this.getWeatherImageUrl =
            "http://openweathermap.org/img/wn/" +
            this.getWeatherInfo.icon +
            "@4x.png";
          this.hasGetWeatherData = true;
          console.log(response);
        })
        .catch((error) => {
          console.log(error);
        });
    },
  },
};
</script>

<style>
#weatherCard {
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
  color: #212121;
  text-decoration: none;
  height: 400px;
  width: 400px;
  margin: auto;
}
.weatherCardWeatherData {
  height: 50%;
  display: flex;
  align-items: center;
}
.weatherCardHeader {
  height: 10%;
  text-align: center;
  padding-top: 2%;
}
.weatherCardDescription {
  height: 5%;
  font-size: 150%;
  text-align: center;
  padding: 0.5% 1%;
}
.weatherCardImage {
  margin-left: auto;
}
.weatherCardTemp {
  font-size: 120%;
  margin-right: 15px;
  padding: 1% 1%;
}
.weatherCardWeatherOtherData {
  height: 15%;
  position: relative;
}
.weatherCardWindSpeed {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  left: 70px;
}
.weatherCardHumidity {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  right: 70px;
}
</style>

env.localのAPIキーを使いaxiosで情報を取得して各情報を表示しvue-awesomeを使って少し見栄えを良くしています。

Vuexを導入する

雨が降っていたら雨のアニメーションを表示するなど少し凝って見たくなったのでWeatherAnime.vueを作成したのですが、
そういえばVuexを使ったことがなかったと思い現在コンポーネントが2つあるので導入してみました。
*今思うとpropsとか使ったほうが良かったと思います。

store.js
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    apiKey: process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
    apiHost: "https://community-open-weather-map.p.rapidapi.com/weather",
    getWeatherQuery: "",
    getWeatherResult: {},
    hasGetWeatherData: false,
  },
  mutations: {
    setGetWeatherQuery(state, val) {
      state.getWeatherQuery = val;
    },
    setGetWeatherResult(state, data) {
      state.getWeatherResult = data;
    },
    setHasGetWeatherData(state, data) {
      state.hasGetWeatherData = data;
    },
  },
  getters: {
    getWeatherResult: (state) => {
      return String(state.getWeatherResult);
    },
    hasGetWeatherData: (state) => {
      return Boolean(state.hasGetWeatherData);
    },
  },
  actions: {
    async getWeather({ commit, state }, getWeatherQuery) {
      try {
        commit("setGetWeatherQuery", getWeatherQuery);

        const response = await axios.get(state.apiHost, {
          headers: {
            "content-type": "application/octet-stream",
            "x-rapidapi-host": process.env.VUE_APP_OPEN_WEATHER_MAP_API_HOST,
            "x-rapidapi-key": process.env.VUE_APP_OPEN_WEATHER_MAP_API_KEY,
            useQueryString: true,
          },
          params: {
            lang: "ja",
            units: "metric",
            q: state.getWeatherQuery,
          },
        });

        const newWeatherData = {
          name: response.data.name,
          temp: response.data.main.temp,
          description: response.data.weather[0].description,
          icon: response.data.weather[0].icon,
          info: response.data.weather[0].main,
          wind: response.data.wind.speed,
          humidity: response.data.main.humidity,
        };

        commit("setGetWeatherResult", newWeatherData);
        commit("setHasGetWeatherData", true);
      } catch (error) {
        console.log(error);
      }
    },
  },
});

export default store;
WeatherHome.vue
<template>
  <section id="weatherHome">
    <input v-model.trim="search" placeholder="調べたい場所を入力してください" />
    <button @click="getData">天気を調べる</button>
    <div v-if="this.hasGetWeatherData" id="weatherCard">
      <h1 class="weatherCardHeader">
        {{ this.getWeatherResult.name }}
      </h1>
      <div class="weatherCardDescription">
        {{ this.getWeatherResult.description }}
      </div>
      <div class="weatherCardWeatherData">
        <img
          :src="
            `http://openweathermap.org/img/wn/${this.getWeatherResult.icon}@4x.png`
          "
          class="weatherCardImage"
        />
        <p class="weatherCardTemp">
          <v-icon name="thermometer-half" scale="2" />
          {{ this.getWeatherResult.temp }}</p>
      </div>
      <div class="weatherCardWeatherOtherData">
        <div class="weatherCardWindSpeed">
          <v-icon name="wind" scale="2" />
          {{ this.getWeatherResult.wind + "m/s" }}
        </div>
        <div class="weatherCardHumidity">
          <v-icon name="tint" scale="2" />
          {{ this.getWeatherResult.humidity + "%" }}
        </div>
      </div>
    </div>
  </section>
</template>

<script>
import { mapActions, mapState } from "vuex";
export default {
  name: "WeatherHome",
  data() {
    return {
      search: "",
    };
  },
  computed: {
    weatherQuery: {
      get() {
        return this.$store.state.getWeatherQuery;
      },
      set(value) {
        this.$store.commit("setGetWeatherQuery", value);
      },
    },
    ...mapState(["getWeatherResult", "hasGetWeatherData"]),
  },
  methods: {
    ...mapActions(["getWeather"]),
    getData() {
      this.getWeather(this.search);
    },
  },
};
</script>

<style>
#weatherCard {
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
  color: #212121;
  text-decoration: none;
  height: 400px;
  width: 400px;
  margin: auto;
}
.weatherCardWeatherData {
  height: 50%;
  display: flex;
  align-items: center;
}
.weatherCardHeader {
  height: 10%;
  text-align: center;
  padding-top: 2%;
}
.weatherCardDescription {
  height: 5%;
  font-size: 150%;
  text-align: center;
  padding: 0.5% 1%;
}
.weatherCardImage {
  margin-left: auto;
}
.weatherCardTemp {
  font-size: 120%;
  margin-right: 15px;
  padding: 1% 1%;
}
.weatherCardWeatherOtherData {
  height: 15%;
  position: relative;
}
.weatherCardWindSpeed {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  left: 70px;
}
.weatherCardHumidity {
  font-size: 150%;
  position: absolute;
  bottom: 0;
  right: 70px;
}
</style>

背景画像を変更する

アニメーションを作りたかったのですがgithubで色々なソースを読んでググってもわからなかったので取り急ぎ背景画像を変更する仕様にしました。
*今思うとちゃんと分かるまで調べたほうが良かったと思います。

WeatherAnime.vue
<template>
  <div
    class="weather-anime"
    :class="[
      { 'weather-rain': getWeatherResult.info === 'Rain' },
      { 'weather-snow': getWeatherResult.info == 'Snow' },
      { 'weather-clear': getWeatherResult.info == 'Clear' },
      { 'weather-clouds': getWeatherResult.info == 'Clouds' },
    ]"
  ></div>
</template>

<script>
import { mapState } from "vuex";
export default {
  computed: {
    ...mapState(["getWeatherResult"]),
  },
};
</script>

<style scoped>
.weather-anime {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}
.weather-rain {
  background-image: url("../assets/images/rain.jpg");
}
.weather-clouds {
  background-image: url("../assets/images/clouds.jpg");
}
.weather-clear {
  background-image: url("../assets/images/clear.jpg");
}
</style>

少し修正する

ボタンのデザイン変更と初期データに東京を指定しました。

WeatherHome.vue
<div class="weatherSearch">
<input
  v-model.trim="search"
  placeholder="調べたい場所を入力してください"
/>
<button @click="getData" class="weatherCardButton">天気を調べる</button>
</div>

<script>
created: function() {
  this.getWeather("Tokyo");
},
</script>

<style>
.weatherCardButton {
  border: 2px solid transparent;
}
.weatherCardButton:hover {
  background: transparent;
  border-color: white;
}
</style>

Netlifyにデプロイする

netlify.toml
[build]
  base = "/web"
[build.environment]
  YARN_VERSION = "1.22.5"
  YARN_FLAGS = "--no-ignore-optional"
  NODE_VERSION = "14.15.3"

終わりに

一応完成はしましたがGithub上にあるOpenWeatherMapを使ったコードを読んでみるといかに自分のコードのレベルが低いか痛感させられます。
コードはGithubに保存してあるので見たい方がいらしたらどうぞ。
改善するために最近はコードリーディングや写経して作り方のパターンを覚えたほうがいいのではと思案中です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?