【CSS Tips】coverの罠:ズームインアニメーション編

年末だからといって、特に毎日が変わるわけではないChappy Tabbyです。
敢えて言えば、開業届をオンラインで提出しようとしたら、提出先の国税庁が完全年末年始休暇に入っていてオンラインでも受け付けず、ちゃぶ台返しをしたくなったくらいです。

カーソルを乗せたら画像が拡大するアニメーションの実装。
昨今のサイトでよく見かけるので、自力で実装しようとしました。

(注意)この記事に記載されているコードをコーディングの参考にする際、画像のURL・クラス名等は適宜書き換えてください。

【悪い例】hover疑似要素にbackground-sizeを使う

(背景画像使用のHTMLマークアップ)

<div class="content">
    <div class="image-block"></div>
</div>

(imgタグ使用のHTMLマークアップ)

<div class="content">
    <img src="image.jpg">
</div>

(CSS)

.content {
    width: 24em;
    height: 16em;
    background: #ccc;
    overflow: hidden;
    //拡大した際に親要素からはみ出さないようにするための設定
}

(CSS: background-imageプロパティで背景画像を指定)

.content .image-block {
    width: 100%;
    height: 100%;
    transition: .5s all ease-in-out;
    background-image: url(image.jpg);
    background-size: cover;
    background-position: center;
}
.content:hover .image, .content:focus .image {
    background-size: 120%;
}

(CSS: imgタグ)

.content img {
    width: 100%;
    height: 100%;
    transition: .5s all ease-in-out;
    object-fit: cover;
}
.content:hover img, .content:focus img {
    width: 120%;
}

まず、画像のサイズをブロックに収めるためbackground-sizeobject-fitプロパティに「cover」を設定し、transitionプロパティとhover擬似クラス(:hover)background-sizeを大きくして(今回はbackground-size: 120%)画像が拡大してズームインするように見せようとしたのですが…。

画像2

結果は↑のようになります。
左側の背景画像を使った方は、スムーズさがまったくなくてアニメーションとして成立していません。

object-fit: coverの場合、hover時にwidthを増やすのではなく「object-fit: none」にするとカクカクの残念なものが出来上がります。

これじゃ実用には耐えられませんね🙀
お約束のGoogle先生に聞いてみましょう。

🐱.。oO{「background-size transition」に関係するページ教えてにゃ}

【解決案】hover疑似要素にtransform: scaleを使用

Google先生がリスト最上位に置いてくれたのが「Stack Overflow」のURL。

ここで書かれた方法の場合、coverは設定するけど、hover擬似クラス(:hover)transformプロパティscale関数を使用して画像を拡大します。

画像を含むブロック自体を対象にしたい場合、画像を入れた子要素ではなく、親要素に対してhover疑似クラスを指定します。
例えば、親要素がclass="content"で子要素がclass="image-block"だった場合は以下の通りとなります。

(HTML)

<div class="content">
    <div class="image-block"></div>
</div>

(CSS)

.content .image-block {
    width: 100%;
    height: 100%;
    //空のdivタグの場合、heightを指定しないと表示されません。
    background-image: url(image.jpg);
    background-size: cover;
    //imgタグの場合、background-sizeプロパティをobject-fitプロパティに変えます。
    transition: .5s all;
}
.content:hover .image-block, .content:focus .image {
    transform: scale(1.2);
}

大体想定通りのアニメーションになってきました。
とはいえ、imgタグ&object-fitの場合widthを増やしても同様の結果でしたが。

この方法を用いる場合注意点があります。
画像を設置しているタグ直前に文字のタグがあると、拡大した画像が文字に覆いかぶさってしまいます。

例えば、次のようなマークアップになっていて、子要素のh2にposition: absoluteがなくて、親要素のdiv(content)にposition: relativeを設定していない場合です。

<div class="content">
    <h2 class="image-title">Image Title</h2>
    <div class="image-block"></div>
</div>

後に書かれているclass「image-block」が上のレイヤー(Photoshop的な感覚だとそういう表現)に置かれるので、このブロックがtransformプロパティのscale関数で拡大されると、h2タグの文字の上に覆いかぶさることになります。

そういうレイアウトにならないように、positionプロパティを活用して好みの場所に配置しましょう。
※translate関数でも同じようなことが出来ます。

【おまけ】画像に文字やオーバーレイ効果を乗せる

次はかの有名な「CSS-Tricks」の方法を試してみます。

って、さっきのStack Overflowと同じ方法だった。
※ただしCSS-Tricksの記事は背景画像限定で扱っている。

ということで、CSS-Tricksに書かれていた、更に他の加工をしたいケースについて書きます。

画像2

↑のような感じで画像に色などを重ねるオーバーレイ効果や文字を追加したい場合は、positionプロパティとbefore疑似要素(afterでもOK)を使用します。

.image-block::before {
    content: '';
    display: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgba(49, 49, 49, .5);
}

この場合、before疑似要素(::before)position: absoluteを設定し、top・leftとも0にして完全に位置を揃えておきます。
そして、通常状態=hover擬似クラスではない方にdisplay: noneを設定して隠しておきます。

.content:hover .image::before, .content:focus .image::before {
    display: block;
}

hover擬似クラスにdisplay: blockを設定すると、カーソルが乗ったときだけオーバーレイが表示されます。
文字に対しても同様のことができます。

imgタグの画像に対して同じことを行う場合、imgタグに対する疑似要素では実装できません。
実際に試したところ、z-indexを設定してもオーバーレイ部分を画像の上に被せられませんでした。

imgタグの後にdivタグを置いて、そちらに同様の設定をしてあげるとオーバーレイを乗せることができます。
この場合、そのdivタグにbefore(またはafter)擬似要素を使う必要はありません。

(HTML)

<div class="content">
    <img src="image.jpg" alt="image">
    <div class="overlay"></div>
</div>

(CSS)

.overlay {
    content: '';
    display: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    background-color: rgba(49, 49, 49, .5);
}

先のコードに黒いオーバーレイを加えたもの。
imgタグの方は文字が拡大されません。

imgタグは閉じタグがないので、文字の入ったタグ(この例の場合h2タグ)をimgタグの子要素にできないためです。

一方、background-imageを用いた例は文字付き要素(h2タグ)が背景画像ブロック(background-imageプロパティを設定したdivタグ)の子要素になっているため、一緒に拡大します。

【まとめ】

background-imageやobject-fitに「cover」を設定してアニメーションをする場合、変化する対象となるプロパティは「%やpxなどの数値」を設定すること。
ただし、background-sizeは例外

理由は「MDN web docs」の「Animatable CSS properties」に書かれている以下の文言からある程度は察しが付きます。

Animation means that their values can be made to change gradually over a given amount of time.

今回の悪い例の場合、「object-fit: cover→none」「background-size: cover→120%」は「徐々に変化していく値」として扱えないからだと思われます。

background-sizeに「%の数値」を設定しているのにだめな理由として考えられるのは、背景画像を拡大してもブロックからはみ出た分は自動的に切り取られるからかもしれません。

CSS的にはブロックのサイズが変わらないのはアニメーションのうちに入らないのでしょうか?

😾.。oO{画像は見て分かるくらいに変化しているのに不思議である}

widthプロパティやscale関数を使ったアニメーションでoverflow: hiddenを設定しないとはみ出てしまうことから、そのように考えられます。

一方、widthやscaleを設定した場合、今回のケースではアニメーション対象の要素に対してwidth: 100%に設定されているし、scaleも初期設定は1.0なので、徐々に変化していく値として成立します。

それでは良いお年を🎍👋

頂いたサポートはチャオちゅーる購入…じゃなくて、営業活動など仕事で必要なものに使わせていただきます。 フリーランスの仕事が軌道に乗ってきたら、母親になにかプレゼントでもします。あるいは猫様をお迎えするか。