画像を徐々に表示する CSS アニメーション
左から・右から・真ん中から
画像が際立つ 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(0.4, 0, 0.2, 1) forwards; background: #fff; content: ''; inset: 0; pointer-events: none; position: absolute; 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(0.4, 0, 0.2, 1) forwards; background: #fff; content: ''; inset: 0; pointer-events: none; position: absolute; z-index: 1; } @keyframes img-wrap { 100% { transform: translateX(-100%); } }
今度は画像の中心から、円形に広がるアニメーション。

CSS 。
.img-wrap { animation: img-wrap 2s cubic-bezier(0.4, 0, 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%); } }
ただし、clip-path
は GPU アクセラレーションで処理されないため、ブラウザに負荷をかけます。また、なめらかなアニメーションにならないデメリットがあります。
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(0.4, 0, 0.2, 1) forwards; background: #fff; content: ''; pointer-events: none; position: absolute; z-index: 1; } .img-wrap::before { animation-name: img-wrap-before; inset: 0 0 50%; } .img-wrap::after { animation-name: img-wrap-after; inset: 50% 0 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(0.4, 0, 0.2, 1) forwards; background: #fff; content: ''; pointer-events: none; position: absolute; z-index: 1; } .img-wrap::before { animation-name: img-wrap-before; inset: 0 50% 0 0; } .img-wrap::after { animation-name: img-wrap-after; inset: 0 0 0 50%; } @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(0.4, 0, 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: 0.2s; top: calc(100% / 3); } .cover3 { animation-delay: 0.4s; top: calc(100% / 3 * 2); } @keyframes cover { 100% { transform: translateX(100%); } }
2 枚の画像を切り替えながらアニメーション
スライドショーのように 2 枚の画像を切り替えます。最初に表示する画像にも切り替え後の画像にもアニメーションを加えます。アニメーション開始ボタンを押してサンプルのアニメーションをご確認いただけます。サンプルはアニメーションの終了後に画像が 1 枚目に戻りますが、提示している CSS は戻らず 2 枚目の画像を維持します。


HTML 。
<div class="img-wrap"> <div class="img-container"> <img /> </div> <div class="img-container"> <img /> </div> </div>
CSS 。
.img-wrap { --animation-duration: 6s; display: grid; overflow: hidden; place-content: center; position: relative; } .img-container { aspect-ratio: 16 / 12; grid-area: 1 / -1; } .img-container:first-child { animation: img-container-first var(--animation-duration) linear forwards; } .img-container:last-child { animation: img-container-last var(--animation-duration) linear calc(var(--animation-duration) / 2) forwards; z-index: -1; } .img-container::before, .img-container::after { animation: 2s cubic-bezier(0.4, 0, 0.2, 1) forwards; background: #fff; content: ''; left: 0; pointer-events: none; position: absolute; right: 0; z-index: 1; } .img-container::before { animation-name: img-container-before; bottom: 50%; top: 0; } .img-container::after { animation-name: img-container-after; bottom: 0; top: 50%; } .img-container:last-child::before, .img-container:last-child::after { animation-delay: calc(var(--animation-duration) / 2); } .img-wrap img { height: 100%; object-fit: cover; width: 100%; } @keyframes img-container-first { 80% { opacity: 1; } 95% { opacity: 0; } 100% { opacity: 0; } } @keyframes img-container-last { 0% { opacity: 0; z-index: 1; } 10% { opacity: 1; z-index: 1; } 100% { opacity: 1; z-index: 1; } } @keyframes img-container-before { 100% { transform: translateX(100%); } } @keyframes img-container-after { 100% { transform: translateX(-100%); } }
スクロールアニメーション
画像が画面内に入ると、アニメーションが始まります。画像が見えなくなるまでスクロールすれば、再度アニメーションをご確認いただけます。

HTML 。
<div class="img-wrap"> <img /> </div>
JavaScript 。
const images = document.querySelectorAll('.img-wrap'); const animationClassName = 'img-animation'; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add(animationClassName); } else { entry.target.classList.remove(animationClassName); } }); }); images.forEach((image) => { observer.observe(image); });
アニメーションを最初の 1 回だけ動かす場合は、以下の JavaScript を使います。
const images = document.querySelectorAll('.img-wrap'); const animationClassName = 'img-animation'; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add(animationClassName); observer.unobserve(entry.target); } }); }); images.forEach((image) => { observer.observe(image); });
参考:Intersection observer によるスクロールアニメーション
CSS 。
.img-wrap { opacity: 0; } .img-animation { animation: img-opacity 2s cubic-bezier(0.4, 0, 0.2, 1) forwards; overflow: hidden; position: relative; } .img-animation::before { animation: img-animation 2s cubic-bezier(0.4, 0, 0.2, 1) forwards; background: #fff; content: ''; inset: 0; pointer-events: none; position: absolute; z-index: 1; } @keyframes img-opacity { 100% { opacity: 1; } } @keyframes img-animation { 100% { transform: translateX(100%); } }