ウルトラソウルアプリを作ってみた

# ウルトラソウルアプリってなに？
皆さん、国民的アーティスト「B'z」をご存知でしょうか？
知ってますよね。知らない人はいないはずです。
そんな彼らの代表曲のひとつでもある「ultra soul」をご存知でしょうか？
知ってますよね。知らない人はいないはずです。
なぜかわからないけど高ぶるあの瞬間！！

そして〜かがや〜く ウルトラソウル！！
ハイ！！！！

この「ハイ！！！」の瞬間がたまらん！

ってことで作りました。

「そしてかがや〜くウルトラソウル！ハイ！！！」の 「ハイ！！！」のタイミングでタッチ！！！ あなたのウルトラソウル力が試される！！

その名も「ウルトラソウルアプリ」！！

はい。

# とりあえずやってみる
とりあえずやってみてください。
https://ultrasoul-app.herokuapp.com/

結果はいかがでしたでしょうか？
僕は「無」以外出せたことないです。

そうです。脳内再生がポイントです。
著作権の関係上音源を使用することが出来なかったとかじゃないです。
もともと脳内再生のつもりでした。

是非、パーフェクト目指して何度も遊んで欲しいです。

# なぜ作った？
今年の2月からプログラミングを始めたのですが、未だアウトプットとして何かアプリケーションを作成したりすることが出来ていませんでした。
インプットすることも大事ですが、何らかの形でアウトプットしたい、、、と歯がゆい思いをしている中で、adventcalendarの話が舞い込んできたので、これは良いチャンスだ！！！と、アプリケーションを作成することにしました。
ちなみになぜウルトラソウルなのかについてですが、

# 本題
この記事の目標は、
「JavaScriptやjQueryをprogateとかで学習してみたけど、実際どう使えば良いのかわからない、、、」もしくは「Railsを学習し始めたけど、jQueryとかも触ってみたいなあ」というJavaScriptやjQueryをある程度知っている初学者が、何らかの開発で実際に使う時に参考になることです。

# 環境
Rails 5.2.1
Ruby 2.5.1

# アプリケーションの流れ
1. トップ画面
<img width="838" alt="スクリーンショット 2018-12-14 14.46.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/becece6a-4406-bea4-6e25-e3db623459b9.png">

2. 準備画面
<img width="726" alt="スクリーンショット 2018-12-14 14.47.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/e48f9b8d-2b2b-990c-5139-cb6d6937bfa9.png">

3. 脳内再生画面
<img width="753" alt="スクリーンショット 2018-12-14 14.47.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/f71adc92-62d1-2870-96bc-23c8d9bba916.png">

4. 結果画面
<img width="768" alt="スクリーンショット 2018-12-14 14.47.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/84d803b6-6eec-e397-231a-d0e98e5c7731.png">

トップ画面以外は、非同期通信でコンテンツを変更しているのがポイントです。

# 解説入ります
##Rails編
### コントローラの作成とアクションを定義

まずは、アプリケーションを作成

```
$ rails new ultrasoul
```


コントローラを作成。今回はモデルは使用しません。

```
$ rails g controller ultrasoul
```

アクションは２つだけです。

```/controllers/ultrasoul_controller.rb
class UltrasoulController < ApplicationController
	# トップ画面に遷移するためのアクション
	def top
	end

	# 準備画面に遷移するためのアクション
	def ultrasoul
	end
end
```

### ルーティングを定義

あわせてルーティングも定義してしまいましょう。
Jsonの部分は後で詳しく解説します。

```/config/routes.rb
Rails.application.routes.draw do
  # UltrasoulControllerのtopアクションを呼び出す
  root 'ultrasoul#top', as: 'top'

  # UltrasoulControllerのultrasoulアクションを呼び出す
  get '/ultrasoul' => 'ultrasoul#ultrasoul', as: 'ultrasoul'

  # Jsonを利用して非同期通信を行う
  get "/result" => 'ultrasoul#result', as: 'result'
end
```

### トップ画面

続けてトップ画面のHTMLを作成していきます。

```/views/ultrasoul/top.html.erb
<div class="box">
	<h1 class="title">ウルトラソウル</h1>
	<p class="about-message">「そしてかがや〜くウルトラソウル！ハイ！！！」の
	「ハイ！！！」のタイミングでタッチ！！！
	あなたのウルトラソウル力が試される！！</p>
	<%= link_to "あそぶ", ultrasoul_path %>
</div>
```

