follow us in feedly
jQueryCSS

jQueryでスクロールと連動した画像の切り替え(斜めトリミング)を実装

スクロールと連動させて画像が切り替わるエフェクトを実装してみます。切り替わる際に使用するマスクを平行四辺形にすることで斜めから徐々に画像が切り替わっていくようにします。レスポンシブ対応にするためにwidthやheightなどもjQueryで動的に設定していきます。

2019年8月14日

完成イメージ

スクロールしていくと画像が現れ、画像が画面の上下中央を過ぎると、その後のスクロール量に連動して画像が切り替わっていきます。画像の高さの1/2量スクロールすると画像が完全に切り替わります。スクロールを戻せばまた元の画像に切り替わっていきます。レスポンシブ対応で作りますので各種スマホでもデスクトップでも同様に機能します。
DEMO画面はこちら

スクロールと連動した画像の切り替え(斜めトリミング)

マスクの動き

マスクの始動位置は画像の右側です。これがスクロール量に連動して左に移動していくことで画像が徐々に見えていきます。完全に重なった状態で画像全体が表示されます。

スクロールと連動した画像の切り替え(斜めトリミング)

html、CSSの記述

htmlの記述

htmlの記述は至ってシンプルでimg01.jpgが初期状態で見えている画像でimg02.jpgがスクロールに連動して現れる画像です。

<div id="contents">
  <div id="hole">
    <img src="./images/img01.jpg" class="img01">
    <div class="mask">
      <img src="./images/img02.jpg" class="img02">
    </div>
  </div>
</div>

CSSの記述

