CSS3 transformのZ軸 時計回転のrotateと縦横回転のXやYを併用する際の注意点

CSS3の新しいプロパティtransformにrotateというのがありますが、このrotateでrotate(90deg)からrotateX(180deg)という順で処理するのとrotateX(180deg)からrotate(90deg)とするので結果が違う理由を検証しました。「rotate」は時計回転、「rotateX」は上下の回転、「rotateY」は左右の回転です。

tarnsform: rotate(90deg)の後にrotateX(180deg)を行うのと、rotateX(180deg)の後にrotate(90deg)を行うのでは、表示される内容が違ったので検証して見ました。

CSS3の新しいプロパティtransformにrotateというのがありますが、「rotate」は時計回転、「rotateX」は上下の回転、「rotateY」は左右の回転です。

元画像

ふなっしー

rotate(90deg);

ふなっしー

rotateX(180deg);

ふなっしー

rotateY(180deg);

ふなっしー

このrotateで時計方向に90度回転した状態で、さらにrotateXやrotateYを追加すると、上下や左右の起点はどちらになるのかが疑問でした。

rotateX(180deg)を例にすると、90度回転される前の元画像の上下を基準に回転するのなら次のようになります。

ふなっしー

そうではなく、rotate(90deg)が適用されて横向きになっている画像の上下を回転するのであれば、次のようになります。

ふなっしー

これは試せばいいだけの話なのでやってみました。

rotateを複数適用するCSSの書き方

rotateを複数適用させたい時は、transformに各rotateを半角スペースで区切って書けば出来ます。

ただ、2015年4月13日時点では、ベンダープレフィックスをつけないと反映されないブラウザもありますので、冗長ですが下のようになります。これはrotate(90deg)で画像を時計回転で90度回して、さらにrotateX(180deg)で180度反転させるという場合です。

.class_name {
	transform: rotate(90deg) rotateX(180deg);

	/* ベンダープレフィックス */
	-moz-transform: rotate(90deg) rotateX(180deg);
	-webkit-transform: rotate(90deg) rotateX(180deg);
	-o-transform: rotate(90deg) rotateX(180deg);
	-ms-transform: rotate(90deg) rotateX(180deg);
}

上のcssを実際に適用したら下の通りになりました。

ふなっしー

結論として、rotateを複数適用してもすべて元の画像を基準にされるという事です。考えてみれば当たり前の話で、もし画像のスタイルシートにheightやwidthが指定してあった場合、横になった時にそれが変わってしまうと困ります。

元の状態が基準であるならrotateとrotateXの順序を入れ替えた、次のどちらも同じ結果になるはずです。

  • transform: rotate(90deg) rotateX(180deg);
  • transform: rotateX(180deg) rotate(90deg);

ところが、このように入れ替えてみると違う表示結果となります。

rotateを複数適用する場合、順序が重要

先ほどの順序を次のように入れ替えてみました。

.class_name {
	transform: rotateX(180deg) rotate(90deg);

	/* ベンダープレフィックス */
	-moz-transform: rotateX(180deg) rotate(90deg);
	-webkit-transform: rotateX(180deg) rotate(90deg);
	-o-transform: rotateX(180deg) rotate(90deg);
	-ms-transform: rotateX(180deg) rotate(90deg);
}

その結果です。

ふなっしー

これは次のように推移しているということでしょうか?

  1. 上下を180度回転
  2. 時計方向に90度回転

そうであれば、時計方向は180度反転した後は逆さまになる、つまり反対側から見た状態という事のようです。

ふなっしー => ふなっしー

X軸 Y軸 Z軸

X軸は横線、Y軸は縦線です

X軸Y軸

rotateXは横線を基準に回転するので、rotateX(180deg)は上下の反転になります。また、rotateYは縦線を基準に回転するのでrotateY(180deg)は左右の反転になります。

rotateX(180deg);

ふなっしー

rotateY(180deg);

ふなっしー

XとYで2次元なのですが、rotateにはもうひとつ3次元の座標Z軸があります。

xyz軸

このZ軸はモニター画面の手前からモニター奥に伸びているイメージです。

rotateXやrotateYで180度回転させた画像は奥から見ているのと同じだという事です。

後ろから見ている状態と同じと考えれば時計回転が逆になるのも納得できます。

通常の状態の時計回りは右回り

時計回転はZ軸で変わる

逆の時計回りは左回り

時計回転はZ軸で変わる

rotateZについて