<img width="1045" alt="スクリーンショット 2018-12-14 15.39.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/5424324e-916b-f9fd-1981-e9a6aec63817.png">


これでは味気がないのでCSSで装飾していきましょう。

```/styleseets/application.scss
/*
 :
 :
 *= require_tree .
 *= require_self
 */

/*共通*/
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
	padding: 0 5%;
}

/*トップ画面*/
.box {
	padding-top: 50px;
	width: 500px;
	margin: 0 auto;
}

.title {
	font-size: 45px;
	text-align: center;
	padding-bottom: 10px;
}

.about-message {
	font-size: 25px;
	text-align: center;
	padding: 30px 0;
}
```

<img width="547" alt="スクリーンショット 2018-12-14 15.50.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/3c2e3002-980d-4d2d-5a59-deacb288f89b.png">

リンクの部分をボタンにしたいですね。

### bootstrapの導入（Rails内でjQueryを使えるようにする）

そこで手軽に実装できるbootstrapを使用します。

まずは、Gemfileの下部に以下の記述を追加します。

```Gemfile
gem 'jquery-rails'

gem 'bootstrap-sass'
```

`gem 'jquery-rails'`はjQueryを使用する際にもインストールしなければならないので一石二鳥です。

記述したら

```
$ bundle install
```

でインストールを実行しましょう。

インストールが完了したら、
以下のファイルに、記述を追加してください。

```/styleseets/aplication.scss
/*
 :
 :
 *= require_tree .
 *= require_self
 */
@import "bootstrap-sprockets"; /*追加*/
@import "bootstrap"; /*追加*/

```

```/javascripts/application.js
:
:
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery
//= require bootstrap-sprockets
//= require_tree .
:
:
```

これでbootstrapが使えるようになりました。

HTMLとCSSを編集します。

```/views/ultrasoul/top.html.erb
<div class="box">
　　　　  :
    :
　　　　　　　　<!-- class: "btn btn-default"を追加 -->
	<%= link_to "あそぶ", ultrasoul_path, class: "btn btn-default" %>
</div>
```

ボタンになりました！
<img width="349" alt="スクリーンショット 2018-12-14 16.28.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/5d33dc5c-058e-6137-df10-f7d0d8706125.png">

あとはCSSでフォントや余白を調整

```/styleseets/application.scss

/*共通*/
:
:

.btn {
	width: 200px;
	display: block;
	border-radius: 15px;
	font-size: 20px;
	padding: 10px 30px;
	margin: 0 auto;
}
/*トップ画面*/

```

これでトップ画面が完成しました！
<img width="838" alt="スクリーンショット 2018-12-14 14.46.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/becece6a-4406-bea4-6e25-e3db623459b9.png">

### 準備画面・脳内再生画面・結果画面

続けて、準備画面・脳内再生画面・結果画面を作成していきます。
脳内再生画面、結果画面に関しては非同期通信によりHTMLの内容が切り替わって表示されてるので、
最初に必要な記述は「準備画面」のみです。

まずはHTMLを記述していきましょう。

```/views/ultrasoul/ultrasoul.html.erb
<div class="box">
	<!-- スタートボタンを押した時、「スタート！」にテキスト変更
	ハイ！！ボタンを押したら非表示 -->
	<p id="introduction">
		音声は流れません。脳内でイントロからウルトラソウルを再生してください。
	</p>

	<img id="illust" src="/assets/ready.png">

	<!-- 初めは<div class="start-message">のみ表示
	ハイ！！ボタンを押したら<div class="start-message">を非表示にして
	<div class="end-message">を表示する -->
	<div class="message-group">
		<div class="start-message">
			<p>準備が整ったら</p>
		</div>
		<div class="end-message">
			<p>あなたの<br>ウルトラソウル力は</p>
			<p class="result-message"></p>
		</div>
	</div>

	<!-- 初めは<div id="ready">のみ表示
	ハイ！！ボタンを押したら<div id="ready">を非表示にして
	<ul id="again">を表示する -->
	<div id="ready">
		<input type="button" value="スタート" id="start" class="btn btn-default"></input>
	</div>
	<ul id="again">
		<li class="retry"><%= link_to 'やり直したい', ultrasoul_path, class: "btn btn-default" %></li>
		<li><%= link_to 'トップに戻る', top_path, class: "btn btn-default" %></li>
	</ul>
</div>
```

ややこしくなってきました笑
続けてCSSを記述してください。

