制作日誌

Vue.jsで診断アプリ作成

Vue.jsのアウトプットであり
私がプログラミング学習を始めた動機でもある
診断アプリを作成しました。


当初は「javaScript使うんじゃろ?よーわからんけど」レベルの知識しか無く
それ以前に

「javaScriptってブラクラでよく使うやつやろ?
あとマウスカーソルに猫つけたりするのに使うやつ」


という大いなる誤解・・というか
古の知識(20年前ww)しか持ち合わせてなかったのが
半年でまさかフレームワークにまで手を出せるようになるとは
思っていませんでした(*´ω`*)

いや、教材や環境にも恵まれたけどやりゃ出来るもんだねホント。

今回の目的


「育児ブログに診断アプリ付けたい!」


これがメインです。
去年の9月から温めてそろそろヒヨコが孵りそうなレベルのネタです。
この辺の細かい経緯は育児ブログ側に書きましたので
こちらをどうぞ。

それ以外の目的は以下の通り。

  • Vue.jsでの開発に慣れる
  • そもそも苦手意識のあるJSで作品を作ることで自信を付ける
  • 開発環境の更なる整備と洗練化
  • 今後診断アプリ作成依頼を受けても困らないように
    出来る部分はテンプレート化

開発環境

  • MacBook Pro (Retina, 15-inch, Mid 2015)
  • MacOS Mojave (10.14)
  • VSCode
  • Sass, Gulp とかWebpackとかbabelとか色々(良く分かっていない)
  • Vue.js (2.6.10)
  • Adobe XD (カンプ作成用)
  • CLIP STUDIO iPad版とApple Pencil (画像加工用)

package.jsonはこんな感じ
ずっと使い回してるから絶対いらんもん入ってる。

{
  "dependencies": {
    "jquery": "^3.4.1",
    "lodash": "^4.17.11",
    "vue": "^2.6.10"
  },
  "devDependencies": {
    "@babel/core": "^7.4.5",
    "@babel/preset-env": "^7.4.5",
    "@babel/register": "^7.4.4",
    "autoprefixer": "^9.5.1",
    "babel-loader": "^8.0.6",
    "babelify": "^10.0.0",
    "browser-sync": "^2.26.5",
    "classnames": "^2.2.6",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.2.2",
    "gulp": "^4.0.2",
    "gulp-babel": "^8.0.0",
    "gulp-changed": "^3.2.0",
    "gulp-clean-css": "^4.2.0",
    "gulp-eslint": "^5.0.0",
    "gulp-imagemin": "^5.0.3",
    "gulp-notify": "^3.2.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^8.0.0",
    "gulp-progeny": "^0.4.1",
    "gulp-sass": "^4.0.2",
    "gulp-webpack": "^1.5.0",
    "vinyl-source-stream": "^2.0.0",
    "vue-devtools": "^5.0.0-beta.1",
    "webpack": "^4.32.2",
    "webpack-stream": "^5.2.1"
  }
}



制作過程

ワイヤーフレーム制作

何はなくともこれからだよね!
いや、診断アプリレベルならいらないのかもだけど
きっちり順序決めてやった方が、後々齟齬が起きにくくていいかなと・・

で、毎度おなじみ

本人にしか何を書いているのか分からないワイヤーフレーム。
一応どんな機能をつけるのかもこの段階で書き出しています。

機能洗い出し

現時点ではざっくりと。カンプやモック画面を作りながら
ちょっとずつ詳細を詰めていきます。

今回の診断アプリでは

  • スタート画面でボタンを押したら質問画面へ
  • 回答ボタンを押したら点数計算して次の画面へ
  • 最終の回答ボタンを押したら集計結果と診断結果を表示
  • 結果画面からリトライボタンで質問画面の最初へ
  • ツイッターとかでシェア?出来そうならつけてみる?

こんな感じの機能を付けることに。
本当に出来るのか?この段階では
「ググればいけるんちゃう?」レベルですww

(ここまでの作業時間: 30分)

デザインカンプ制作

XDで作成。
配色はネットで見つけた配色パターン集から選択。
背景画像はCLIP STUDIO(iPad)で描いています。

 ・・質問と結果はネタです。
あまり奇をてらわずにシンプルに。
対象は育児ブログの閲覧者なので、万人受けしそうだけど
ちょっと柔らかい印象にしてあります。

(この段階の作業時間: お絵描きまで合わせて2時間半)

他の人の作品を参考にしようそうしよう・・無いな!?

診断アプリを作りたいと思い始めた頃から作り方は調べてまして
(そもそもこういう物をwebアプリと呼ぶと言うことすら知らなかったあの頃)
最初にたどり着いたのがここ

「jQuery何ぞこれ・・うげぇまたJavaScript貴様かああぁぁぁ!!!orz」
と過去の挫折トラウマが蘇った瞬間でもありました。
なので、jQueryで作れるのは(出来るかどうかはともかく)知ってましたが
今回はVue.jsで作りたい。

・・同じJSだからきっと作れると思うんだ、と
ググりまくった結果


Vue.jsで診断テストを作ろうなんて酔狂な奴はめったにいない

という事実が発覚したのでした・・(´;ω;`)ウッ