なのでrotateXやrotateYを処理してから時計回転のrotateを行うのと、時計回転のrotateをやってからrotateXやrotateYを行うのでは結果が違うという事だと思います。

下の画像の場合、rotate(90deg)は赤い矢印方向への回転。rotateX(180deg)は、黄色い線側と青い線側の反転です。

時計回転のrotate(90deg)後にrotateX(180deg)

  1. まずrotate(90deg)で赤い矢印方向へ90度回転
  2. その後rotateX(180deg)で黄色い線側と青い線側が反転

ふなっしー=>ふなっしー=>ふなっしー

rotateX(180deg)後に時計回転のrotate(90deg)

  1. まずrotateX(180deg)で黄色い線側と青い線側が反転
  2. その後rotate(90deg)で赤い矢印方向へ90度回転

ふなっしー=>ふなっしー=>ふなっしー

試しにrotateX(180deg) => rotate(90deg)の順のrotateの値を90degではなくマイナスの-90degにしてみます。

rotateX(180deg) => rotate(-90deg)の順
ふなっしー

rotate(90deg) => rotateX(180deg)の順で処理したのと同じ結果になりました。

まとめ

まとめると次のように言えると思います。

  • CSS3のtransformには複数の処理を書ける。
  • 複数の処理を書く場合は半角スペースで区切る。
  • 複数処理の実行は書いてある順番で行われる。
  • 処理の基準となるX、Y、Zの各軸は処理前の画像の状態と常に同じ
  • ただし、時計回転に関してはZ軸の向きによって見え方が変わるので注意!

ExifのOrientationの数値と画像のタテヨコ表示図解一覧

ExifのOrientationの仕様書に沿った内容で実際に画像を作って位置関係をまとめてみました。ExifのOrientation毎の画像の配置方法を調べていたんですが、水平とか左右反対とか書いてあっても今ひとつ分かりかねるところがありまして、例えば本来縦長の画像を横向きにした際の水平や左右は、元の状態の縦長に対してなのか、それとも横長にした後の画像を基準にしたものなのかなど判断しかねていました。

ExifのOrientation毎の画像の配置方法を調べていたんですが、水平とか左右反対とか書いてあっても今ひとつ分かりかねるところがありまして、例えば本来縦長の画像を横向きにした際の水平や左右は、元の状態の縦長に対してなのか、それとも横長にした後の画像を基準にしたものなのかなど判断しかねていました。

Orientationの反転画像などはスマホの自撮りのことじゃないかと考えて実際に撮影してみたんですが、2、4、5、7番のコードがある写真にはならないですね。

仕様書のPDFを見つけたので、それに沿った内容で実際に画像を作って位置関係をまとめてみました。

Exif2.3のOrientation規格

見つけたのは一般社団法人 カメラ映像機器工業会が発行するPDFでCIPA DC-008- 2012 デジタルスチルカメラ用 画像ファイルフォーマット規格 Exif 2.3というものです。このPDFファイルはhttp://www.cipa.jp/std/documents/j/DC-008-2012_J.pdfからダウンロードできます。

デジタルスチルカメラ用 画像ファイルフォーマット規格表紙

PDFの中に埋め込まれているページ下の番号で28ページ目に説明がありました。下はそこからの引用ですがレイアウトだけ少し変えています。

■ 画像方向 Orientation
    行と列の観点から見た、画像の方向。
     Tag = 274 (112.H)
     Type = SHORT
     Count = 1
     Default = 1
      1 = 0番目の行が目で見たときの画像の上(visual top)、
          0番目の列が左側(visual left-hand side)となる。
      2 = 0番目の行が目で見たときの画像の上、
          0番目の列が右側(visual right-hand side)となる。
      3 = 0番目の行が目で見たときの画像の下(visual bottom)、
          0番目の列が右側となる。
      4 = 0番目の行が目で見たときの画像の下、
          0番目の列が左側となる。
      5 = 0番目の行が目で見たときの画像の左側、
          0番目の列が上となる。
      6 = 0番目の行が目で見たときの画像の右側、
          0番目の列が上となる。
      7 = 0番目の行が目で見たときの画像の右側、
          0番目の列が下となる。
      8 = 0番目の行が目で見たときの画像の左側、
          0番目の列が下となる。
     その他 = 予約

デジカメで2、4、5、7番の番号が振られた写真は撮り方がわからないんですが、この内容に沿って画像を作ってみました。

以下は全部0番目の行をRowとして赤いラインで、0番目の列 Column を青いラインで表示します。