```/styleseets/application.scss
:
:
/*準備画面 脳内再生画面 結果画面*/

.start-message > p {
	font-size: 25px;
	text-align: center;
	padding-bottom: 15px;
}

.end-message > p {
	font-size: 25px;
	text-align: center;
	padding-bottom: 15px;
}

.result-message {
	font-weight: bold;
}

#again {
	list-style: none;
}

.retry {
	padding: 8px 0;
}

```

こちらの画面が表示されるはずです。

<img width="565" alt="スクリーンショット 2018-12-14 17.32.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/600e7515-898e-0896-b23a-d732ed3d8ca2.png">

お気づきでしょうか？
結果画面で表示されるはずの「あなたのウルトラソウル力は」や「やり直したい」ボタンなどが表示されてしまっています。
こちらを非表示にしましょう。

```/styleseets/application.scss
:
:
/*準備画面 脳内再生画面 結果画面*/
:
:
/* 追加 */
.end-message {
	display: none;
}

#again {
	list-style: none;
	display: none; /* 追加 */
}

:
:
```

これで準備画面は完成です！

<img width="726" alt="スクリーンショット 2018-12-14 14.47.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/e48f9b8d-2b2b-990c-5139-cb6d6937bfa9.png">

## JavaScript編

ようやく、JavaScriptの実装に入れます！

### 内部での流れの確認
その前に一度内部での流れを確認したいと思います。
このアプリは、「スタートボタンを押した時点でのミリ秒」と「ハイ！ボタンを押した時点でのミリ秒」の差分が、実際の「ハイ！！！」のタイミングに近ければ近いほど良い結果が、ウルトラソウル力として表示される仕組みです。

なので
・スタートボタンを押した時点でのミリ秒の取得
・ハイ！ボタンを押した時点でのミリ秒の取得
・差分の計算（秒数に直す）
・実際の「ハイ！！！」のタイミング(1分06秒)から差分を引く
・差分を引いた結果の秒数によってメッセージや画像の切り替え
以上の機能を実装しなければなりません。

各機能を画面構成に当てはめると
【脳内再生画面】
・スタートボタンを押した時点でのミリ秒の取得

【結果画面】
・ハイ！ボタンを押した時点でのミリ秒の取得
・差分の計算（秒数に直す）
・実際の「ハイ！！！」のタイミングから差分を引く
・差分を引いた結果の秒数によってメッセージや画像の切り替え

結果画面に遷移する際の機能が多いですね。

それでは、JavaScriptを実装していきましょう。

### 変数、連想配列の準備

```/javascripts/application.js
:
:
$(function(){
	/**
	 * スタートボタンを押したタイミングのミリ秒のための変数
	 * @type {Number}
	 */
	let startTime = 0;

	/**
	 * 「ハイ！」を押したタイミングのミリ秒のための変数
	 * @type {Number}
	 */
	let endTime = 0;

	/**
	 * endTimeからstartTimeを引いたミリ秒のための変数
	 * @type {Number}
	 */
	let totalTime = 0;

	/**
	 * 結果画面でのメッセージを変更するための連想配列
	 * @type {Object}
	 */
	let messages = {
		'perfect': 'ウルトラソウルパーフェクト',
		'great': 'ウルトラソウルグッド',
		'good': '平凡ソウル',
		'bad': '無'
	};

	/**
	 * 結果画面での画像を変更するための連想配列
	 * @type {Object}
	 */
	let src = {
		'perfect': '/assets/perfect.png',
		'great': '/assets/great.png',
		'good': '/assets/good.png',
		'bad': '/assets/bad.png'
	};
});
```

### 「スタート」ボタンを押した時のイベント

まずは脳内再生画面へ切り替わるための実装です。
ポイントは以下のjQueryのイベントの部分です。

```/javascripts/application.js

$(function(){
:
:

	// 「スタート」ボタンを押した時のイベント
	$(document).on('click', '#start', function(){
		// スタートボタンを押した時点でのミリ秒
		startTime = Date.now();
		// イラストを変更するメソッド
		$('#illust').attr('src', '/assets/start.png');
		// テキストを変更するメソッド
		$('#introduction').text('スタート！！！');
		// テキストを変更するメソッド
		$('.start-message').find('p').text('ハイ！！！のタイミングでタッチ！！')
		// ボタンのテキストとidを変更するメソッド
		$(this).attr({
			'value': 'ハイ！',
			'id': 'end'
		});
	});
});
```

```/views/ultrasoul/ultrasoul.html.erb
:
<input type="button" value="スタート" id="start" class="btn btn-default"></input>
:
```