唯一希望を持たせてくれたのがこの記事。
ホント書いてくれてありがとう、感謝しかない。

・・多分この記事を見つけなければ
「Vue.jsで診断アプリは無理なんかのう・・」と諦めていたかと。
先人の努力の跡が残っているの大事ね。(だからこうやって書いてるんですが)

元ネタのデータチェック

元々今回作ろうとしている診断アプリは
子供が学校で受けたチェックを日本語に翻訳したものでして
翻訳した段階でgoogleスプレッドシートを使って診断できるようには
作ってありました。

なので質問項目と点数に関しては既に問題なし。
ただ「集計した点数を見て判断する」形だったので
結果画面に表示する文章は作成していません。

てことでモック画面作成の前に診断結果画面用の文章作り。
・・意外とこのカンプで作ったスペースで理解できる様に
簡潔な文章を書くのは気を遣いますね。

画面モック作成

この間
「画面の切り替えってどうやるの?なにvuex ? vue-router !?
また猫本に戻って勉強してからじゃないと作れないんじゃろうか・・(;´Д`)」
と思いつつググってたらこんなのを見つけまして。

なるほど、これを見ていたら出来そうな気がしてきた・・
今回切り替えボタンもコンポーネント内に入れちゃうけど
それでも出来るかな、まぁ試してダメなら猫本に戻ろうと決めて
まずは画面モックを作成しました。

で、作り始めてから

レスポンシブ対応どうしよう・・

と悩む(;´Д`)

何だっけ?リキッドデザイン?とかいうやつ。
拡大縮小しても文字のバランスが変わらないようにしたいんだよなぁ・・
何せ育児ブログに貼るから、スマホで見る人も多いと思うんだよねぇ。

結論が出なかったのでこれはちょっと後回しにして、先にPC用の画面だけ作成。
それぞれの画面を作ってはコンポーネントにし
ちゃんと表示できることを確認しました。

(画面モックPC版作成時間: 3時間)

画面遷移機能を付ける

まずここでハマりましたorz

上で紹介したサイトを参考に、まずコンポーネントの置き場所を
index.htmlに作成

<div v-bind:is="currentPage"  v-on: start-check="moveCheck"></div>

スタート画面のコンポーネントに入っているボタンを押すと
start-checkがonになるようにしてみる

<button class="btn btn--main btn--large" v-on:click="clickStart">診断をはじめる</button>
  methods: {
    //スタートを押したら質問画面へ移動
    clickStart() {
      this.$emit("start-check");
    }
  },

で、親の方にデータとして「currentPage」を持たせて
moveCheckというメソッドを書いて、このデータを質問画面のものに置き換える。

  data: {
    currentPage: "start",
    score: []
  },
  methods: {
    //スタートで質問画面へ
    moveCheck() {
      this.currentPage = "checklist";
    },
  }

これで画面が遷移するように
・・なりませんでしたorz

いや理論はこれで合ってると思うんだけどなぁ・・と
猫本やウェブカツのソース見たり、ググったり
console.logで色々出力してはdevツールとにらめっこする事1時間。
原因は

//誤った書き方
v-on: start-check="moveCheck"

//正しい書き方
v-on:start-check="moveCheck"



v-on:の後に半角スペースが入っていた

