画像が徐々に切り替わるスライダーを実装する(サンプル有り)

ファッション系、スタイリッシュ系のウェブサイトで写真が映えるスライドショーを紹介します。

サンプル

デモはこちら。

Sass & JavaScript css & jquery

Sass&JavaScriptのコードで解説していきます。(脱jqueryチャレンジ)

HTML

<ul class="slider">
	<li class="slider__item now"><img src="slider1.jpg" alt=""></li>
	<li class="slider__item"><img src="slider2.jpg" alt=""></li>
	<li class="slider__item"><img src="slider3.jpg" alt=""></li>
</ul>

Sass

// supports
@mixin supports($property, $value) {
	$style : "#{$property} : #{$value}";
	$style : "(#{$style})";

	@supports (#{$style}) {
	#{$property} : #{$value};
		@content;
	}
}
// keyframes
@keyframes slide {
	0% {
		-webkit-mask-size: 0% 100%;
		mask-size: 0% 100%;
	}
	100% {
		-webkit-mask-size: 100% 100%;
		mask-size: 100% 100%;
	}
}
@keyframes fadeInOut {
	0%{ opacity: 0; }
	5%{ opacity: 1; }
	95%{ opacity: 1; }
	100%{ opacity: 0; }
}

.slider {
	position: relative;
	overflow: hidden;
	height: 500px;
	&__item {
	overflow: hidden;
		position: absolute;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		z-index: 1;
		img {
			width: 100%;
			height: 100%;
			object-fit: cover;
			font-family: 'object-fit: cover;';/*object-fit-images用スタイル*/
			@include supports(-webkit-mask-size, 100% 100%){
				-webkit-mask-image: url(mask.jpg);
				-webkit-mask-repeat: no-repeat;
				-webkit-mask-position: left center;
			}
			@include supports(mask-size, 100% 100%){
				mask-image: url(mask.jpg);
				mask-repeat: no-repeat;
				mask-position: left center;
			}
		}
		&.pre {
			z-index: 2;
		}
		&.now {
			z-index: 3;
			img {
				animation :fadeInOut 8s linear forwards;
				@include supports(-webkit-mask-size, 100% 100%){
					animation: slide 2s cubic-bezier(.4, 0, .2, 1) 0s forwards;
				}
				@include supports(mask-size, 100% 100%){
					animation: slide 2s cubic-bezier(.4, 0, .2, 1) 0s forwards;
				}
			}
		}
	}
}

JavaScript

<script>
const slider = document.querySelector(".slider");
if(slider !== null){
	topslider();
}
function topslider(){
	var items = document.querySelectorAll(".slider__item");
	var items = Array.prototype.slice.call(items,0); 
	// ES6(ECMAScript 2015)使用バージョン(IE11未対応)
	// const items = Array.from(document.querySelectorAll(".slider__item"));
	var timer = setInterval(function(){
		var item = slider.getElementsByClassName("now")[0];
		//nowクラスが付いているスライダーの次が存在するかを確認
	  	if(item.nextElementSibling){
	  		var next = item.nextElementSibling;
	  	}else{
	  		var next = slider.firstElementChild;
	  	}
	  	for (let i = 0; i < items.length; i++) {
		  	var child = items[i];
			if(child.classList.contains('pre')){
				child.classList.remove('pre');
			}
		}
		item.classList.remove("now");
		item.classList.add("pre");
		next.classList.add("now");

		// // ES6(ECMAScript 2015)使用バージョン(IE11未対応)
		// var index = items.findIndex(item =>
		// 	Array.from(item.classList).includes('now')
		// );
		// var preindex = items.findIndex(item =>
		// 	Array.from(item.classList).includes('pre')
		// );
		// if(index === items.length - 1){
		// 	var nindex = 0;
		// }else{
		// 	var nindex = index + 1;
		// }
		// if(preindex >= 0){
		// 	items[preindex].classList.remove('pre');
		// }
			
		// items[index].classList.remove('now');
		// items[index].classList.add('pre');
		// items[nindex].classList.add('now');
	},5000);
}

// object-fit-images
// https://github.com/fregante/object-fit-images
objectFitImages();
</script>

サンプルコードの説明

js側はシンプルな内容で、

  • 現在アクティブなスライドに付与するクラス(now)
  • 1つ前にアクティブだったスライドに付与するクラス(pre)

5秒後ごと順番に付け外ししているだけです。
nowとpreでz-indexで重なり順を調整しつつ、nowクラスが付いたらcssアニメーションを1度だけ実行するようにしています。

今回画像が徐々に切り替わる表現に使用しているのはmaskプロパティ
maskプロパティは指定した画像に対してマスクをかけることができます
たとえば、適当な長方形のmask.pngを用意し、mask-sizeを変更すると下記のように表示されます。

共通スタイル

-webkit-mask-image: url(mask.jpg);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: left center;


-webkit-mask-size: 10% 100%;


-webkit-mask-size: 50% 100%;


-webkit-mask-size: 90% 100%;
ちなみに、ここで使っているmask.jpgは50×50の適当な長方形の画像です。→
background-sizeと同じように使うことができるので、0% 100%や100% 100%で画像のサイズに引き伸ばして使っています。

今回のスライドショーはこのmask-sizeを使って下記アニメーションで動かしました。

@keyframes slide {
	0% {
		-webkit-mask-size: 0% 100%;
		mask-size: 0% 100%;
	}
	100% {
		-webkit-mask-size: 100% 100%;
		mask-size: 100% 100%;
	}
}
補足
  1. ベンダープレフィックス無しは現在Firefox系のブラウザしか対応しておらず、大半のブラウザをカバーするには-webkit-をつける必要があります。
  2. object-fitはimg要素をbackground-size:coverのように表現できるプロパティです。
    IE11に対応していないため、「font-family: ‘object-fit: cover;’;」と「objectFitImages();」はそのポリフィルを適用するためのもの。
    詳しくはこちらの記事で紹介しているのでご覧ください。

ブラウザ対応

2020/12時点で各ブラウザのmaskプロパティ対応状況は下記の通りです。

IE11が非対応、先ほど書いた通りほとんどのブラウザが-webkit-のプレフィックスプロパティを併記することで対応可能です。
object-fitのようにIE11用ポリフィルがないかと調べたんですが、見つからなかったので今回は別の方法で対応しました。
今回のサンプルでは、maskプロパティ非対応ブラウザの場合、opacityによるシンプルなフェードイン・フェードアウトのスライドショーで表示されるようになっています。
実装コードは下記の部分です。

&.now {
	z-index: 3;
	img {
		animation :fadeInOut 8s linear forwards;
		@include supports(-webkit-mask-size, 100% 100%){
			animation: slide 2s cubic-bezier(.4, 0, .2, 1) 0s forwards;
		}
		@include supports(mask-size, 100% 100%){
			animation: slide 2s cubic-bezier(.4, 0, .2, 1) 0s forwards;
		}
	}
}

maskプロパティ非対応ブラウザ用にfadeInOutのキーフレームをまず記述し、-webkit-mask-size、もしくはmask-sizeのプロパティに対応しているブラウザの場合slideのキーフレームを上書いています。

雑記

この表現を実装するのに最初はSVGのClip Pathを使って試していたんですが、マスク用の画像自体を変形させる使い方だと、maskプロパティの方がマスクの適用からアニメーションまで全てCSSで制御できて便利だと感じました。

transformのmatrix3dを使って同じように表現しているサイトを見たことがあるので、今度そちらも試してメリットデメリットを比較してみたいと思います。