一番外側のdiv要素(#contents)はDEMO画面では下記のように設定してあります。スマホではwidth: 100%;、デスクトップ・タブレットではwidth: 600px;としています。

@media screen and (min-width: 320px) and (max-width: 767px) {
#contents { width: 100%; margin: 1000px 0; overflow: hidden;}
}
@media screen and (min-width: 768px) {
#contents { width: 600px; margin: 1000px auto; overflow: hidden;}
}

スマホ版にしろデスクトップ版にしろimg01.jpgの大きさはwidth: 100%;としていますので実際の大きさがわかりません(スマホでは画面横幅いっぱいに画像が表示されます)。したがって、マスク(div.mask)の大きさとimg02.jpgの大きさはCSSでは指定できないのでjQueryで動的に指定していきます。

#hole { width: 100%; position: relative; }
.img01 { width: 100%;}
.mask { position: absolute; top: 0; left: 0; overflow: hidden; transition: all 0.3s ease-out;}
.img02 { position: absolute; top: 0; left: 0; transition: all 0.3s ease-out;}

マスクの大きさ

jQueryの記述に入る前に基本事項を確認していきます。まずマスクの大きさですが画像がすっぽり収まる大きさが必要です。計算し易いように平行四辺形は45度傾けますので以下のサイズになるはずです。

マスクのwidth = 画像のwidth+height
マスクのheight = 画像のheight
スクロールと連動した画像の切り替え(斜めトリミング)

マスクの位置

次にマスクのtop: 0; left: 0;の位置を確認すると以下の位置になります。

スクロールと連動した画像の切り替え(斜めトリミング)

このことからマスクに画像がすっぽり収まっている状態というのはleftの値が画像の高さの半分、マイナスであることがわかります。

left: −(画像のheight * 0.5)px; //画像がすっぽり収まっている状態

最後にマスクの始動位置も確認しておきます。この位置はマスクが画像の幅+画像の縦の1/2右に寄った状態であることがわかります。

left: (画像のwidth + 画像のheight * 0.5)px;
スクロールと連動した画像の切り替え(斜めトリミング)

jQueryの記述

変数の設定

まずは変数をいろいろ設定しておきます。

const $window = $(window);
const $mask = $('.mask');
const $img02 = $('.img02');
const holeOffsetTop = $('#hole').offset().top;
const windowHeight = $window.height();
const img01Width = $('.img01').width();
const img01Height = $('.img01').height();

マスクの始動位置の設定

マスクの始動位置を設定します。考え方は前述した通りですが、img02.jpgはマスクを親とする絶対位置指定ですのでマスクの移動距離と同じ分を戻してあげることでimg01.jpgと重なります。またマスクをtransform: skewX(-45deg);で傾けた場合、画像も同じように傾いてしまいますのでtransform: skewX(45deg);で元に戻してあげます。img02.jpgの大きさはimg01.jpgと同じにします(img01.jpgとimg02.jpgは縦横比が同じ画像とします)。

$mask.css({
			transform: `translateX(${img01Width + img01Height * 0.5}px) skewX(-45deg)`,
			width: `${img01Width + img01Height}px`,
			height: `${img01Height}px`
		});
$img02.css({
			transform: `translateX(-${img01Width + img01Height * 0.5}px) skewX(45deg)`,
			width: `${img01Width}px`
		});

テンプレート文字列 – JavaScript | MDN

スクロールイベントの設定

スクロールイベントの基本形です。6行目はスクロールの途中でリロードなどをした場合にスクロールしたことにしてイベントを発生させる記述です。

$window.scroll(function() {
・
・スクロール中の処理
・
});
$window.trigger('scroll');

イベント内容は3つにわけて考えます。

$window.scroll(function() {
	const dy = $(this).scrollTop();

	if (dy < holeOffsetTop - windowHeight * 0.5) {
		・
		・画像が画面の上下中央まで来ていない場合
		・(=画像が完全に隠れている)
		・
	} else if ((dy >= holeOffsetTop - windowHeight * 0.5) && (dy < holeOffsetTop - windowHeight * 0.5 + img01Height * 0.5)) {
		・
		・画像が画面の上下中央まで来てから画像の高さの1/2量スクロールするまで
		・(=画像が徐々に見えてくる)
		・
	} else {
		・
		・上記以上スクロールしている場合
		・(=画像が完全に見えている)
		・
	}
});
$window.trigger('scroll');

それぞれの状態での記述は以下のようになります。マスクを左にずらしてimg02.jpgの上に移動させていくわけですが、img02.jpgはマスクを親とする絶対位置指定ですのでマスクが左に動いた分、img02.jpgを右に動かしてあげて、マスクとimg02.jpgの相対位置を変化させていくことで画像の位置が変わらないようにしてあげます(img01.jpgの真上の位置を維持する)。

また、今回は画像の高さの1/2量スクロールしたら画像が全て見えるようにします。そのためにスクロール量に対するマスクの移動量を決める係数を定義しておきます。

// 画像の高さの何倍のスクロール量で画像が全て見えるようにするか
const ra = 0.5;
// 画像の高さのra分のスクロール量で画像が全てみえる場合の移動スピード
const dx = (img01Width + img01Height) / (img01Height * ra)

上記係数は下記11、13、16行目で使用しています。

$window.scroll(function() {
	const dy = $(this).scrollTop();

	if (dy < holeOffsetTop - windowHeight * 0.5) {
		$mask.css({
				transform: `translateX(${img01Width + img01Height * 0.5}px) skewX(-45deg)`,
						});
		$img02.css({
				transform: `translateX(-${img01Width + img01Height * 0.5}px) skewX(45deg)`,
						});
	} else if ((dy >= holeOffsetTop - windowHeight * 0.5) && (dy < holeOffsetTop - windowHeight * 0.5 + img01Height * ra)) {
		$mask.css({
				transform: `translateX(${img01Width + img01Height * 0.5 - (dy - (holeOffsetTop - windowHeight * 0.5)) * dx}px) skewX(-45deg)`
						});
		$img02.css({
				transform: `translateX(${-(img01Width + img01Height * 0.5) + (dy - (holeOffsetTop - windowHeight * 0.5)) * dx}px) skewX(45deg)`
						});
	} else {
		$mask.css({
				transform: `translateX(-${img01Height * 0.5}px) skewX(-45deg)`
						});
		$img02.css({
				transform: `translateX(${img01Height * 0.5}px) skewX(45deg)`
						});
	}
});
$window.trigger('scroll');

最後に通しでjQueryの記述を掲載しておきます。コメントアウトしている記述はテンプレートリテラルを使用しない記述です。

DEMO画面では以下のコードをbabelでコンパイルしたものを使用しています。
$(function() {
	'use strict';

	const $window = $(window);
	const $mask = $('.mask');
	const $img02 = $('.img02');
	const holeOffsetTop = $('#hole').offset().top;
	const windowHeight = $window.height();
	const img01Width = $('.img01').width();
	const img01Height = $('.img01').height();

	// 画像の高さの何倍のいスクロール量で画像が全て見えるようにするか
	const ra = 0.5;
	// 画像の高さのra分のスクロール量で画像が全てみえる場合の移動スピード
	const dx = (img01Width + img01Height) / (img01Height * ra)

	$mask.css({
						// transform: 'translateX(' + (img01Width + img01Height * 0.5) + 'px) skewX(-45deg)',
						// width: img01Width + img01Height+ 'px',
						// height: img01Height + 'px'
						transform: `translateX(${img01Width + img01Height * 0.5}px) skewX(-45deg)`,
						width: `${img01Width + img01Height}px`,
						height: `${img01Height}px`
					});
	$img02.css({
						// transform: 'translateX(-' + (img01Width + img01Height * 0.5) + 'px) skewX(45deg)',
						// width: img01Width + 'px'
						transform: `translateX(-${img01Width + img01Height * 0.5}px) skewX(45deg)`,
						width: `${img01Width}px`
					});

	$window.scroll(function() {
		const dy = $(this).scrollTop();

		if (dy < holeOffsetTop - windowHeight * 0.5) {
			$mask.css({
								transform: `translateX(${img01Width + img01Height * 0.5}px) skewX(-45deg)`,
							});
			$img02.css({
								transform: `translateX(-${img01Width + img01Height * 0.5}px) skewX(45deg)`,
							});
		} else if ((dy >= holeOffsetTop - windowHeight * 0.5) && (dy < holeOffsetTop - windowHeight * 0.5 + img01Height * ra)) {
			$mask.css({
								// transform: "translateX(" + ((img01Width + img01Height * 0.5) - (dy - (holeOffsetTop - windowHeight * 0.5)) * dx) + "px) skewX(-45deg)"
								transform: `translateX(${img01Width + img01Height * 0.5 - (dy - (holeOffsetTop - windowHeight * 0.5)) * dx}px) skewX(-45deg)`
							});
			$img02.css({
								// transform: "translateX(" + (-(img01Width + img01Height * 0.5) + (dy - (holeOffsetTop - windowHeight * 0.5)) * dx) + "px) skewX(45deg)"
								transform: `translateX(${-(img01Width + img01Height * 0.5) + (dy - (holeOffsetTop - windowHeight * 0.5)) * dx}px) skewX(45deg)`
							});
		} else {
			$mask.css({
								// transform: 'translateX(-' + img01Height * 0.5 + 'px) skewX(-45deg)'
								transform: `translateX(-${img01Height * 0.5}px) skewX(-45deg)`
							});
			$img02.css({
								// transform: 'translateX(' + img01Height * 0.5 + 'px) skewX(45deg)'
								transform: `translateX(${img01Height * 0.5}px) skewX(45deg)`
							});
		}

	});
	$window.trigger('scroll');

});

以上で「jQueryでスクロールと連動した画像の切り替え(斜めトリミング)を実装」の解説を終わります。

このエントリーをはてなブックマークに追加