この画像を作って確かめるまで、なぜこの順番で番号がつけられたのかわからなかったのですが、やってみたら理解出来ました。

手元の画像にOrientation番号がある場合、それを修正するには下のサンプル画像から手元の画像と同じOrientation番号のものを見つけて、それと同じ状態になるように手元の画像を修正すればいいわけです。

Orientation : 1

1は普通の状態です。この場合は人間が見て正しい表示なので画像の修正は不要です。

0番目の行が目で見たときの画像の上(visual top)、0番目の列が左側(visual left-hand side)となる。

Exif Orientation=1

Orientation : 2

2は1の画像の左右だけを反転させたものです。

0番目の行が目で見たときの画像の上0番目の列が右側(visual right-hand side)となる。

Exif Orientation=2

Orientation : 3

3は2の画像から上下だけを反転させたものです。

0番目の行が目で見たときの画像の下(visual bottom)、0番目の列が右側となる。

Exif Orientation=3

Orientation : 4

4は3の画像から左右だけを反転させたものです。

0番目の行が目で見たときの画像の下0番目の列が左側となる。

Exif Orientation=4

このサンプルの場合は基準が縦長だったので、ここまでの4つは全部縦長です。

Orientation : 5

このサンプルの場合、5ではじめて横長になります。

5は画像をはじめて時計回りに90度回転させます。

0番目の行が目で見たときの画像の左側0番目の列が上となる。

Exif Orientation=5

Orientation : 6

6は5の画像から左右だけを反転させたものです。

0番目の行が目で見たときの画像の右側0番目の列が上となる。

Exif Orientation=6

Orientation : 7

7は6の画像から上下だけを反転させたものです。

0番目の行が目で見たときの画像の右側0番目の列が下となる。

Exif Orientation=7

Orientation : 8

8は7の画像から左右だけを反転させたものです。

0番目の行が目で見たときの画像の左側0番目の列が下となる。

Exif Orientation=8

基準の1番の画像が縦長の場合、最初の4番までは全部縦長で左右の反転か上下の反転だけで遷移していきます。唯一5の時だけ4の状態から時計回転で90度回転させ、以後は最後まで横長のまま左右か上下の反転だけの処理で遷移します。

ひと目にまとめた一覧表

Orientation : 1
Exif Orientation=1
Orientation : 2
Exif Orientation=2
Orientation : 3
Exif Orientation=3
Orientation : 4
Exif Orientation=4
Orientation : 5
Exif Orientation=5
Orientation : 6
Exif Orientation=6
Orientation : 7
Exif Orientation=7
Orientation : 8
Exif Orientation=8
  • 1は普通の状態
  • 2は1から見ると左右のみ反転した状態。時計回転はなし。
  • 3は2から見ると上下のみ反転した状態。時計回転はなし。
  • 4は3から見ると左右のみ反転した状態。時計回転はなし。
  • 5は4から見ると時計方向に90度回転した状態。反転はなし。
  • 6は5から見ると左右のみ反転した状態。時計回転はなし。
  • 7は6から見ると上下のみ反転した状態。時計回転はなし。
  • 8は7から見ると左右のみ反転した状態。時計回転はなし。

Orientation番号ごとの向きの修正方法一覧

例えばOrientation番号が4の場合は、1の状態からいったん2の状態にして、さらに 2 => 3 => 4 と、その番号になるまで直して行けば正しい表示になるわけです。ですが番号をいちいち辿っていかなくても、1と各画像を対比すれば済みます。下はその対比一覧です。

  • 1の場合はそのまま。
  • 2の場合は左右のみ反転。
  • 3の場合は時計まわりに180度回転。(上下反転と左右反転の両方やったのと同じ)
  • 4の場合は上下のみ反転。
  • 5の場合は時計回りに90度回転した後、その状態の時に右にある部分と左にある部分を反転。
  • 6の場合は時計回りに90度回転。
  • 7の場合は時計回りに90度回転した後、その状態の時に上にある部分と下にある部分を反転。
  • 8の場合は時計回りに270度回転。

関連ページ:CSS3 transformのZ軸 時計回転のrotateと縦横回転のXやYを併用する際の注意点

画像が縦長か横長か調べて自動でクラス名をつけるJavaScript

画像が横長なのか、縦長なのか、正方形なのかで分けて、それぞれクラス名を追加するJavaScriptを書いてみました。よく読んでもらっているページに「絶対はみ出さない画像!自動でサイズ調節するスタイルシート」というのがあるんですが、これに関連したことで「スマホで見ても、すべての画像が横幅いっぱいになって、はみ出さなくなったんですが、縦長の写真だけサイズを変えられないか」という質問を受けました。