・・うん、きっと初心者あるあるなんでしょうこれは・・(ノД`)シクシク
そこだけ直したらすんなり動きましたよ、ええ。
しかしこれは前途多難だわ・・

と、この段階では相当時間かかることを覚悟してました。

ともあれこの調子で、スタート→質問→結果→リトライでスタート、の
画面遷移は全て実装できました。

質問を1問ずつ表示する

これは前回のtodoアプリで使った
「todoリストのリアルタイム検索」をそのまま活かしました。

まずはtodoリストと同じように質問をリストでだばっと表示するようにして

  <ul>
 <li v-for="(question,index) in filteredItems" v-bind:key="question.id">
  <h1>Q{{question.id}}.</h1>
  <div class="question">
    <p v-html="question.text"></p>
  </div>
  </li>
  </ul>

実際に表示する項目は質問コンポーネントに持たせた
今の質問IDをキーワードにしてmethodで検索、computedで算出して表示。

 data: function() {
     return {
       //現在の質問番号
       currentQuestion: 1,
   }
 },
computed: {
    //フィルタした質問を表示
    filteredItems() {
      return this.searchItem(this.questions, this.currentQuestion);
    }
  },
  methods: {
    //現在の質問のみを取り出す
    searchItem(list, key) {
      return list.filter(function(question) {
        return question.id === key;
      });
    },
},

回答ボタンを押すと、この質問番号が1つずつ増えるようにして
次の質問へと移動します。

回答したときの加算機能

回答ボタンを押して加算用のメソッドを呼び出し。
その時に引数でポイントを渡して、あらかじめ用意してあるスコアの配列に
加算していくだけです。

リトライした際に前の値が残ってるとエライコッチャなので
1問目は回答した段階で一度スコアを全部0にするようにしました。

また24問目を回答すると、結果画面へ遷移。
この際、親にスコアの入った配列を渡しています(後述)。

(質問表示と回答加算機能制作: 1時間)

結果の表示機能

こちらも質問と同様です。
判定した結果をキーワードにして結果の配列を検索、それを表示するだけ。

それぞれの分野の合計スコアは・・どうやって質問のコンポーネントから貰うかは
後で考えるとして、まずは結果のコンポーネントに値があれば表示できる形に。

この辺は割とすんなり行きました。

(結果表示機能制作: 30分)

コンポーネント間でデータをやり取りする

質問画面のコンポーネントで各分野のスコアを付けていき
24問目が終了した段階で、結果画面のコンポーネントにこれを渡して
診断結果を計算させたい。

てことで、非親子コンポーネントでのやり取りをどうするか。
猫本には「イベントバス」を使う方法が載っていましたが
ググるとそれ以外にも方法はあるのね。

今回は、親を介してProps down/Events upをする事に。
サイトと猫本とGoogle様に頼りつつ試行錯誤すること2時間半

まず質問コンポーネントから親へ

//24問目クリック時にfinish-checkを発火させ、スコアを渡す
 this.$emit("finish-check", this.score);
//finish-checkが発火したら親のmoveResultメソッドを呼び出す
<div v-bind:is="currentPage" v-on:finish-check="moveResult></div>
//質問画面からscore貰って結果画面へ
    moveResult(score) {
      this.score = score;
      this.currentPage = "result";
    },

これで親にスコアが渡ったので、次はここから結果のコンポーネントへ

// v-bind:score="score"を使って親のスコアを送る
<div v-bind:is="currentPage" 
  v-bind:score="score"
  v-on:finish-check="moveResult" ></div>
 //結果コンポーネントではpropsでscoreを待ち受ける
 props: {
    score: Array
  },

これで質問コンポーネントから結果コンポーネントにscoreが渡りました。

ちなみにここでも詰まったのですが、今回の原因は

送り出す親の方の属性を v-bind:val=”score”にしてた


そら動かんわ!本に載ってたのを見ながらやったのはいいけど
送り出し側と受け取り側で属性名は同じにしないと迷子になるよね!!

いや、もうね、こういうしょーもない原因で引っかかって
頭禿げそうに悩むものなんだなーと痛感しましたわ・・
ベテランの皆様からしたらpgr微笑ましいレベルなんでしょうけど・・orz

結果の判定機能

これはごろ寝しながらアルゴリズムを考えてあったのですんなり。
ただのif分の組み合わせ→判定結果をデータ置き換えするだけです。

・・まだ読める、これならまだ読める、な!

ちょっとPC使いすぎると途端に体調を崩すので
調べ物とかこういうのを考えるのは横になりながら
スマホ片手にググりつつ、コピー用紙にごりごり書いてます。

今回絶対値が必要だけどどうやってやるんかなーとググったら
ちゃんとMath.abs()ってのがあるのね。プログラミングって凄いのねぇ(小並感

(結果判定機能制作: 15分)

画面にアニメーションを付ける

ボタンのアニメーションは既に指定してあったので
それぞれの画面の切り替え時と、次の質問への移動時にアニメーションを入れました。

で、案の定ここで問題が発生。
リストで作ったせいか、質問切り替えにアニメーションを入れると
次の質問が前の質問の下に出てレイアウトが崩れる・・

これは.question-leave-activeに「position:absolute;」を設定して解決。
したものの、今度は切り替え時に前の質問の幅が妙に狭くなって
何となく見た目的に引っかかる。

こちらはulにflexboxを使い、li要素の幅を調整することで対応しました。

後は猫本で学んだとおり、transitionとtransition-groupを使って実装。

(アニメーション実装とCSS調整: 1時間)

レスポンシブ対応、文字をデバイスサイズに合わせて拡縮させる

今回はなるべく要素を%指定したのですが
文字だけはいかんともしがたく・・となっていたところで発見した記事。

これを参考に、スマホ・タブレット画面では
mixin使って文字の大きさをvwに合わせて変化するようにしました。(PCは固定)
PC版のアプリの幅が800pxなので、その時のフォントサイズを基準にして

  @mixin fontsizeVw($size) {
    $winW: 800; // 画面幅を設定する
    font-size: ($size / $winW) * 100 + vw;
  }

こんな感じで。
一部小さくなりすぎる所は、基準のフォントを1段階上げて対応。

(レスポンシブ対応: 1時間)

シェアボタン

育児ブログはFBとの親和性も結構高いので
ツイッターとFBのシェアボタンを付けることに。

こちらは公式そのまんまです。特にひねったことはしていません。
参考にしたのはこの記事。



デプロイ

・・白状しよう
デプロイの意味を今回知ったんだ、私。

いや英語の意味としての「配置」は知ってたんですけどね。
作った物を一般に使える形に上げることだったんですね。
エンジニア用語は難しいのいっぱいですなー(;´Д`)

