JavaScriptで画像プレビュー機能を実装してみた
ここ最近、エンジニアとしてスキルを伸ばす方法を色々と考えていました。自分で考えていても答えは堂々巡りになるばかりだったので、思い切って自社のエンジニアランチで「どうやったらエンジニアとしてのスキルが伸びるのか?」という質問をしてみることに。すると、色々な情報や方法を皆さんから提示頂くことができ、その中で幾つか共通していたのが「自分の作りたいものを作る」というものでした。
そういえばこのブログの前回の記事でも、同様に「自分のサービスを作る」ということを記載していることを思い出し、改めてこの勉強方針に自信を持ちました。ということで、今回から「自分のサービスを作る過程で学んだこと」を週1回程度のペースで振り返る、ということを実践していきたいと思います。
開発しているサービス
「自分のサービスを作る」というお題目において、今回題材にしようと思ったサービスが「旅行のコースをシェアする」サービスです。これは3ヶ月ほど前に考案したサービスです。自分は物臭なため「誰かが行った旅先のプランが見れたらなぁ」というなんとも他人任せな考えから生まれました。そんなサービスに誰がプランを投稿するねん、というツッコミは一旦おいておき、サービスのイメージはある程度自分の中でもできているので、とりあえず着手をしてみることにしました。
ちなみにリポジトリ自体は2ヶ月前に立てていましたが、大分入り口のところで開発の手が止まってしまっていたので、そちらを再開させることとします。GitHubのリポジトリは下記です。興味があれば御覧ください。
今回やったこと
前回の勉強を活かす&JavaScript苦手意識の克服のために画像のプレビュー機能の実装をしました。今回書いたコードは下記にあります。
https://github.com/toyokappa/tabimemo/pull/32/files
このリポジトリ上には残ってはいませんが、途中までの実装はJavaScriptで書き、最終的な実装はCoffeeScriptで記述をしました。今後もRailsを書いていくにあたりCoffeeScriptは避けては通れない道だと思ったので、記法に慣れるためのチャレンジです。
今回の画像プレビュー機能を実装するにあたって、苦戦したポイントは大きく2つでした。1つは「変数のスコープの理解」で、もう1つは「Turbolinksによるイベントハンドラの設定方法」です。
変数のスコープの理解
今回の実装にあたり、書籍の中にちょうどいい題材として、JavaScriptで画像ファイルを読み込み表示させるという内容があったので、そちらの内容を写経しつつ、今回の要件に合うようにアレンジをしていきました。
アレンジをする中で特に詰まったのが変数を定義する場所により、意味合いが大きく違ってくるという点です。今回複数の画像を読み込んで表示をさせる様にしたかったので、読み込んだファイル郡をループさせて取り出す、というような処理をさせたかったのですが、イマイチうまくいきませんでした。具体的には、当初下記の場所で作成した要素を変数へ格納していました。
createPreview: (e) => files = e.target.files parent = e.target.parentNode for file in files # ここで要素を変数へ格納 li = document.createElement "li" li.className = "preview" img = document.createElement "img" reader = new FileReader() reader.readAsDataURL file reader.addEventListener "load", (re) -> img.src = re.target.result li.appendChild img parent.querySelector(".photo_area").appendChild(li) return
結果として2つ以上の画像ファイルがある際に1つしか表示されないという、予期せぬ結果となってしまいました。色々と試行錯誤をする中で、最終的に下記の通りのコードに行き着きました。
createPreview: (e) => files = e.target.files parent = e.target.parentNode for file in files reader = new FileReader() reader.readAsDataURL file reader.addEventListener "load", (re) -> # ここで要素を変数へ格納 li = document.createElement "li" li.className = "preview" img = document.createElement "img" img.src = re.target.result li.appendChild img parent.querySelector(".photo_area").appendChild(li) return
このコードでは期待したとおり、2つ以上の画像がある場合でもしっかりとすべての画像が表示されます。これはJavaScriptの変数のスコープの問題で、ブロックの外で指定した変数はブロックの中ではグローバル変数として扱われてしまうということで理解しました。変数が柔軟な分、こういった点にも注意を払わなければならないという良い学びになりました。
Turbolinksによるイベントハンドラの設定方法
上記を解決しこれで実装完了!と思い改めて動作確認をした際に、コードが上手く動かないという問題に直面をしました。「先程までちゃんと動いていたのになんでだ!?」という思いが駆け巡りました。
調べていくと今回の原因が「Turbolinks」にあるということがほのかに匂い始めました。今までは「Turbolinks、そいえばRailsの標準のGemに入っていたなぁ」ぐらいにしか感じていませんでしたが、Rails×JavaScriptを学び始めたこの機会に強敵として立ちはだかりました。
どうやら「Turbolinks」はRailsの動作を高速化してくれるものらしく、JavaScriptやCSSの無駄な読み込みはせず(初回以降は行わず)Ajaxで画面の遷移を行う様にしてくれるものらしいです。(ここはもっと理解を深めたいなぁ)
この流れを行うため、$(document).ready()
や$(window).load()
が発火せず、意図したイベントハンドラの動作が行われず、今回のようにさっきまで動いていたコードが動かなくなる(正式にはindex
やshow
などの他の画面から遷移した際に読み込まれなくなる)ようです。
具体的には下記の通りにページがロードされた際に画像プレビューのコードが作動するように書いていました。
$(window).load -> new previewImages($(".spot_container"))
結果的にAjaxでページが丸ごと読み込まれることがなくなるため、このコードは作動しなくなってしまうようです。色々と調べた結果、最終的に下記のようなコードにたどり着きました。
$(document).on "turbolinks:load", -> new previewImages($(".spot_container"))
先程のコードとの違いとしては、Turbolinksが提供するTriggerの「turbolinks:load」が行われた際にコードが読み込まれる様になるようです。これによりAjaxでページが遷移した場合でもしっかりと画像プレビューのコードが作動するようになります。
まとめ
今回の自分の実装を振り返る機会を設けてみましたが、書き終えてみてとても素晴らしいメリットに気づきました。それは何かというと「自分の曖昧に理解している点」を理解できるということです。要は、上記で結構断定的に書いていますが、正直自信はないですwということです。
「なんで変数の格納場所が違うとだめなんだっけ」とか「Tubrolinksの動きってどんなんなんだっけ」とか、書いていく中で「あーやべぇ、適当に書いてるなぁ、あとでもっかい調べなおさなきゃ」という気持ちになりました。
なので、この記事の内容の正確性にはあまり自信がなく、胸を張ってお伝えできているかどうかがわからないため、時間がある時に自分の曖昧な知識を再度調べ上げて、この記事の正確性を高められればと思います。その為、この記事は書籍でいう初版ということで、今後も理解が深まった高まったタイミングで更新をしていきたいと思います。
最終的にこのまとめに書かれている自信のなさを打ち消し線で消せる日が来ることをモチベーションに引き続き頑張っていきたいと思いますw
ということで少し長くなりましたが、今週の振り返りでした。