うちでよく読んでもらっているページに「絶対はみ出さない画像!自動でサイズ調節するスタイルシート」というのがあるんですが、これに関連したことで質問を受けました。

「スマホで見ても、すべての画像が横幅いっぱいになって、はみ出さなくなったんですが、縦長の写真だけサイズを変えられないか」という内容です。

画像が横長なのか、縦長なのか、正方形なのかで分けて、それぞれクラス名をつければいいんですが、もうすでに大量のページがある場合は全ページを修正しなくてはいけないし、そうでないとしても毎回画像毎にクラス名を割り当てるのは大変です。

そこで、自動でクラス名を追加するJavaScriptを書いてみました。

画像のサイズを調べてクラス名を割り当てるJavaScript

今回、参考になったのは次のページです。これが非常に素晴らしい内容で、作業がとても簡単になりました。ありがとうございます。

[JavaScript] 画像のオリジナル サイズを取得する 最もシンプルな方法 – こじょらぼ

で、実際に作ったソースはこんなカンジです。HTMLのどこに追加しても動きますが、<head> 部分の終わりにある「</head> の直前」につけるのがいいと思います。

<script>
(function(){
	function image_class(){
		var img = new Image();
		var images = document.querySelectorAll('img');

		for(var i=0;i<images.length;i++){
			img.src = images[i].src;

			if(img.width < img.height){
				images[i].className += ' vertically_long';
				images[i].parentNode.className += ' vertically_long_outer';
			}else if(img.width > img.height){
				images[i].className += ' horizontally_long';
				images[i].parentNode.className += ' horizontally_long_outer';
			}else{
				images[i].className += ' square';
				images[i].parentNode.className += ' square_outer';
			}
		}
	}

	if(window.addEventListener){
		window.addEventListener('load', image_class, false);
	}else if(window.attachEvent){
		window.attachEvent('onload', image_class);
	}
})();
</script>

これでページ読み込み完了後
縦長の画像には vertically_long というクラス名が
横長の画像には horizontally_long というクラス名が
正方形には square というクラス名が追加されます。

さらに
vertically_long の親ノードには vertically_long_outer
horizontally_long の親ノードには horizontally_long_outer
square の親ノードには square_outer
というクラス名が追加されます。

このクラス名毎にスマホ専用スタイルシートを書けば、好きなように表示を変えられます。

ただ、お気づきかと思いますが同じ親ノードの中に、縦長と横長の両方の画像があった場合、両方のクラス名が親ノードに追加されるので、その部分は思ったように表示されないと思います。

サンプルページ

このスクリプトを適用していない場合と、適用した場合のサンプルページを用意しました。2つのページの違いはスクリプトをつけているか、いないかだけです。

スマホで見ても絶対にはみ出しませんが、スクリプトを適用したページだけ、縦長と正方形の表示が変わります。

スクリプトを適用していない場合
http://dwm.me/download/vertical_and_horizontal_ratio_js_sample_1.html
スクリプトを適用した場合
http://dwm.me/download/vertical_and_horizontal_ratio_js_sample_2.html

サンプルページのスタイルシートは次のようになっています。

<style type="text/css">

/* これで画像は絶対にはみ出さない */
img {
	max-width: 100%;
	height: auto;
}

/* 縦長の画像を真ん中に配置する */
.vertically_long_outer {
	text-align: center;
}

/* 縦長の画像のサイズを横幅120ピクセルに固定 */
/* 高さは自動で調整 */
.vertically_long {
	width: 120px;
	height: auto;
}

/* 正方形の画像を右寄せで配置 */
.square_outer {
	text-align: right;
}

/* 正方形画像の高さを200ピクセル、幅も200ピクセルに固定 */
.square {
	width: 200px;
	height: 200px;
}

</style>

真ん中合わせにしたいのに、左寄せになってしまう場合は下のように変更してください。

変更前

.vertically_long {
	width: 120px;
	height: auto;
}

変更後

.vertically_long {
	width: 120px;
	height: auto;
	/*2行追加*/
	margin-left: auto;
	margin-right: auto;
}

2015年1月9日追記:今年になってSeesaaブログでの表示で縦の長さが固定されてしまい、表示がおかしくなったというご指摘がありました。その場合は width と height に !important を追加してください。


height: auto !important;

今後更に変更される場合があるので、width と height 以外もすべて !important を追加してもいいかもしれません。

