2021.1.31
『転生したら〇〇〇〇だった件』
: 質問の答えると、”あなたが転生したらどんな妖怪になるか”を判定してくれるwebアプリ。
- 2020.5.28に製作した『転生したら○○だった件』(kichis/Gs_js_final)をリファクタリングしたもの。
- リファクタリングといいつつ「戻るボタン」の追加やスタイルの変更もあり。
- 実際のアプリはこちらで遊べます。
以下、個人的メモ
- SPA : 試してみたかった hash によるSPAで実装(注1)、リロードされた時にhashをクリアにする処理もあり。
- 「前に戻る」「はじめに戻る」ボタン : コンポーネントを戻った際のhashの処理やコンポーネントによってのボタンの表示あるなしも実装できた。
- ブラウザの戻るボタンの無効化 : 「前に戻る」「はじめに戻る」ボタンではなく、ブラウザの戻るボタンで戻ってしまうとポイントの整合性がとれなくなるので、無効化した。
- レスポンシブ対応 : スマートフォン、タブレット、PCのそれぞれにおいてスタイルが崩れないように調整した。
- 旧コードのリファクタリング : 冗長だったJavaScriptコードを半分程度に短縮化。
- ポイント保持機能の変更(localStorage->オブジェクト) : 改めて考えるとlocalStorageでなければならない必要性がなかったので変更。
- スタイル、アニメーション : スタート画面やボタンがシンプルすぎたので、興味を惹かれる(楽しそうだと思ってもらえる)ようにアニメーションをつけた。画像も統一感がでるように変更した。
(いらすとやさん、ありがとうございます。)
注1: ただし、SEOという観点ではhashは好ましくない(googleのクロールがリンクをみつけられないため??)とのこと。
historyAPIの方が推奨される。
- JSでレンダリングしたDOM要素のonclick動作
実装したかった機能 : SPAで表示/非表示を切り替えるコンポーネントのDOMを、オブジェクトの値として格納し、hashchangeに応じて取り出し・レンダリングする。
問題 : レンダリングまでは実装できたものの、レンダリングしたDOMに対するonclickイベントを設定しても動作させることができなかった。
=> 解決できず
選択肢
1,JSで動的レンダリングし、HTMLタグ内のonclickに記述する(これなら動作する)。
2,HTMLでDOMを記述し、displayの値を変更する。(2020verと同じ)
結果 : 2,を採用 (1,は今回の場合、冗長な記述になりそうだったため)
予想(とメモ) : JSで動的レンダリングしているためにイベントリスナが対象のDOM要素を見つけられていないのか?
(通常のJSでのレンダリングだとスクレイピングされた場合にもヒットしないとのことなので注意。フロント系のFWはどうやってヒットするようにしているのだろう。)
- 妖怪判定アルゴリズム : 単純にポイントの多少ではなく、イレギュラーなケース(「山」より「海」が優先される、アマビエの特殊条件など)を設定した。
それ故に、 その条件をどう区別できるデータにするか(ポイントで区別するか、何かのフラグを設定するか?)、 それをどう判別する実装にするか が難しかった。
なるべくif文を多用しないように心がけたものの、二重のif文になってしまっているので改善の余地がありそう。 - レスポンシブ対応 : レスポンシブを意識して作成したのは初めてだったので、フォントやスタイルを決める順番などの要領が悪かった(手戻りがあった)。
結果となる妖怪は全13種。
質問を通して回答者と各妖怪の親和性を計測、ポイントで表し、最終的に一番高いポイントを保有している妖怪を結果とする。(例外 あり)
全13問。
各質問に対して、全ての妖怪の(こう答えるであろう)想定回答が設定されている。
質問内容は各妖怪の特徴を反映しており、特徴が反映されている妖怪をその質問の”担当妖怪”とする。
質問ごとに、以下のように妖怪にポイントを割り振る。
- 回答者と同じ回答をしている妖怪:+1
- 異なる妖怪:+0
- {回答者が担当妖怪と同じ回答した場合}担当妖怪:+4
- {第1問の「好き」を選択した場合} 以降の質問無し&即結果表示 => 結果”垢舐め”
- {第1問の「嫌い」を選択した場合} 同じ回答をした妖怪:+0 (”垢舐め”以外の妖怪は全てこの選択肢で差異がないため)
- {第2問の「海」を選択した場合} 同じ回答をした妖怪:+50
- ({「海」を選んだ場合}結果が 海系妖怪 からのみ選ばれる、という仕様の実現のためポイント数の落差をつけた。)
- (「山」を選んだ場合には、結果が(”アマビエ”以外の)海系妖怪になることもありうる。)
- {第3問の「貝」を選択した場合} 同じ回答をした妖怪(”アマビエ”のみ):+20
- {第2問「海」第3問「貝」を選んだ場合} 以降の質問を全て”アマビエとは逆の回答”にしない限り(=1つでも同じ回答をしたら)、結果”アマビエ”。
「貝」選択有無をポイントから判別するため、ポイント数の落差をつけた。 - {第2問「山」を選んだ場合} 第3問「貝」であっても、結果が ”アマビエ”になることはない。
- {第2問「海」第3問「貝」を選んだ場合} 以降の質問を全て”アマビエとは逆の回答”にしない限り(=1つでも同じ回答をしたら)、結果”アマビエ”。
- 最大ポイントが71以上(=「海」「貝」+アマビエと同じ回答を1回以上) => ”アマビエ”
- 最大ポイントが70(=「海」「貝」+他はアマビエと同じ回答をしていない) => ”カッパ”or”小豆洗い” のポイントの大きい方
- 最大ポイントが70未満、50以上(=「海」+ 「貝」以外) => 最大ポイントを保有する(海系)妖怪
- 最大ポイントが50未満(=「山」) => ”アマビエ”以外で、最大ポイントを保有する妖怪(max15)
※{最大ポイントの妖怪が複数の場合} -> 最大ポイントの妖怪たち + ”一口おばけ”の中からrandam関数で選ぶ。
(この場合のみ、隠しキャラ”一口おばけ”出現の可能性がある)
- 全質問を通して、全く同じ選択パターンの妖怪を作らないこと(カブっていると妖怪の個性で判定できない)
- ”担当妖怪”のポイントにレバレッジをかけ、全体的な回答の傾向だけでなく、妖怪の特徴を重視する仕組みにしたこと。
- 遊びごころ(隠しキャラ、イレギュラーパターン)は必要。
以下、個人的メモ
- シングルプロセス・シングルスレッドの言語。
- 可変長引数 : 引数の個数が不明の場合に使う? "...arguments"
- オブジェクトや配列は参照渡し@JS : 同じメモリの場所を”参照”することになるので、 変数の値のやりとりは注意。
- 関数@JS = クロージャ (関数とその関数が作られた環境という 2 つのものの組み合わせ)
: 関数が作られた?実行された?ときの状態・値を保持して、後ほど呼び出された時にもその保持情報が残っていること。(理解しきれていない)
MDNの例を参照 - JavaScriptには公式ロゴがない。(おおよそHTML5のロゴをもじったロゴか、黄色地の右下に黒文字でJSと書かれているロゴが使われているようだ)
- デフォルトの文字サイズは16px。
- font-sizeの設定
px : 絶対値
% : 親要素の設定font-sizeを100%とする
em : 親要素の設定font-sizeを1emとする
rem : htmlの設定font-sizeを1remとする
vw/vh : ビューポートの横/縦幅を100とする - レスポンシブにするためには、具体的な値(px,rem)より、vw/vh/vmin/vmaxを使用した方がよい。
(vw/vhより、vmin/vmaxの方がスマホ<=>PCの個別修正をしなくて良さそう) - @mediaを使用する場合も、修正するスタイルの前に記述すると修正できないので注意。
- cssアニメーション : 割と簡単に実装できた。
メリット:処理負担がJSより少ない。
デメリット:対応していないブラウザがある?(2012年の記事だったので事情が変わっている可能性はある。MDNには特に言及なし。)
=> 某氏よると、ベンダープレフィックスを付ければたいてい大丈夫、とのこと。 - ベンダープレフィックスはブラウザ独自の拡張機能・草案段階の仕様を先行実装する際につけるもの。正式対応した段階では付けない方がよい(逆に動作しなくなる事例があるらしい)。
- urlエンコードというものについて初めて認識した。
(深く理解できていないが、) 1、urlで使用する文字を適宜変換するルール と 2、formタグに入力した情報の変換ルール の2種類の意味があるよう。 - 浮世絵の著作権について : 著作権者の死後70年経っていれば許可なしに使用可能。
(注意事項 : 1、著作者人格権は失われていないため、著作者や遺族の尊厳を傷つけないような使用法とすること。
2、浮世絵を”ただそのまま撮影しただけの写真”には著作権はないが、”独自性が加わった写真”は著作権が発生する可能性がある。)
- 画面サイズによっては、結果に遷移したとき、トップから少し下がった位置で表示される場合がある。
原因: レスポンシブを考慮し、結果コンポーネントの妖怪説明文をスクロールできるようにした。
かつ、(footerとtwitterを下部に位置固定にしているため、footerに隠されず)全文が見えるように説明文の後半にpaddingを入れている。
そのpaddingの下限に合わせているためと思われる。 - ポイント付与の関数(setPoint)に繋がるonclick動作時、setPoint()の記述前にすでにポイント付与がされているという謎現象。
原因: 不明。
試したこと: 関数とイベントリスナの記述順序を変更する、onclickをアロー関数にする、関数のネストを浅くする(onclick func > setPoint > addPoint => onclick func > addPoint)、setPoint内で変数宣言、2つのaddPointの位置を変えるなど => 解消せず
これを試してみたが、引数として直接console.log()を渡すと期待通りのタイミングで動作する(軽い処理だから?)。
しかし、console.log()をある関数に記述しその関数を引数に渡すと期待通りのタイミングで処理されない、なぜか1回目のnewEventは動作しない、実際に行いたい処理を書いても期待通りのタイミングで処理されない、などの問題あり、不採用。
結果: 解消せずだが、得られる結果には問題ないため、とりあえず置いておく。
- 2020verを製作した時よりも格段にJSの理解が進んだので、実現したいロジックがコードで表現できる、エラーの原因に当たりがつけられる、デバッグの仕方がわかる、手探りで検索してみても記事の言っている内容がわかる..など成長を感じた。楽しい。
- 一方でまだ知らなかったことも学べて有意義だった。
- 最初から細かい関数を作ろうとするより、一度大きい関数で流れを書いた後に分割していった方が良い。
(分割する必要があるほど長い処理にならなかったりする) - 多くのデバイスにレスポンシブ対応させようとすると、なかなか難しい。まだそこまで要領がつかめていない。
- テストコード : 想定通りの実装ができているかを手動で確認するのが辛い。
- bootstrapのprogressバー : 今回はデザイン的にごちゃつくので見送ったが、別の機会に実装してみたい。