まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:rotate

CSS で画面内の特定要素に回転をかけることができる、ということを知り、その様子を視覚的に確認できるサービスを作って公開してみました。

この技術(というほどのものではないけど)は CSS の tranform 属性を使って、回転軸と回転角度を指定することで実現できます。ともあれまずは実際に挙動を確認してみましょう:
https://dotnsf.github.io/transform-rotate/


上記 URL にアクセスすると以下のような画面が表示されます。画像(デフォルトではいらすとやさんの「コンピュータを使うペンギン」のイラスト)と、その下に3つのスライダーバーが表示されているはずです:
2020020101


x のスライダーバーを動かすと、動かした角度だけ画面が縦方向に回転します:
2020020102


y のスライダーバーを動かすと、同様に横方向に回転します:
2020020103


z のスライダーバーは画面と平行に回転します:
2020020104


x, y, z のスライダーバーを同時に動かすこともできます。縦、横、平行すべて指定したぶんだけ回転して画像が表示される、ということがわかると思います:
2020020105


これらはすべて CSS の transform 属性だけで実現しています。以下、その技術的な内容を紹介しますが、前提として x, y, z という軸の名前とその意味、そして立体的な位置関係を理解しておく必要があるので、そちらから説明します。以下の図のような関係になっていると理解してください:
20200201


図のような x 軸、y 軸、z 軸があって、自分は z 軸正方面から画面を見ています。実際に見えている画面は x 軸と y 軸からなる平面上に描かれています。

そしてスライダーバーはそれぞれ x 軸を中心に回転、y 軸を中心に回転、z 軸を中心に回転するスライダーバーになっていて、それぞれ指定した角度だけ回転した結果が画面に表示されます。そのため(結果的には)x で回転すると縦方向、y で回転すると横方向に回転したように見えます。また z で回転すると見えている平面上で時計回り/反時計回りに回転しているように見せることができています。

で、これをどう実現しているかという部分が CSS の transform 属性です。例えば id="a" の要素を x 軸中心に 30 度回転させて表示したい場合は、以下のようなスタイルを適用します:
#a{
  transform: rotateX( 30deg );
}

同様にして y 軸中心に 30 度回転させて表示したい場合は以下です:
#a{
  transform: rotateY( 30deg );
}

z 軸中心に 30 度回転させる場合は以下です:
#a{
  transform: rotateZ( 30deg );
}

「30 度」という部分は -180 から 180 まで変えることができます。それぞれの結果は上述のようになります。上述のサービスではこれらのスタイルを各スライダーバーの値を参照しながら動的に変更して表示しており、その結果がこのようになっています。


これはオマケの情報ですが、このサービスではデフォルトでペンギンの画像を表示して回転させていますが、imgurl という画像の URL をパラメータ指定することで表示画像を変更することができます。例えばこの画像を使いたい場合は、画像の URL(https://1.bp.blogspot.com/-A-AUINeSdLY/XexqsbbnoVI/AAAAAAABWiA/1zwr87fQJbsntV_Ez_ky6-RJPRfgeCZ5ACNcBGAsYHQ/s400/kinshi_mark_computer.png)をパラメータとして以下のように指定します:

https://dotnsf.github.io/transform-rotate/?imgurl=https://1.bp.blogspot.com/-A-AUINeSdLY/XexqsbbnoVI/AAAAAAABWiA/1zwr87fQJbsntV_Ez_ky6-RJPRfgeCZ5ACNcBGAsYHQ/s400/kinshi_mark_computer.png


こんな感じで表示できるはずです:
2020020106




便利な地図表示ライブラリ Leaflet(と OpenStreetMap)を使って表示する地図は原則的には上が北になります。これを変更して(つまり地図を回転させて、上が北側にならないようにして)表示することに挑戦してみました。

最初は「やり方をググれば見つかるだろう」と軽く考えていたのですが、これはどうやらかなり難しいらしく、Stackoverflow で同様の質問を見つけましたが、ここでの回答には「Leaflet ネイティブで用意された方法は存在しない」「簡単な方法はない」とされています。かなりハードル高そうです。。

ネイティブには用意されていない、、簡単ではないものを自力で解決する、、 言うは簡単ですが実際には細かな調整含めて結構面倒なものでした。ただ一応サンプルとして動く形にできましたので、公開&紹介します。

まず、ソースコードはこちらに用意しました。実装は index.html ファイルを参照してください(バックエンドなしにこのファイル1つだけで実装しています):
https://github.com/dotnsf/om


実際に動く様子を確認・体験できるように github ページも用意しました。スマホのブラウザでこちらにアクセスしてください:
https://dotnsf.github.io/om/


なお、このページでは DeviceOrientation イベントをハンドリングしているため、環境によっては事前準備が必要です。こちらのページでも紹介しているように Android や iOS 12.1 以下の場合は事前設定不要です(ページを表示すればそのまま動きます)。 iOS 12.2 以上 13.0 未満の場合は URL 先にアクセスする前に予め Safari の設定で「モーションと画面の向きのアクセス」を ON にしておいてください(この設定後に URL にアクセスすれば動きます):
2020011001



また iOS 13.0 以上の場合は事前の準備は不要ですが、ページロード直後の初期状態で表示される「センサーの有効化」というボタンをクリックしてから「許可」してください:
2020011002


いずれの環境においても上記の準備を済ませた上でアクセスすると、東京を中心とした関東の地図が表示されます。また画面の中心には赤字でこのスマホデバイスの横方向への傾き角度が表示されます:
2020011701


スマホを横方向に傾けると、その傾きに合わせて地図が画面内で回転して表示されます。下図ではスマホを向かって右側に(時計回り方向に)傾けています。スマホの傾きに関係なく、常に真上(下図では左上方向)が北になります:
2020011700


実際に動かしている様子を動画にしました:



以下、技術的な解説を加えていきます。

まず CSS で body など地図を表示する要素のサイズを width も height も 100% に指定して、全画面で地図が表示されるようにしています。

そして回転処理そのものは地図を表示する <div> 要素に CSS の transform: rotate() 属性を適用して実現しています。今回の例では <div id="demoMap"> 内に Leaflet 地図を表示していますが、デバイスの傾き(角度: deg)を検知して、
 $('#demoMap').css( 'transform', 'rotate(-'+deg+'deg)' );
という処理を実行することで <div> ごと回転をかけて表示しています(加えて deg の数値を <div id="me"> 内で表示していますが、こちらは特別な処理をしていないため、詳細は省略します)。

ただこれだけだとうまくいきません。Leaflet の仕様なのか、CSS transform の仕様なのか「もともと表示されている要素が回転して表示される」らしく、縦 100% 横 100% の画面を回転させると、角度によっては表示されない部分が出来てしまうのでした:
2020011801



↑黒がスマホ本体(黒枠の中だけが実際に表示される)、左側は本体画面に地図が表示されている。スマホを回転させて表示すると、元の地図の矩形部分は表示されるが、表示しきれない部分(黄色の部分)が生じる。実際には黄色部分はグレーで表示されてしまいます。


この状況を回避するための工夫を加えています。具体的には <div id="demoMap"> のサイズを width, height ともに 200% に指定して実際のスマホ画面の4倍の面積のエリアをロードするようにしています(更に left と top を -50% にして、そのロードしたエリアの中心部分だけを表示するよう調整しています。こうすることで表示された画面の中心を軸に画面が自然に回転するようになります)。細かいことですが、この仕様に合わせて <div id="me"> の位置もこの変更に合わせて調整したり、(表示位置がズレないよう)画面にスクロールバーが出ないよう CSS で抑制したりしています:
2020011802


↑地図部分を実画面サイズよりも大きくすることで、回転させても表示不可エリアが生じないように調整しています。


こうした工夫によってなんとか実現できました。ただ地図上に書かれた文字が斜めになってしまうのはさすがにどうしようもないです。その辺りは地図 API 側でサポートしてくれるのを待つしかないかなあ。。

なお index.html 内の JavaScript 変数 lat(緯度), lng(経度)が地図の中心点座標、zoom がズームレベルです。これらの値を変えると表示するエリアや拡大縮小レベルを変えることができますので、自分の環境で挑戦してみたい方は是非挑戦してみてください。


このページのトップヘ