作ったもの
今回の春ハッカソンのテーマである「かん」「きゅう」。私たちは、写真における「緩急(かんきゅう)」と、撮影という「瞬“間”(かん)」の取り方に焦点を当て、traPの伝統文化である「躍動」の写真を“簡”単に撮影・共有できるSNS「shaQ(シェイク)」を開発しました!
そもそも躍動とは
traP部内で語り継がれる「躍動(yakudo)」とは、以下のような意味で使われます。
スマホで写真を撮る際、シャッターを押す瞬間にカメラを前後に勢いよく振ることによって、手動で得ることのできる(放射状の)ブラーの演出のこと(下の写真を参考)。まるで集中線をおいたかのような存在感、躍動感が見る者の草:w:を誘う!アプリなどを使うことなく手軽に躍動感を演出できる一方で、綺麗な放射状のブラーを得るには、カメラを振るタイミングやその角度を適切に制御する必要があり、見た目以上に難しい。そのため、美しく大胆な躍動感を演出するにはそれなりの熟練が必要になる。一発撮りが美徳とされているらしい。
-traP wiki Dictionaryより
(参考 : あなたの知らない躍動の世界 )

現代における課題
しかし、現代においてこの文化を実践しようとすると、様々な「課題」に直面します。
-
スマホの性能が「優秀すぎる」問題
近年のスマホは手ブレ補正が標準搭載されており、勢いよくスマホ振っても写真がクリアに撮れてしまいます。かつてのような「躍動感」を物理的に作り出すことが、極めて困難になりました。 -
いろいろな場面で躍動を撮りたい
集合写真のような撮り直しがしにくいタイミングでは、カメラを振って躍動写真を撮るのは難しいです。また、いつでもどんな場面でもきれいに躍動写真を撮るには、技術とタイミングが求められ、気軽に楽しむにはハードルが高いのが現状です。 -
「躍動」を共有したい
これまでのyakudoはtraQやTwitter(現X)等のハッシュタグ文化の中で発展してきましたが、投稿が他の話題に埋もれてしまいがちです。私たちは、撮影した瞬間の躍動感を、もっと気軽に「躍動写真だけが集まる専用のSNS」を求めていました。
shaQでできること
shaQは、特別な技術を必要とせず、誰でも“簡単”に躍動写真を撮影できるWebアプリです。
- 躍動写真の撮影
撮影時は通常通りシャッターを切ります。その後、スマホを振ると、加速度・角速度センサーがスマホの動きをキャプチャ。振る勢いやひねりに合わせて、写真にリアルタイムでぼかし加工を生成します。これにより、最新の手振れ補正を搭載した端末でも簡単に躍動写真が撮れます。

shaQ が動作する様子
- 躍動の共有
ユーザー全員が投稿した躍動写真がタイムラインに並ぶ新しいSNSです。撮影した瞬“間”を集約して共有できます。

技術的概要
フロントエンド
フレームワークにはVueを使用し、Vue Router を用いてSPA(リロードを伴わないページ遷移)として実装した以外は、ほぼバニラという感じの構成になっています。これは 決して事前に技術スタックの検討をする時間が取れなかったからではなく、 部内で開催される「Webエンジニアになろう講習会」で採用されている構成との差異をなるべく小さくして開発初心者の壁を低くすることが目的です。
-
スタイリングには「Webエンジニアになろう講習会」に合わせて Scoped CSS を使用。Tailwind CSS からPreflightだけ借りてきて土台を作っています。
-
コンポーネントライブラリは使用しませんでした。既存のラインナップを確認したり、Figmaの仮デザインに合わせてスタイルを上書きしたりする時間を少なくすることを重視しました。
-
APIクライアントライブラリは使用せず、fetchによってクライアントを手書きしました。ハッカソンの短期開発においてAPIの最新仕様を定義したファイルの存在には期待できません。バックエンドとは簡単なMarkdownファイルで情報を共有していました。今思うとaxiosくらいは使ってもよかったかな…。
shaQのフロントエンドで特筆すべき点は、やはりアプリの機能の核を満たすためにハードウェア(デバイス)のカメラやセンサーの値を複数種類活用しているところです。
カメラへのアクセス
まず、写真投稿アプリとしてのshaQは当然デバイスのカメラにアクセスする必要があります。そこでWebアプリからカメラにアクセスするアプローチについて調べたところ、主に次に述べる2通りの方法があり、それぞれに長短があるということがわかりました。
デバイスに備わるカメラシステムを使用する方法
ブラウザでチャットツールなどを開いて利用すると、送信ボタンの近くに写真を撮影するボタンがついていることがあり、ブラウザからカメラを起動させることができます。このボタンは次のような input 要素として表現されます。
<input type="file" accept="image/*" capture="enviroment" />
これを用いて、次のような手順でWebアプリからカメラを起動し、ユーザーに写真を撮影させて利用することができます。
- 要素をタップしてデバイスのカメラを起動
- デバイスに備わるカメラシステムで写真を撮影
- ユーザーが撮影結果について決定操作をしたのち、アプリ側に制御が戻ってくる
この方法には、撮影部分を信頼できるデバイスのカメラシステムに丸投げすることで、導入が容易、バグを減らせる、さらに写真の画質もよくなるといった様々なメリットがあります。ただ一点 撮影中にアプリが一切介入できない というデメリットを重く見て、shaQでは次に述べる方法を採用することにしました。
カメラの映像をページ上にリアルタイムで映す方法
onMounted(async () => {
videoRef.value.srcObject = navigator.mediaDevices.getUserMedia(constraints)
})
- Navigator.mediaDevices ... 接続されたメディア入力装置の情報
- MediaDevices.getUserMedia() ... 引数に欲しい情報の種類(カメラの映像など)を渡して接続
これを記述したコンポーネントが読み込まれると、自動的にユーザーに対してブラウザ上でカメラの使用許可が求められ、ユーザーが許可すればWebアプリはカメラの動画をページ上に写しながら使用することができます。MeetのようなWeb会議ツールにもおそらく同様の権限が与えられているはずです。
- ページ上にカメラの動画を映す
- 撮影ボタンを用意し、押されたタイミングでページ上の動画部分をキャプチャ
これならアプリが常に制御を保っているので、撮影ボタンを押した瞬間に次のページへ遷移したりできます。手順が一つ減り、ユーザー体験は向上します。ところで、ページ上の限られた領域をキャプチャするわけなので、得られる写真はふつう500KBに満たない小さくて画質の悪いものになります。しかしshaQに限っては結局ぼかし加工を施すことになるので撮影直後の写真の画質の良し悪しはほとんど問題になりません。むしろ、画像サイズの小ささが読み込みにかかる時間を短縮し、ユーザー体験にプラスの影響を与えることになりました。
加速度・角速度センサーへのアクセス
shaQはその読み方(シェイク)の通り「振る」ことによって撮影した写真にぼかし加工を加えていきます。デバイスが振られていることを検知するには、デバイスの加速度・角速度センサーにアクセスして値を監視する必要があります。
- DeviceMotionEvent.acceleration ... デバイスの3方向の加速度
- DeviceMotionEvent.rotationRate ... デバイスの3軸の角速度
ちなみに本番コードでは開発のてんてこ舞いの中でなぜか acceleration ではなく accelerationIncludingGravity の方が使われてしまっているのですが、重力加速度が手でデバイスを振る速度より小さかったおかげか、とくに誤検知らしきものはなく、振った分だけ画像がブレるという素直な挙動を見せてくれています。
画像のぼかし加工
ぼかし加工そのものは canvas 要素 に半透明な画像をたくさん重ねていくことで表現しています。画像を重ねながら拡大していけば放射ぼかしを、縦横に移動させていけばモーションぼかしを表現することができます。
Photo taken by Blessing Ri from Unsplash
さて、躍動のステレオタイプは写真に放射ぼかしをかけることで表現されますが、概念実証の段階で様々な画像に放射ぼかしをかけてみた結果「なんとなくつまらない」という感想を抱きました。おそらく原因は
- ボケ方がワンパターンで、タイムラインを見ていて飽きてしまう
- 放射ぼかしでは写真の中央付近があまりボケない
といったことにあったのだろうと思います。
躍動には様々な種類があります。ベーシックな放射ぼかしの躍動のみならず、被写体が移動しているように写るモーション躍動、被写体が回転しているように写るローリング躍動など色々あり、これらの合わせ技もあり、それがゆえのカオスが評価され、躍動という文化を今日まで存えさせてきました。この表現の多様性をshaQが反映しないわけにはいきません。
議論の結果、開発の途中で acceleration の情報に加えて rotationRate の情報を合わせた6自由度を全て用いてぼかし加工を調整するという方針に切り替えました。shaQの完成披露後にユーザーによって投稿されるたくさんの写真はさながら従来のやり方(デバイスを振りながらシャッターボタンを押す)で撮影したかのように見事にてんでばらばらな躍動をしていて、この方針に切り替えた判断を強く肯定するものでした。
バックエンド
Go で書いています。サーバーのライブラリは Echo 、DB へのクエリには sqlx を使っています。例年通り Web エンジニアになろう講習会と同じ構成です。
shaQ の通常のアプリとは異なる部分は、画像を扱う必要がある点です。今回は、部内のオブジェクトストレージに画像データを保存しました。画像を配信する際は、サーバーアプリで直接配信するとアプリ側の帯域と CPU を消費してしまうことが考えられたので、アプリはオブジェクトストレージ上の画像の Presigned URL をレスポンスで返し、フロントエンドには直接オブジェクトストレージにアクセスしてもらいました。今度はオブジェクトストレージ側の帯域が足りるか心配でしたが、ハッカソンの試遊期間でも問題なく動きました。もっとアクセスが増える場合は CDN を噛ませるなどの工夫が必要になると思います。
感想
hachimitsu
バックエンドを担当しました。1年生の時に、春ハッカソン、ワンマンソン、冬ハッカソンとWebで参加してきましたが、少しずつできることが増えてきて成長を感じました。今回では、ワンマンソンの時にはうまくできなかったNeoShowcaseの認証部分を初めて実装しました。この調子で一歩ずつできることを増やしていきたいです。
haruki_0920
初めてのハッカソンでしたが、とても楽しむことができました!プログラミングの経験も浅く心配も多かったですが、02班の皆さんが優しく教えてくださったのでなんとかやり切ることができました。この経験を糧にしてこれからも自分のスキルを磨きつつ開発を頑張っていきたいと思います!
ikura-hamu
リーダーをしました。今回は自分はあまり実装に手を回さず、他のメンバーに実装を頑張って自分はレビューなどをしました。いきなり一番難しそうな仕事を割り振ってしまうなどして、失敗したなと思いました。メンバー全員が頑張ってくれて、最終的に完成したので良かったです。
kaomojikun
ハッカソンにWebで参加するのは二回目ですが、昨年度はフロントエンドとして、参加させていただいたので、バックエンドを担当するのは初めてでした。アイデア出しの際に、お題に沿った面白い案がたくさん出てきて、楽しかったです!これを機にフロントエンドもバックエンドもどちらも頑張っていきたいです!!
kitsne
shaQはカメラや加速度といったハードウェアの情報と密に連携するWebアプリであり、私には類するWebアプリの実装経験がなかったので、この部分の調査と実装をねこりせくんとはるきくんにお願いすることにしました。結果としてハッカソン開始直後から全員にタスクがある状態になってよかったと思っています。例によって最後に駄目を詰める作業が遅滞して危なかったですが、なんとか完成に漕ぎつけられてよかったです。
neko_reset1024
初参加のハッカソンで、web開発ほぼ未経験の状態で参加させてもらいました。アイデア出しの段階では、漠然とした実装への不安がありましたが、先輩方のお陰で仕事をもらえ、自分の担当したところは形にはできたと思います(issueは先輩方に:solved:してもらいました)。当たり前に思っていたSNSの機能の実装を、納得できるクオリティにまで持っていくのにはこんなに労力がいるんだと思い知らされました。今後のイベントにも参加したいので、今回の実装を振り返りつつ経験を積んでいきます。