JavaScriptでアイコンクリック時に画像を枠内で回転させる手法
最近、スマホでブログを見ていて感じたのですが、縦長スマホで横長の写真って見づらくないですか?
スマホの自動回転がオンになっているなら、サッと本体を回転させるだけで横長写真も見やすいのだと思いますが、ぼくは普段から回転をロックしています。自動回転オンにしていると、ごろごろしながらスマホ使うときに不便じゃないですか。…自堕落すぎますかね?
ともあれ、画面全体を回転させたいわけではなく、画像だけを回転させる機能があれば便利だよなーと思ったのでJavaScriptとCSSで作ってみました。
先に答えから言っちゃうとtransform:rotate(90deg);みたいなのを使うだけなんですが、これってデフォルトだと回転軸が画像の中心になっているので、案外使いづらいのですよ。
なので、そのへんの注意点も含めて解説していきたいと思います。
アイコンクリック時に画像を枠内で回転させる実例
何はともあれ、先にどんなモノか実行例を見て頂きましょう。写真左上の回転アイコンをクリックするたびに、枠内(左寄せ)で画像が右回転するはずです。
回転後の位置調整をしない例
画像の中心を基点として回転してしまっているのがわかるでしょうか?
CSSによる画像回転というとtransform:rotate(90deg);をして済ませる例が多く、静的コンテンツならそれでも良いのかなとも思いますが、動的にアイコンクリック時に回転させる場合などは基点を左上に設定(transform-origin:left top;)して位置調整(transform:translate)しないとページレイアウトが崩れてダサいのではないかなぁと思います。
HTML部分
<div class="rotatable">
<img src="[回転させる画像1]" />
</div>
<div class="rotatable">
<img src="[回転させる画像2]" />
</div>
<style>
.rotatable {
position:relative;
width:300px;
height:300px;
max-width:100%;
border:3px double gray;
}
.rotatable img,.rotatable2 img {
max-width:100%;
max-height:100%;
position:absolute;
margin:0;
padding:0;
}
.rotate_icon {
position:absolute;
top:8px;
left:8px;
width:32px;
height:32px;
background:url(rotate.png);
opacity:0.3;
cursor:pointer;
}
</style>
基本的にはクラス名を "rotatable" にしたDIVタグで画像ファイルを囲んでいるだけです。
JavaScriptで動的に左上のアイコンを生成するため、positoin:relative;は必須。この指定によって、子要素が相対座標指定で配置できるようになるからです。
また、画像の回転時にサイズまでは変更しないため、原則として最も長い辺の長さ(ここでは300pxとしている)を持った正方形の枠で囲んでいます。この範囲内で画像を回転させる、というわけです。
縦横比が1:1で正方形な画像しか使わないならこんなこと考えなくて良いので楽なんですけど、PC画面のスクショだったら16:9だろうし、写真だったら3:2や4:3だったりしますし、縦横の長さが異なるほうが多いですよね。
それから、回転アイコン(rotate.png)については単純な画像なのでInkScapeで自作しました。必要であればお好きにお使いください。一応、黒い矢印と白い矢印の二種類用意してあります。
(右クリックして[名前を付けてリンク先を保存])
JavaScript部分
<script>
window.addEventListener('load', function () {
document.querySelectorAll('.rotatable').forEach(elm => {
let icon = document.createElement('div');
icon.className = 'rotate_icon';
icon.onclick = function () {
let img = this.parentNode.querySelector('img');
if (!img.dataset.orientation) {
img.dataset.orientation = 0;
}
img.style.transformOrigin = 'top left';
switch (parseInt(img.dataset.orientation)) {
case 0:
img.style.transform = 'rotate(90deg) translateY(-100%)';
img.dataset.orientation++;
break;
case 1:
img.style.transform = 'rotate(180deg) translateX(-100%) translateY(-100%)';
img.dataset.orientation++;
break;
case 2:
img.style.transform = 'rotate(270deg) translateX(-100%)';
img.dataset.orientation++;
break;
case 3:
img.style.transform = '';
img.dataset.orientation = 0;
break;
}
}
elm.appendChild(icon);
});
});
</script>
いつもどおり、ドキュメントのロード完了時にアレコレ設定しています。
まずはクラス名 rotatable の要素をすべて検索し、その中にcreateElementでDIVタグを新規生成。
このDIVタグにはクラス名 rotate_icon を付けることによって、画像左上に表示される回転アイコンとなります。
CSSについてはあまり説明する必要ないと思いますが、rotate_iconは相対座標指定で、画像の左上周辺(X:8 Y:8)に配置されるようになっており、画像はrotate.pngを使い、透明度を30%くらいにしています。このへんはお好みで調整してください。特にスマホで表示する場合は32x32pxの大きさは小さすぎたかも知れません。
そして一番キモの部分ですが、この回転アイコンがクリックされたときのイベントもここで定義。
img.style.transformOrigin = 'top left';
transformOriginで画像回転時の基準点を左上に設定しています。画像の中心点で回転した後に位置調整するのは計算が面倒だからです。
そして、回転アイコンがクリックされるたびに、img.dataset.orientationを「0→1→2→3→0へ戻る」と変更しています。このdataset属性はユーザーが好きに使えるので単なる変数として使っている感じですね。
もちろんJavaScript側のローカル変数として持っても良いのですが、回転させたい画像をページ内に何枚置くかわかりませんよね。画像を置いた分だけ、このorientation(画像の向き)変数が必要になるため、それならdatasetに持たせよう、というわけです。
switch (parseInt(img.dataset.orientation)) {
あとはswitch構文で、「0→右に90度、1→右に180度、2→右に270度、3→元に戻す」という動作をします。rotateの部分ですね。
しかし、rotate(90deg);とした場合、画像の左上を基点として90度回転するため、画像は縦になっても枠の左側に出てしまいます。それを防ぐためにtranslateY(-100%)を追加し、
img.style.transform = 'rotate(90deg) translateY(-100%)';
という処理をしています。枠から左に飛び出すのだからtranslateXなのではないか?と思われるかもですが、回転させた後に移動するわけではなく、移動させた後に回転させているため、Y座標の移動が必要なのです。
同様に180度、270度のときもXとY座標を調整しています。
まとめ
最初に書いたとおり、縦長スマホで横長写真は小さすぎて見づらいなぁ、画像だけの回転なんて簡単にできそうなもんだけどなぁーとJavaScriptで作り始めたら、思ってたのと違う動作で案外苦労しました。(上の「回転後の位置調整をしない例」参照)
CSSのtransformってあんまり使ったことがなかったのもありますけど、あくまで画像は回転も移動もしていない前提で、最初の状態からどのようにtransformさせるか、というあたりを考えなくてはならないので頭がこんがらがりそうになります。
このブログでは紹介してなかったかもですが、以前カメラのExif情報を元に画像回転させつつcanvasへ描画するJavaScriptを書いたことがあるため、そのコードを持ってこようかとも思いましたが、それはそれでコード量が3倍くらい増えるんですよねー。
なので、ひとまずtransformで指定した枠内に収めつつ、画像回転する処理が出来て良かったです。
いつものことながら、当ブログで書いているコード類※はご自由にお使い頂いて構いません。