さて、今回の診断アプリは育児ブログに上げる事だけは最初に決めていたのですが
wordpressの1ページとして入れるか、サブドメインに置くかで悩んだ結果

ページで置くとヘッダーとかが目立って診断に集中できなくね?


という理由・・と「単にwordpress上に上げるのを調べる時間が惜しい」という
とても実利的な理由wにより、サブドメインを切って上げました。

SSL化とか何気に手間取ったのですが、Hostmonsterなんてレンタルサーバー
使ってる人まずいないだろうから省略。
ちなみに海外サーバーです。育児ブログがここに入ってるからしゃあないね。

まとめと反省点と次回への展望

作業時間

  • ワイヤーフレーム、機能洗い出し: 30分
  • デザインカンプ作成: 2時間半
  • 画面モック(PC)とアニメーションを付ける: 4時間
  • レスポンシブ対応: 1時間
  • Vue.jsの機能を付ける: 5時間15分
  • シェアボタンを付ける: 30分
  • デプロイ(SSL化含む)、実機確認: 30分

合計: 14時間15分

・・まぁ、初めて作ったにしては上出来?ですかねぇ。

目的の達成度

正直ね、これを作るまではJSに体する苦手意識がもの凄かったんですよ。
それがVue.jsというフレームワークに助けられたとはいえ
学生時代とアフィリエイター時代に2度独学を試みてあえなく挫折したような
四十路のおかんでも、ここまでの作品を作れた!というのは大きいです。

もはやVue.js使えば何とかなるんじゃね?(出来るとは言ってない)くらいの
気楽な気持ちでJSと対面できそうな気すらしています。
苦手意識を克服出来たのは本当によかった(*´ω`*)

反省点

  • CSSな!!もっとテンプレ化出来るところをテンプレにして
    実装速度を上げたい、そしてテンプレにした事を忘れないようにせねば
    (今回忘れてた人orz)
  • 詰まったときにはエラーをググる、console.log使いまくる
    それと同時にタイプミスを探そうな!しょーもないミスしてる事多いよ!!
  • レスポンシブの事をデザインカンプ段階で意識しておく
    XDでアニメーション付けるまではしなくても良いけど
    せめてどこにどういうアニメーションを付けようとしてるかメモっておこう



次回への展望

  • SPAという言葉を知ったので、vuexやvue-routerにチャレンジしたい
  • 今回はアプリだったけど、通常のサイトにjQueryの変わりにVue.jsを使って
    よく使われそうな機能を実装+テンプレ化する
  • Laravelと一緒に使えるらしい、とどこかで見たのでいずれこれもチャレンジ
  • シェアボタンに結果別の文言を挿入して投稿出来るように出来ないんだろうか・・
  • その前にwordpressのテンプレ作りと、ポートフォリオまとめサイト作ろうな!



ソースコード

今回はgithubに上げてあります。

これ、最初githubに一体どのファイルを上げたら良いのか分からなくて
何を血迷ったか背景画像までアップしちゃってるのですが(;´Д`)
index.html, srcフォルダにあるapp.js, _main.scss辺りが参考になるのかな。

お役に立てれば幸いです(*´ω`*)

コメントはこちらから

メールアドレスが公開されることはありません。

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください