画像を徐々に表示する CSS アニメーション

Akira

福岡在住の Web デザイナー。Web サイト制作に役立つ情報を紹介しています。AMP が大好き。

FacebookTwitter で記事の更新をお知らせしています。

画像が際立つ CSS アニメーションの紹介です。アイキャッチ画像や本文中の画像、ランディングページの画像など、いろいろな画像に使えます。

左から・右から・真ん中から

サンプルは、便宜上アニメーションの再生回数を無限にしています(提示している CSS のアニメーション回数は 1 回です)。

共通の HTML 。

<div class="img-wrap">
  <img>
</div>

左から徐々に表示するアニメーション。

CSS 。

.img-wrap {
  overflow: hidden;
  position: relative;
}

.img-wrap:before {
  animation: img-wrap 2s cubic-bezier(.4, 0, .2, 1) forwards;
  background: #fff;
  bottom: 0;
  content: '';
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
}

@keyframes img-wrap {
  100% {
    transform: translateX(100%);
  }
}

上記 CSS の translateX(100%)translateX(-100%) と値をマイナスにすると、右から表示するアニメーションに。

CSS 。

.img-wrap {
  overflow: hidden;
  position: relative;
}

.img-wrap:before {
  animation: img-wrap 2s cubic-bezier(.4, 0, .2, 1) forwards;
  background: #fff;
  bottom: 0;
  content: '';
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
}

@keyframes img-wrap {
  100% {
    transform: translateX(-100%);
  }
}

今度は画像の中心から、円形に広がるアニメーション(IE と Edge ではアニメーションしません)。

CSS 。

.img-wrap {
  animation: img-wrap 2s cubic-bezier(.4, 0, .2, 1);
}

@keyframes img-wrap {
  0% {
    clip-path: circle(0 at 50% 50%);
    -webkit-clip-path: circle(0 at 50% 50%);
  }
  
  100% {
    clip-path: circle(100% at 50% 50%);
    -webkit-clip-path: circle(100% at 50% 50%);
  }
}

ただし、IE と Edge には対応できません。いずれでもアニメーションはせず、ただ普通に画像が表示されます。

2 つの領域で異なるアニメーション

画像を 2 つの領域に分け、それぞれ異なるアニメーションにしてみます。HTML は、これまでのものと変わりません。

<div class="img-wrap">
  <img>
</div>

画像の上部は左から、下部は右から表示するアニメーション。

CSS 。

.img-wrap {
  overflow: hidden;
  position: relative;
}

.img-wrap:before,
.img-wrap:after {
  animation: 2s cubic-bezier(.4, 0, .2, 1) forwards;
  background: #fff;
  content: '';
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  z-index: 1;
}

.img-wrap:before {
  animation-name: img-wrap-before;
  top: 0;
  bottom: 50%;
}

.img-wrap:after {
  animation-name: img-wrap-after;
  top: 50%;
  bottom: 0;
}

@keyframes img-wrap-before {
  100% {
    transform: translateX(100%);
  }
}

@keyframes img-wrap-after {
  100% {
    transform: translateX(-100%);
  }
}

今度は画像の左側は上から、右側は下から表示するアニメーション。

CSS 。

.img-wrap {
  overflow: hidden;
  position: relative;
}

.img-wrap:before,
.img-wrap:after {
  animation: 2s cubic-bezier(.4, 0, .2, 1) forwards;
  background: #fff;
  bottom: 0;
  content: '';
  pointer-events: none;
  position: absolute;
  top: 0;
  z-index: 1;
}

.img-wrap:before {
  animation-name: img-wrap-before;
  left: 0;
  right: 50%;
}

.img-wrap:after {
  animation-name: img-wrap-after;
  left: 50%;
  right: 0;
}

@keyframes img-wrap-before {
  100% {
    transform: translateY(100%);
  }
}

@keyframes img-wrap-after {
  100% {
    transform: translateY(-100%);
  }
}

3 つ以上の領域で異なるアニメーション

<div> を使えば画像を 3 つ以上の領域に分け、それぞれ異なるアニメーションを指定できます。

HTML 。

<div class="img-wrap">
  <div class="cover1"></div>
  <div class="cover2"></div>
  <div class="cover3"></div>
  <img>
</div>

CSS 。

.img-wrap {
  overflow: hidden;
  position: relative;
}

.cover1,
.cover2,
.cover3 {
  animation: cover 2s cubic-bezier(.4, 0, .2, 1) forwards;
  background: #fff;
  height: calc(100%/3);
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  z-index: 1;
}

.cover1 {
  top: 0;
}

.cover2 {
  animation-delay: .2s;
  top: calc(100%/3);
}

.cover3 {
  animation-delay: .4s;
  top: calc(100%/3*2);
}

@keyframes cover {
  100% {
    transform: translateX(100%);
  }
}

スクロールアニメーション

画像が画面内に入ると、アニメーションが始まります。画像が見えなくなるまでスクロールすれば、再度アニメーションをご確認いただけます。

HTML 。

<div class="img-wrap">
  <img>
</div>

JavaScript 。

(function() {
  const image = document.querySelectorAll('.img-wrap');
  
  const observer = new IntersectionObserver(function(entries) {
    entries.forEach(function(entry) {
      if (entry.intersectionRatio > 0) {
        entry.target.classList.add('img-animation');
      } else {
        entry.target.classList.remove('img-animation');
      }
    });
  });
  
  Array.prototype.forEach.call(image, function(img) {
    observer.observe(img);
  });
})();

CSS 。

.img-animation {
  animation: img-opacity 2s cubic-bezier(.4, 0, .2, 1);
  overflow: hidden;
  position: relative;
}

.img-animation:before {
  animation: img-animation 2s cubic-bezier(.4, 0, .2, 1) forwards;
  background: #fff;
  bottom: 0;
  content: '';
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
}

@keyframes img-opacity {
  0% {
    opacity: 0;
  }
}

@keyframes img-animation {
  100% {
    transform: translateX(100%);
  }
}

コメント

  1. はじめまして。
    探していたアニメーションが非常にわかりやすく、しかも簡単に実装出来て、大変感謝しております。

    PCブラウザでは上手くアニメーションするのですが、スマホ(見ているのはIphone)で上手く動きません。
    もしお時間ございましたら、お手数ですがお答えいただければ幸いです。

    また大変恐縮ですが、スクロールした時の反応を1回だけに設定する方法も併せて教えて頂ければ幸いです。

    何卒よろしくお願いいたします。

    実装参考サイト
    https://miya-creation.com/cocolo/

    • ご質問 1 : iPhone で上手く動かない

      サイト URL をご提示頂いたおかげで、原因がすぐに分かりました。大変助かりました。

      原因は、polyfill が読み込まれていないことです。

      ブラウザの負荷を軽減するために、スクロールアニメーションの JavaScript には Intersection Observer API を使っています。

      ただ、Intersection Observer API は、iOS の全てのブラウザと IE が未対応です。

      未対応のブラウザでも動かすには、polyfill が必要です。

      polyfill の使い方は、まず W3C が提供している intersection-observer.js を読み込ませます。

      読み込み方法は、外部ファイルでも構いませんし、インラインでお書きになっても構いません。

      サイトを拝見した限り、私であればコードを縮小し、</body> 閉じタグ直前にインラインで書きます。

      この intersection-observer.js を読み込んだ後に、スクロールアニメーションの JavaScript を読み込ませます。

      読み込み順を必ず intersection-observer.js → スクロールアニメーションの JavaScript にされることがポイントです。

      これで iOS や IE でもアニメーションが動くと思います。

      ご質問 2 : スクロールした時の反応を1回だけに設定

      アニメーションを 1 回だけにするには、スクロールアニメーションの JavaScript を下記のコードにご変更ください。

      (function() {
        const image = document.querySelectorAll('.img-wrap');
        
        const observer = new IntersectionObserver(function(entries) {
          entries.forEach(function(entry) {
            if (entry.intersectionRatio > 0) {
              entry.target.classList.add('img-animation');
              observer.unobserve(entry.target);
            }
          });
        });
        
        Array.prototype.forEach.call(image, function(img) {
          observer.observe(img);
        });
      })();
  2. はじめまして。

    別の画像の上でこちらのアニメーションを使うことは可能でしょうか。

    • どちらかのアニメーションでしょうか?

      おまけ:コメントへの返信用

      ※ご希望とは違うようなので、サンプルを削除

      下のことかな?と思ったのですが。

    • 返信ありがとうございます。
      3つの領域のアニメーションを背景画像の左上部分にだけ使用したいと考えています。

    • このようなものでしょうか?

      ※ご希望とは違ったため、サンプルを削除。

      申し訳ありません。ご希望のアニメーションのイメージを掴めません。

    • 説明不足で申し訳ございません。

      例えば、こちらの女性の画像を静止画として背景に置き、
      左上だけに別の画像をアニメーションで登場させるというイメージです。

    • なるほど。このようなものでしょうか?

      もし、これでよろしければ、HTML と CSS はこのようなものです。

      HTML 。class="ani-img" のある <img> が、左から出てくる画像です。その上の <img> は、背景の画像(静止画)です。

      <div class="img-wrap">
        <img>
        <img class="ani-img">
      </div>

      CSS 。アニメーションは 1 回にしています。左から出てくる画像の大きさは、.ani-imgwidth で調整ください。

      .img-wrap {
        overflow: hidden;
        position: relative;
      }
      
      .ani-img {
        animation: ani-img 2s cubic-bezier(.4, 0, .2, 1) forwards;
        height: auto;
        left: 0;
        position: absolute;
        top: 0;
        width: 33%;
      }
      
      @keyframes ani-img {
        0% {
          transform: translateX(-100%);
        }
      }
    • 仰る通りでございます!
      ありがとうございます。
      試してみます!

  3. はじめまして。
    このようなおしゃれなアニメーションを探していたので非常に助かりました!
    ただ、何度HTML、CSSを記載しても反映されず、、、
    HPもまだ公開していないのでサイトもお見せできず。。。
    下記のようにコードを入れたのですが、一切反応がありません。。。
    情報がかなり少ないですが、原因わかりますでしょうか・・・?

    <img src="common/images/aaaa.png” alt=”” >

    CSSはまるまるコピペです。
    よろしくお願いいたします。

    • 申し訳ありません!自己解決いたしました!
      HP自体にローダーを導入していた為表示されているころにはアニメーションが終わっている、という結論でした。
      こちらってローダーが終了した段階でアニメーションスタート。。。などはできないでしょうか?

    • JavaScript をお書きになるといいように思えます。

      私がぱっと思いついたのは、2 つの方法です。

      • ページが読み込まれたタイミングで、<body> などに class を追加。その class を使い、CSS を適用する。参考:Window: load event
      • ページが読み込まれたタイミングで、animation を指定する(スタイルシートでは animation は指定しない)。参考:Style animation Property

      ただ、後者は疑似要素に使えないため、簡単なのは前者だと思います。

      また、ローダーの実装方法によっては、「ページが読み込まれたタイミング」ではなく、「ローダーが終わるタイミング」の方がいいかもしれません。

    • ご丁寧にありがとうございました!
      参考URLをもとに前者の方法でやりたいと思います!

  4. たびたびご質問してしまい申し訳ありません。
    前回の件に関しては前者の方法でできるようになりました!本当にありがとうございます。

    もう一点なのですが、今回のアニメーションだと開始した段階で画像が一旦表示されてからまた消えてアニメーション開始、というフローになってしまい、該当画像を最初は非表示にしたいのですが、どのようにすればよろしいでしょうか?
    opacityやanimation-fill-mode: forwards等試してみましたがうまくいかず。。。
    コードはサイトのままを使用しています。
    たびたび申し訳ありませんが、よろしくお願いいたします。

    • 実際にサイトを拝見しないと具体的に分かりませんが、全ての CSS で JavaScript で追加する class を使われていないでしょうか。

      1 番最初のサンプルを例にすると、JavaScript で追加する class を使うのは疑似要素への animation: img-wrap 2s cubic-bezier(.4, 0, .2, 1) forwards; だけでいいように思えます。

      私であれば、まずはこのようなものを試します。

      .img-wrap {
        overflow: hidden;
        position: relative;
      }
      
      .img-wrap:before {
        background: #fff;
        bottom: 0;
        content: '';
        left: 0;
        pointer-events: none;
        position: absolute;
        right: 0;
        top: 0;
        z-index: 1;
      }
      
      /* JavaScript で追加する class を使うのはこれだけ */
      「JavaScriptで追加するclass」 .img-wrap:before {
        animation: img-wrap 2s cubic-bezier(.4, 0, .2, 1) forwards;
      }
      
      @keyframes img-wrap {
        100% {
          transform: translateX(100%);
        }
      }

      ただ、スタイルシートの非同期での読み込みなどが原因の場合は、他の対策が必要になってくると思います。

    • >実際にサイトを拝見しないと具体的に分かりませんが、全ての CSS で JavaScript で追加する class を使われていないでしょうか。

      下記のJavascriptを実装しているのでそのせいでしょうか?
      document.addEventListener(‘DOMContentLoaded’, async function(){
      await new Promise( resolve => setTimeout( resolve, 3000 ) ); // 3秒待ち
      document.body.classList.add(‘.img-wrap’);
      });

      上記のJavascriptを使用し、前回のネックであったロードが完了してからアニメーションの実装というのを実現しました。
      ただ、今回の問題である、
      「画像が先に出てきてしまう。」
      「TOPページ以外の全ページのボディに記載されてしまい、全ページの画像が(ヘッダーフッター含む)読み込みが完了すると左から表示されてしまう」
      という現象におちいってしまいました。
      上記のものもCSSのbeforeを分け、下記の部分を修正し記載しましたが上手くいきませんでした。
      document.body.classList.add(‘.img-wrap:before’);
      HPの限定公開は可能なのですが、こちらだと不特定多数にログが残ってしまうので。。。大変申し訳ありません。。。
      解決方法はありませんでしょうか・・・?

    • このようにされると、どうなるでしょうか。例にするのは、最初のサンプルです。

      ① HTML

      HTML は、サンプルと同じものを使います。

      <div class="img-wrap">
        <img>
      </div>

      ② JavaScript

      JavaScript のこの部分を…

      document.body.classList.add('.img-wrap');

      このように変更し、img-ani を class に追加します。

      document.body.classList.add('img-ani');

      ③ CSS

      img-ani を使い animation プロパティを指定します。

      .img-wrap {
        overflow: hidden;
        position: relative;
      }
      
      .img-wrap:before {
        background: #fff;
        bottom: 0;
        content: '';
        left: 0;
        pointer-events: none;
        position: absolute;
        right: 0;
        top: 0;
        z-index: 1;
      }
      
      .img-ani .img-wrap:before {
        animation: img-wrap 2s cubic-bezier(.4, 0, .2, 1) forwards;
      }
      
      @keyframes img-wrap {
        100% {
          transform: translateX(100%);
        }
      }
    • 返信ありがとうございます。
      このコードで二つの事象を一気に解決できました!
      .img-ani .img-wrap:beforeのように二つ定義できるのは知りませんでした。。。
      初心者なのに丁寧に回答いただきありがとうございました!

    • 上手くいったようで良かったです。

  5. はじめまして!大変参考になりました!スクロールアニメーションについてご質問です。
    参考サイトのようにうまく実装はできたのですが、class(今回の場合、.img-wrap)を保有する同じページにある全ての要素に対しアニメーションが発生してしまいます。
    おそらく、thisなどを使い、そのクラスのthis(その要素)に対しという支持が必要かと思いますが、js部分でどのようにすれば良いか把握できずにいます。
    同じページに同じclassを保有していても、各要素に反映されるにはどのようにすればよろしいでしょうか?

    • 同じ class にしないのが 1 番簡単だと思います。

      スクロールアニメーションを適用する画像に、固有の class を付けるのではダメなのでしょうか。

    • 例えばホームページなどでは、classを同じにしないことが最も簡単かとおもいます。
      今回Railsで開発したアプリに実装しているのですが、eachの繰り返し文で利用しています。
      なのであるPostが投稿されると、そのpostに全てclassを保有するようなこととなります。
      そのため、classを同じにしないというのは現実的でないがゆえ、js部分でどうにかできないかと思いました

    • 私は Rails を使ったことがありません。全くの的外れかもしれませんが、each_with_indexeach.with_index が使えるのなら固有の class を作れるのかなと思いました。

      each_with_indexの使い方

      他に思い付いたのは、CSS の :nth-child() です。

      /* 3 番目の .img-wrap のみアニメーションを適用する */
      .img-wrap:nth-child(3).img-animation {
        ~ 省略 ~
      }

      これらが無理な上に、ターゲット要素を特定できない場合は、申し訳ありませんがやり方を知りません。できるかどうかすら分かりません。

  6. はじめまして。「3 つ以上の領域で異なるアニメーション」をスクロールアニメーションで再現したいのですが、教えていただけないでしょうか?cover1~cover3をnth-childで統一し、JavaScriptでcoverを制御してみたのですが、再度表示領域に戻った際、どうしても一度1/2程度が表示されてしまいます。因みに「Intersection observerによるスクロールアニメーション」(記事)通りにすると正常に動作してくれます。他に良い記述はないかとお力をお借りしたくご質問をさせていただきました。宜しくお願いいたします。

    • 具体的な HTML が分からないため、HTML はサンプルとほぼ同じものを用います。

      <div class="img-wrap">
        <div class="cover cover1"></div>
        <div class="cover cover2"></div>
        <div class="cover cover3"></div>
        <img>
      </div>

      JavaScript は、スクロールアニメーションに記載のものと全く同じとします。

      そして、CSS をこのように書くと上手くいくように思えます。

      .img-wrap {
        overflow: hidden;
        position: relative;
      }
      
      .cover {
        background: #fff;
        height: calc(100%/3);
        left: 0;
        pointer-events: none;
        position: absolute;
        right: 0;
        z-index: 1;
      }
      
      .img-animation .cover {
        animation: cover 2s cubic-bezier(.4, 0, .2, 1) forwards;
      }
      
      .cover1 {
        top: 0;
      }
      
      .cover2 {
        top: calc(100%/3);
      }
      
      .img-animation .cover2 {
        animation-delay: .2s;
      }
      
      .cover3 {
        top: calc(100%/3*2);
      }
      
      .img-animation .cover3 {
        animation-delay: .4s;
      }
      
      @keyframes cover {
        100% {
          transform: translateX(100%);
        }
      }

      HTML の class は、任意のものにご変更ください。

    • お教えいただいた通りに記述することで思い通りに動いてくれました。
      HTMLは

      のように記述し、CSSはサンプルを参考に
      .cover.img-animation { animation: cover 2s cubic-bezier(.4, 0, .2, 1) forwards; background: #fff; height: calc(100%/3); left: 0; pointer-events: none; position: absolute; right: 0; z-index: 1 }
      .cover.img-animation { top: 0; }
      .cover:nth-child(2).img-animation { animation-delay: .2s; top: calc(100%/3) }
      .cover:nth-child(3).img-animation { animation-delay: .4s; top: calc(100%/3*2) }
      としておりました。大変助かりました、有難うございました。

    • 連投失礼します。コメント内でHTMLタグを使用すると消えてしまいました。テスト段階だった為、下記の通りHTMLもサンプルをほぼそのまま使用させていただいておりました。
      div class=”img-wrap”
      div class=”cover” /div
      div class=”cover” /div
      div class=”cover” /div
      /div

    • 上手くいったようで何よりです。

  7. 可能であれば教えていただきたいことがありご連絡しました。
    https://firstlayout.net/css-animation-of-images/#toc4
    こちらを参考にやってみました。下記URLがダミーページです。
    http://aytestpage.2-d.jp/test.html
    これをスマホで読み込み、下へスクロールしようとすると横へスクロールできる状態(横に動いてほしくないのに動く状態)になります。:beforeで読み込む#fffのパーツが画面右側へ移動しますが、そのパーツの幅分だけ横へ動くようです。
    これを解消するための方法はないでしょうか?
    コンテンツを下へスクロールし再度、上部にスクロールした際に再びアニメーションが稼働するのは残したいです。

    • CSS は現在このような指定になっていますが…

      .img-animation {
          animation: img-opacity 2s cubic-bezier(.4, 0, .2, 1);
          /*overflow: hidden;*/
          position: relative;
      }

      overflow: hidden; のコメントアウトが原因だと思います。コメントアウトの解除でどうなるでしょうか。

    • お忙しいところ教えていただきありがとうございます。
      サンプルページでは入れていなかった「画像の上に文字を乗せる」のための設定をそのまま残していました。大変失礼いたしました。原因が分かったので、こちらでその他の要素がうまく表示されるよう調整してみます。
      どうもありがとうございました。

送信に失敗しました

ボットと判定された可能性があります。

大変お手数をおかけしますが、以下の内容をご確認の上、再度のコメントの送信をお願い申し上げます。