画像をCSSで斜めにマスクした時の知見
サイト全体が斜めになったサイトで、画像をクリッピングする必要がありました。しかもレスポンシブWebデザインです。
その時の知見と、利用しませんでしたが、ちょっとしたテクニックを思いついたので、メモとして残しておきます。
画像を斜めクリップにする方法
画像を斜めクリップするイメージは、こんな感じです。
要素を斜めにする方法は、IE9以上から利用できます。
IE8もIEの独自フィルターを利用して頑張ればできるようですが、底が見えない感じだったので、IE8は斜めにせずに対応させていただきました。
要素を斜めにするCSSは、transformプロパティを利用します。
transformプロパティには、いくつかの関数が用意されており、主に「skewY()」を利用し、場合によっては「rotate()」も利用しました。
CSSの記述例
.foo {
-webkit-transform: skewY(10deg);
-ms-transform: skewY(10deg);
transform: skewY(10deg);
}
skewY() と rotate() の違い
「skewY()」と「rotate()」は、両方とも角度(単位: deg)を指定して利用します。
「skewY()」は、傾斜に変形させ、 「rotate()」は、回転の変形を加えます。
今回は、単に斜めにさせるだけでなく、一定の方向の形状を保つ必要がありました。そのため、回転(rotate)にするよりも傾斜(skew)にする方が、扱いやすかったです。
またtransform-originプロパティを利用することで、デフォルトだと要素の中心だった起点を、任意の場所の起点に変更できます。要素を変形させる時の起点操作は、重要でした。
skewY()の画像マスク
skewY()を利用して、画像マスクしてみます。
CSS
/* ベンダープリフィックスは省略しています */
.skew {
transform: skewY(20deg);
transform-origin: top right;
}
画像の左がそのままの状態で、右が要素にCSS Transform: skewY()を適用した例です。 画像を斜めにできましたが、よく見ると画像が歪んでいます。
この斜めの形状を維持したまま、画像の歪みを解消します。
画像をマスクする要素を用意し、その要素に「overflow: hidden;」で画像の一部が隠れるようにします。そして、傾斜の高い部分を起点にして、画像にskewY()を逆にかけます。
あとで出てくる計算式が説明しやすいように、skewY(-10deg)をかけた要素で解説します。
HTML
<div class="skew">
<h1>transform: skewY(-10deg);</h1>
<div class="skew_mask">
<img src="sakura.jpg" alt="" class="skew_img">
</div>
</div>
CSS
/* ベンダープリフィックスは省略しています */
.skew {
transform: skewY(-10deg);
transform-origin: top right;
}
.skew_mask {
overflow: hidden;
}
.skew_img {
transform: skewY(10deg);
transform-origin: top right;
}
この過程を行うと画像の歪みが消え、画像の上部をマスクできます。
さらに画像の下部もマスクしましょう。「overflow: hidden;」を加えた要素に「height」の値を定義し、下部が全て見えないようにします。
適当に数値を入れてもクリッピングできますが、計算して調整しましょう。
クリップする高さを求める時は、三角関数を利用します。
下部の隠れている高さを図の三角形 「c」に置き換え、角度と横幅を元に計算します。 tan Θをrad(ラジアン)に変換し、「c = rad * b」で計算します。 そして画像の高さからcを引いた数値が、クリップする高さになります。
クリップする高さ = 画像高さ - (rad * 画像の横幅)
またSassのフレームワークである「Compass」のv 1.0以上には、tan関数が用意されているので、degの単位からクリップする高さを直接計算できます。
@import 'compass';
.skew_mask {
overflow: hidden;
// 画像サイズ 600x400 の場合
height: 400px - tan(10deg) * 600px;
}
出力結果
.skew_mask {
overflow: hidden;
height: 294.20381px;
}
IE9以上は、上記のキャプチャーのようになるはずです。
ただしIE8は斜めにならず、長方形の画像が中途半端な位置でクリッピングされてしまいます。
IE8へのフォールバックを行う場合は、Modernizerを使用し、「.no-csstransforms」をフックにして、高さを自動にしてやると楽です。
.skew_mask {
overflow: hidden;
height: 294.20381px;
}
.no-csstransforms .skew_mask {
height: auto;
}
skewX()の画像マスク
水平を保ったまま横を斜めにしたい場合は、skewX()を利用します。 斜めにする方法は、skewY()でやる方法と、ほぼ同じです。
CSS
/* ベンダープリフィックスは省略しています */
.skew {
transform: skewX(-10deg);
transform-origin: bottom left;
}
.skew_mask {
overflow: hidden;
}
.skew_img {
transform: skewX(10deg);
transform-origin: bottom left;
}
斜めにできましたが、右側に余白が空いています。 skewY()の時は、画像サイズから三角関数の「c」の長さ分、引きました。 skewX()は、画像サイズから三角関数の「c」の長さを足します。
画像の横幅 = 元画像の横幅 + (rad * 元画像の高さ)
skewY()と同様に「px」で求めてもいいですが、レスポンシブWebデザインでも利用できる値で考えます。
元画像の横幅は、カラムの幅にフィットすることを前提に考えると、width: 100%; 置き換えて、その値から高さをパーセントで求めます。
画像(600px X 400px)を、画像の横幅100%として考えて、高さを求める
100% : h = 600px: 400px
h = 400 * 100 / 600
h = 66.67%
画像の高さ(%)と角度から求めたradをかけて、画像の横幅(100%)を足します。
画像の横幅を計算
x = 100(%) + (0.176(rad) * 66.67(%))
x = 111.73%
求めた値を、min-widthプロパティに入れます。
なぜmin-widthプロパティかといういと、Fluid Imageでmax-width: 100%;が定義されていると、widthプロパティではうまく効かないからです。
/* ベンダープリフィックスは省略しています */
.skew {
transform: skewX(-10deg);
transform-origin: bottom left;
}
.skew_mask {
overflow: hidden;
}
.skew_img {
min-width: 117.6%;
transform: skewX(10deg);
transform-origin: bottom left;
}
画像の幅をパーセントで計算しているため、スクリーンサイズが異なっても、カラム幅にあわせて画像が伸縮します。
skewY()の画像マスク: Fluid Image(レスポンシブ)
skewX()は縦横比が保たれていれば、画像サイズが伸縮しても大丈夫ですが、skewY()の場合は、固定で画像の高さを求めています。そのため画像サイズが少しでも変わると、うまくクリップされません。
Android 4.0以上に対応しなければならなかったので、結局JSで対応しましたが、Android 4.4からでは「vw」単位を利用すれば、JSを利用せずに対応できます。
「vw」は「viewport width」の略で、基準のViewportの横幅を「100vw」として利用できます。
Viewportの横幅が1024pxの場合
- 100vw = 1024px
- 20vw = 204.8px
Viewportの横幅が320pxの場合
- 100vw = 320px
- 20vw = 64px
この「vw」を画像の縦横比に当てはめることで、横幅を元にしてCSSにクリップする画像高さを任せられます。
下記のようなレイアウトと、以下の条件をもとに高さを求めます。
レイアウト例
- 元画像サイズ 600x400
- 「親要素の横幅」と「viewportの幅」が一致している
- カラム(画像)の横幅 width: 33.33%
- 傾斜角度 10deg(rad = 0.176)
画像サイズの縦横比から「vw」にした時の画像高さを求めます。
カラムの親要素の横幅はviewportの幅と一致しているため、画像幅の「33.33%」は、そのまま「33.33vw」に置き換えられます。
33.33vw : h = 600px: 400px
h = 400 * 33.33 / 600
h = 22.22vw
そして三角関数からクリッピングする高さを「vw」で求め、画像の高さから引きます。
y = 22.22vw(画像高さ) - (0.176(rad) * 33.33vw(画像の横幅))
y = 16.35vw
求められた値は、画像をクリップする高さの近い値です。
heightプロパティの値にセットして、数値の微調整を行いましましょう。
「vw」は、Viewportの横幅によって変化するため「width = device-width」であれば、ブラウザのサイズによって画像の高さが自動的に伸縮します。
通常の要素に、斜めの画像が食い込む
通常の要素とskewY()を利用した要素が混在すると、要素が一部食い込みます。
この解消は、画像をクリップするよりもシンプルです。
傾斜角度から求めたrad(ラジアン)に要素の幅をかけて、marginプロパティにセットするだけです。
要素の幅は、特に定義しなければ、100%として計算できます。
例えば、傾斜角度が10degであれば、rad = 0.176 です。
0.176(rad) * 100(%) = 17.6%
.button {
margin-bottom: 17.6%;
}
marginの上下のパーセントは、親要素の横幅から算出されます。
そのためパーセントで求めた値は、三角形の比率を保つことにつながり、傾斜の高さが保持されます。
斜めになった画像をクリッピングするポイント
- 画像は、要素の高い部分を起点に、逆に傾斜をかける。
- 他にも色々やったけど、とにかく三角関数で、なんとかなる。
- CSSの単位は適切に選択する。
CSSで三角関数を使う日がやってくるとは、夢にも思いませんでした。
義務教育って大事ですね。