これを、ページの本文にだけ適用させたくて、サイドバーなどそれ以外の部分には影響を与えたくない場合があると思います。

例えばSeesaaブログなら各ページの本文は #content .text の中にあるので、それをスタイルシートに追加してください。(同じSeesaaブログでも構成の違うテンプレートがあるようです。詳しくはコメント欄を参照してください。)

具体的に言うとこんなカンジになります。

<style type="text/css">

#content .text img {
	max-width: 100%;
	height: auto;
}

#content .text .vertically_long_outer {
	text-align: center;
}

〜〜以下省略〜〜

PHPのmb_convert_encodingを使って文字コードを検出する方法

読み込んだ文字列の文字コードがわからない場合はどうすればいいか?次の方法を試してみたら、うまく行きました。PHPには文字コードを変換してくれるmb_convert_encodingという関数があります。元の文字コードがわからなくても自動判別をしてくれるようになっているんですが、それでうまく判別できない時に mb_convert_encoding 関数を利用して、文字コードを調べる方法です。

PHPには文字コードを変換してくれるmb_convert_encodingという関数があります。

元の文字コードがわからなくても自動判別をしてくれるようになっているんですが、それでうまく判別できない時に mb_convert_encoding 関数を利用して、文字コードを調べる方法です。

mb_convert_encoding 関数

string mb_convert_encoding( $str, $to_encoding, $from_encoding );

$str に変換したい文字列
$to_encoding に変換後の文字コード
$from_encoding に変換前の文字コード を指定します。

$from_encoding には配列またはカンマ区切りの文字列で文字コードを複数指定できます。複数指定した場合は順番に試していって該当する文字コードが適用されます。

例えば、Shift_JISのWebページを $buff に読み込んで($buffの中にShift_JISの文字列)

$string = mb_convert_encoding( $buff, ‘UTF-8’, ‘SJIS’ );

とすれば、$string にはUTF-8に変換されたWebページが入ります。
(PHPのマニュアルにはSJISと書いてありますが、試してみたらShift_JISでも大丈夫でした)

変換前の文字コードがわからない場合は、$from_encoding を省略すると、そこに mb_internal_encoding() の結果が割り当てられます。(内部文字エンコーディングが割り当てられます)

また言語設定が Japanese の場合は、$from_encoding を auto にすると “ASCII,JIS,UTF-8,EUC-JP,SJIS” が割り当てられます。(PHP: サポートされる文字エンコーディング – Manual

$from_encoding を省略したり、auto を指定したりしてうまくいけばいいんですが、うまくいかない場合は $from_encoding を正しく指定しなければいけません。

読み込んだ文字列の文字コードがわからない場合はどうすればいいか?

次の方法を試してみたら、うまく行きました。$str に変換したいけど文字コードがわからない文字列が入っています。

$to_encoding = 'UTF-8';
$from_encoding = null;

foreach(array('UTF-8','SJIS','EUC-JP','ASCII','JIS') as $charcode){
	if(mb_convert_encoding($str, $charcode, $charcode) == $str){
		$from_encoding = $charcode;
		break;
	}
}

if(!$from_encoding){
	echo 'ERROR: 文字コードが判別出来ませんでした。';
	return;
}

if($to_encoding == $from_encoding){
	echo $str;
	return;
}

echo mb_convert_encoding($str, $to_encoding, $from_encoding);

mb_convert_encoding($str, $charcode, $charcode) == $str

例えば $str が ‘UTF-8’ だった場合、mb_convert_encoding($str, ‘UTF-8’, ‘UTF-8’) の結果は、元の $str と同じになるはずです。

そうなった場合には、$from_encoding に’UTF-8’を代入して break します。

foreach を抜けても $from_encoding が null のままだったなら、array(‘UTF-8′,’SJIS’,’EUC-JP’,’ASCII’,’JIS’) の中には該当文字コードがなかった事になるのでエラーを表示して終了します。

もし、$from_encoding と $to_encoding が同じなら、変換の必要がないので、そのまま $str を表示します。

そうでない場合は $str を ‘UTF-8’ に変換して表示します。

文字列判別関数を作っておくと便利

たとえば、次のような文字列判別関数を作っておくと便利です。

function hoge($str){
	foreach(array('UTF-8','SJIS','EUC-JP','ASCII','JIS') as $charcode){
		if(mb_convert_encoding($str, $charcode, $charcode) == $str){
			return $charcode;
		}
	}

	return null;
}