KOJIKA17

画像を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()」は、回転の変形を加えます。

transform

今回は、単に斜めにさせるだけでなく、一定の方向の形状を保つ必要がありました。そのため、回転(rotate)にするよりも傾斜(skew)にする方が、扱いやすかったです。

またtransform-originプロパティを利用することで、デフォルトだと要素の中心だった起点を、任意の場所の起点に変更できます。要素を変形させる時の起点操作は、重要でした。

起点イメージ

skewY()の画像マスク

skewY()を利用して、画像マスクしてみます。

CSS

/* ベンダープリフィックスは省略しています */
.skew {
  transform: skewY(20deg);
  transform-origin: top right;
}

skewY()の例

デモ

画像の左がそのままの状態で、右が要素に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 * 画像の横幅)

なお、tan Θから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;
}

skewX()

デモ

斜めにできましたが、右側に余白が空いています。 skewY()の時は、画像サイズから三角関数の「c」の長さ分、引きました。 skewX()は、画像サイズから三角関数の「c」の長さを足します。

skewX()

画像の横幅 = 元画像の横幅 + (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;
}

skewX()

デモ

画像の幅をパーセントで計算しているため、スクリーンサイズが異なっても、カラム幅にあわせて画像が伸縮します。

skewX() Responsive

デモ

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にクリップする画像高さを任せられます。

下記のようなレイアウトと、以下の条件をもとに高さを求めます。

レイアウト例

skewX()

  • 元画像サイズ 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」であれば、ブラウザのサイズによって画像の高さが自動的に伸縮します。

単位: vwを利用した作成例

通常の要素に、斜めの画像が食い込む

通常の要素とskewY()を利用した要素が混在すると、要素が一部食い込みます。

skewX()

デモ

この解消は、画像をクリップするよりもシンプルです。
傾斜角度から求めたrad(ラジアン)に要素の幅をかけて、marginプロパティにセットするだけです。
要素の幅は、特に定義しなければ、100%として計算できます。

例えば、傾斜角度が10degであれば、rad = 0.176 です。

0.176(rad) * 100(%) = 17.6%
.button {
  margin-bottom: 17.6%;
}

skewX()

デモ

marginの上下のパーセントは、親要素の横幅から算出されます。
そのためパーセントで求めた値は、三角形の比率を保つことにつながり、傾斜の高さが保持されます。

斜めになった画像をクリッピングするポイント

  • 画像は、要素の高い部分を起点に、逆に傾斜をかける。
  • 他にも色々やったけど、とにかく三角関数で、なんとかなる。
  • CSSの単位は適切に選択する。

CSSで三角関数を使う日がやってくるとは、夢にも思いませんでした。
義務教育って大事ですね。

Image by PAKUTASO: 桜-ふとした想い