<img width="282" alt="スクリーンショット 2018-12-14 14.47.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/271955/4c82aee3-101e-b033-245e-975793b8cc66.png">


このボタンを押した時に
id="start"のjQueryオブジェクトを対象にしたイベント内の処理が実行されます。

>【脳内再生画面】
・スタートボタンを押した時点でのミリ秒の取得

まさにこのミリ秒の取得を行なっています。
その後の処理は、見出しのテキストを変更したり、ボタンのテキストやidを変更するメソッドを実行しています。

### 「ハイ！」ボタンを押した時のイベント

```/javascripts/application.js

$(function(){
:
:

	// 「ハイ！」ボタンを押した時のイベント
	$(document).on('click', '#end', function(){
		// ハイ！ボタンを押した時点でのミリ秒
		endTime = Date.now();
		// 残りミリ秒を計算
		totalTime = endTime - startTime;
		// 秒に直す
		let resultTime = totalTime / 1000;
		// 「ハイ！」が66秒目なので、そこからさっき計算した秒数を引く
		resultTime = 66 - resultTime;

		// 非同期通信を行う
		$.ajax({
			url: '/result', // URLは”/result”を指定
			type: 'GET', // リクエストのタイプはGET
			dataType: 'json' // データの型はjson
		})
		// 通信に成功した場合の処理
		.done(function(data) {
			// illsutChange関数からの戻り値を受け取り画像を変更
			$('#illust').attr('src', illustChange(resultTime));
			// 要素を隠すメソッド
			$('#introduction, .start-message, #ready').hide();
			// 要素を表示するメソッド
			$('.end-message, #again').show();
			// ultraSoul関数からの戻り値をもとにテキストを変更するメソッド
			$('.result-message').text('「 ' + ultraSoul(resultTime) + ' 」');

			// パーフェクトであれば「やり直し」ボタンを表示しない
			if (resultTime <= 2 && resultTime >= 0) {
				$('.retry').hide();
			}
		})
		// 通信に失敗した場合の処理
		.fail(function(data) {
			// アラートで失敗した旨をダイアログで表示
			alert('エラーです。もう一度「スタート」を押してください。');
			// ボタンのテキストとidを変更するメソッド
			$('#end').attr({
				'value': 'スタート',
				'id': 'start'
			});
		})
	});
});
```

id="start"からid="end"に変更されたボタンを押すことにより、
id="end"のjQueryオブジェクトを対象にしたイベント内の処理が実行されます。

>【結果画面】
・ハイ！ボタンを押した時点でのミリ秒の取得
・差分の計算（秒数に直す）
・実際の「ハイ！！！」のタイミング(1分06秒)から差分を引く

以上の機能を全て実行しています。

### Ajax(非同期通信)

そして、
>・差分を引いた結果の秒数によってメッセージや画像の切り替え

については、Ajax（非同期通信）を用いています。
以下の記事を参考にしました。
https://qiita.com/__tambo__/items/409ccf256e84017ea307

### 関数

そして、差分を引いた結果の秒数に基づくメッセージと画像が表示できるように以下の関数を追加しましょう。

```/javascripts/application.js
$(function(){
:
:
	/**
	 * 秒数によって結果画面で表示するメッセージを戻り値として返すための関数
	 * @param {Number} time 秒数を受け取る
	 * @return {Object}     結果メッセージを返す
	 */
	function ultraSoul (time) {
		if (time <= 2 && time >= 0) {
			return messages['perfect'];
		} else if (time <= 5 && time >= 0) {
			return messages['great'];
		} else if (time <= 10 && time >= 0) {
			return messages['good'];
		} else {
			return messages['bad'];
		}
	}

	/**
	 * 秒数によって結果画面で表示する画像を戻り値として返すための関数
	 * @param {Number} time 秒数を受け取る
	 * @return {Object}     結果メッセージを返す
	 */
	function illustChange (time) {
		if (time <= 2 && time >= 0) {
			return src['perfect'];
		} else if (time <= 5 && time >= 0) {
			return src['great'];
		} else if (time <= 10 && time >= 0) {
			return src['good'];
		} else {
			return src['bad'];
		}
	}
});
```

これでメッセージと画像の切り替えが出来るようになりました！

## あとがき
もともと画像はいらすとやではなくて北島康介さんを使用して~~個人的にめちゃくちゃ面白かった~~のですが、著作権の関係上使用することを諦めました。悔しいです。
~~北島康介さんの写真が著作権フリーになるのを僕は待ち続けます。~~

とりあえず言えることは、アウトプットめちゃくちゃ大事。

完