# 14.스크롤 효과

By
코알라코딩
In 

# 1. 스크롤 효과를 만들어보자

# 1.1. 1단계-섹션1

# 1.1.1. 좌우이동 - css 메서드 활용

미리보기
https://qwerewqwerew.github.io/source/jq/14/step1/play.html

html
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<link rel="stylesheet" href="./css/jq-01.css" />
		<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
		<script src="./js/jq-01.js" defer></script>
	</head>

	<body>
		<section class="section1">
			<h2>section1</h2>
			<div class="container">
				<div class="box box1 bg2"></div>
				<div class="box box2 bg3"></div>
			</div>
		</section>
		<section class="section2">
			<h2>section2</h2>
			<div class="box box1 bg4"></div>
			<div class="box box2 bg5"></div>
		</section>
		<section class="section3">
			<h2>section3</h2>
			<div class="box box1 bg2"></div>
			<div class="box box2 bg1"></div>
		</section>
		<section class="section4">
			<h2>section4</h2>
			<div class="box box1 bg2"></div>
			<div class="box box2 bg1"></div>
		</section>
		<section class="section5">
			<h2>section5</h2>
			<div class="box box1 bg2"></div>
			<div class="box box2 bg3"></div>
		</section>
	</body>
</html>
css
* {
	margin: 0;
	padding: 0;
}
:root {
	--bg1: #285dfb;
	--bg2: #537dfb;
	--bg3: #7e9efc;
	--bg4: #a9befd;
	--bg5: #d4dffe;
}
.bg1 {
	background-color: var(--bg1);
	color: var(--bg5);
}
.bg2 {
	background-color: var(--bg2);
	color: var(--bg4);
}
.bg3 {
	background-color: var(--bg3);
	color: var(--bg3);
}
.bg4 {
	background-color: var(--bg4);
	color: var(--bg2);
}
.bg5 {
	background-color: var(--bg5);
	color: var(--bg1);
}
section {
	overflow: hidden;
	text-align: center;
	width: 100%;
	height: 100vh;
}

section h2 {
	padding: 12vw 6vw;
}
애니메이트 효과 css
.box {
	display: inline-block; /* clamp (최소,기본,최대)최소, 최대가 명확한 경우 사용가능 */
	width: clamp(100px, 30%, 100%);
	height: 300px;
	transition: all 2s;
}
.box1 {
	transform: translateX(-200%);
}
.box2 {
	transform: translateX(200%);
}
.box.in {
	transform: translateX(-200%);
}
jQuery
$(window).on('scroll', () => {
	let winSCT;
	const sections = $('section');
	winSCT = $(window).scrollTop();
	sections.each(function (idx, o) {
		$(o).addClass(`bg${idx + 1}`);
		const tg = $(this);
		const tgtop = tg.offset().top;
		if (winSCT > tgtop) {
			tg.find('.box').css('transform', 'translateX(0%)');
		} else if (winSCT > tgtop) {
			tg.find('.box').css('transform', 'translateX(0%)');
		} else if (winSCT > tgtop) {
			tg.find('.box').css('transform', 'translateX(0%)');
		}
	});
});

# 1.2. 2단계-섹션2

# 1.2.1. 상하이동 시간차-animate() 메서드 활용

미리보기

html
<section class="section2">
	<h2>section2</h2>
	<div class="gallery">
		<div class="box bg3"></div>
		<div class="box bg4"></div>
		<div class="box bg5"></div>
	</div>
</section>
css
.section1 .box {
	display: inline-block;
	/* 최소, 최대가 명확한 경우 사용가능 */
	width: clamp(100px, 30%, 100%);
	height: 300px;
	transition: all 2s;
}

.section1 .box1 {
	transform: translateX(-200%);
}

.section1 .box2 {
	transform: translateX(200%);
}

.section1 .box.in {
	transform: translateX(-200%);
}

.section2 {
	position: relative;
}
.section2 .gallery {
	position: relative;
}
.section2 .gallery .box {
	width: 15vw;
	height: 200px;
	position: absolute;
	opacity: 0;
	top: 100vw;
}

.section2 .bg3 {
	left: 10vw;
}

.section2 .bg4 {
	left: 40vw;
}

.section2 .bg5 {
	right: 10vw;
}
jQuery
const sections = $('section');
let speed = Math.floor(sections.outerHeight() * 0.2);
let topArr = [];
let winSCT;

sections.each((idx, section) => {
	$(section).addClass(`bg${idx + 1}`);
	const sectionTop = $(section).offset().top;
	topArr.push(sectionTop);
});

$(window).on('scroll', () => {
	winSCT = $(window).scrollTop();
	if (winSCT > topArr[0] winSCT < topArr[1] - speed) {
		sections.eq(0).find('.box').css('transform', 'translateX(0%)');
	}
	if (winSCT > topArr[1] winSCT < topArr[2]) {
		sections.eq(1).find('.bg3').stop().delay(0).animate({ top: '5vw', opacity: 1 }, 500, 'swing');
		sections.eq(1).find('.bg4').stop().delay(100).animate({ top: '0vw', opacity: 1 }, 800, 'swing');
		sections.eq(1).find('.bg5').stop().delay(200).animate({ top: '-5vw', opacity: 1 }, 1100, 'swing');
	}
});

# 1.3. 3단계-섹션3

# 1.3.1. 리빌효과-addClass() 메서드 활용

마스크효과를 구현해보자

미리보기
https://qwerewqwerew.github.io/source/jq/14/step3/play.html

html
<section class="section3">
	<div class="item">
		<h2>section3</h2>
		<figure>
			<img src="http://qwerew.cafe24.com/images/1.jpg" alt="" />
			<figcaption>yum yum</figcaption>
		</figure>
	</div>
	<div class="item">
		<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Saepe est nostrum amet eligendi quas fugit libero cumque deserunt voluptate placeat dolorum culpa praesentium reiciendis, aliquid ad illum laborum, harum ratione.</p>
	</div>
</section>
css
.section3 {
	display: flex;
	color: #333;
	gap: 2rem;
}

.section3 .item:nth-child(1) {
	flex-basis: 60%;
}

.section3 .item:nth-child(2) {
	flex-basis: 40%;
	align-self: center;
}

.section3 figure {
	position: relative;
	box-shadow: -1rem 1rem 3rem -2rem rgba(0, 0, 0, 0.5);
}

.section3 figure:before {
	position: absolute;
	top: 0;
	right: 0;
	bottom: 0;
	left: 0;
	background: var(--bg1);
	transition: clip-path 0.8s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}

.section3 figure img {
	width: 100%;
	display: block;
	clip-path: inset(0 100% 0 0);
	/* duration 0.6 delay 0.3 */
	transition: clip-path 0.6s 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}

.section3 figure figcaption {
	position: absolute;
	top: 20px;
	right: 20px;
	padding: 10px;
	font-weight: bold;
	text-transform: uppercase;
	color: #fff;
	background: var(--bg1);
	mix-blend-mode: difference;
	transition: clip-path 0.3s 0.9s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}

.section3 figure::before,
.section3 figure figcaption {
	clip-path: inset(0 0 0 100%);
}

.section3.is-animated figure::before,
.section3.is-animated figure img,
.section3.is-animated figure figcaption {
	clip-path: inset(0);
}
javascript
...생략
if (winSCT > topArr[2] && winSCT < topArr[3]-speed) {
	sections.eq(2).addClass('is-animated');
}

# 1.4. 4단계-섹션4

# 1.4.1. PIP스크롤

화면안의 화면이 스크롤 되는 효과를 만들어보자

이미지파일

미리보기
https://qwerewqwerew.github.io/source/jq/14/step4/play.html

html
<section class="section4">
	<h2>section4</h2>
	<div class="container">
		<div class="item left pa">
			<div class="mockup pc">
				<div class="mask"><img src="./image/project1_pc.png" alt="" class="screen" /></div>
				<img src="./image/desktop.png" alt="" class="device" />
			</div>
			<div class="mockup mobile">
				<div class="mask"><img src="./image/project1_mobile.png" alt="" class="screen" /></div>
				<img src="./image/mobile.png" alt="" class="device" />
			</div>
		</div>
		<div class="item right bg1 pa"></div>
	</div>
</section>
css
.section4 .container {
	display: flex;
	position: relative;
}
.pa {
	position: absolute;
	top: 0;
}

.item {
	height: 30vw;
}
.left {
	width: 60vw;
	transition: left 1s ease-in-out;
	left: -100%;
}
.right {
	width: 40vw;
	right: 0;
}
.is-animated .left {
	left: 0;
}
.left .mockup img,
.left .mockup .mask {
	position: absolute;
	top: 0;
	left: 0;
}
.left .mockup.pc {
	margin-left: clamp(5%, 100px, 10%);
	position: relative;
	width: 60%;
	height: 100%;
}

.left .mockup.pc .mask {
	z-index: 3;
	width: 32.3vw;
	height: 61.8%;
	overflow: hidden;
	top: 6%;
	left: 5.2%;
}
.left .mockup.pc img.screen {
	z-index: 1;
	width: 100%;
}
.left .mockup.pc img.device {
	z-index: 2;
	width: 100%;
}
/* mobile */
.left .mockup.mobile {
	z-index: 99;
	position: relative;
	top: -76%;
	left: 58%;
	width: 20%;
	height: 60%;
}

.left .mockup.mobile .mask {
	z-index: 999;
	width: 10.5vw;
	height: 100%;
	overflow: hidden;
	top: 10.8%;
	left: 7%;
	border-radius: 16px 16px 0 0;
}
.left .mockup.mobile img.screen {
	z-index: 3;
	width: 100%;
}
.left .mockup.mobile img.device {
	z-index: 4;
	width: 100%;
}
javascript
$(() => {
	const sections = $('section');
	let speed = Math.floor(sections.outerHeight() * 0.3);
	let topArr = [];
	let winSCT;

	sections.each((idx, section) => {
		$(section).addClass(`bg${idx + 1}`);
		const sectionTop = $(section).offset().top;
		topArr.push(sectionTop);
	});

	/* 스크롤함수 */
	$(window).on('scroll', () => {
		winSCT = $(window).scrollTop();
		if (winSCT > topArr[0] && winSCT < topArr[1] - speed) {
			sections.eq(0).find('.box').css('transform', 'translateX(0%)');
		}

		if (winSCT > topArr[1] && winSCT < topArr[2] - speed) {
			sections.eq(1).find('.bg3').stop().delay(100).animate({ top: 0, opacity: 1 }, 500, 'swing');
			sections.eq(1).find('.bg4').stop().delay(200).animate({ top: -100, opacity: 1 }, 800, 'swing');
			sections.eq(1).find('.bg5').stop().delay(300).animate({ top: -200, opacity: 1 }, 1100, 'swing');
		}

		if (winSCT > topArr[2] && winSCT < topArr[3] - speed) {
			console.log(winSCT > topArr[2] && winSCT < topArr[3]);
			sections.eq(2).addClass('is-animated');
		}
		if (winSCT > topArr[3] && winSCT < topArr[4]) {
			sections.eq(3).addClass('is-animated');
		}
	});
	pipScroll();
	function pipScroll() {
		const section = sections.eq(3);
		const devices = ['.mockup.pc', '.mockup.mobile'];

		$.each(devices, function (i, deviceEl) {
			const device = section.find(deviceEl);
			const screen = device.find('.mask>img');
			const mask = device.find('.mask');
			const heightDifference = screen.innerHeight() - mask.innerHeight();
			console.log(device.innerHeight());
			console.log(screen.innerHeight());

			device.on({
				mouseenter: function () {
					if (section.hasClass('is-animated')) {
						screen.stop().animate({ top: -heightDifference }, 1000);
					}
				},
				mouseleave: function () {
					if (section.hasClass('is-animated')) {
						screen.stop().animate({ top: 0 }, 1000);
					}
				},
			});
		});
	}
}); //jQuery
// 윈도우 크기가 변경될 때 heightDifference를 다시 계산.
function pipScroll() {
...생략
$(window).on('resize', function () {
	$.each(devices, function (i, deviceEl) {
		let device = section.find(deviceEl);
		let screen = device.find('.mask>img');
		let mask = device.find('.mask');
		let heightDifference = screen.innerHeight() - mask.innerHeight();

		// heightDifference를 다시 설정.
		device.data('heightDifference', heightDifference);
		console.log(heightDifference);
			});
		});
	}
})//jQuery

# 1.4.2. PIP스크롤 발전형

여러 섹션에서 사용할수 사용할수 있도록 함수를 수정한다.

미리보기
https://qwerewqwerew.github.io/source/jq/14/step6/play.html

<section class="section5">
	<div class="container">
		<div class="item left">
			<div class="mockup pc">
				<div class="mask">
					<img src="./image/project1_pc.png" alt="" class="screen" />
				</div>
				<img src="./image/desktop.png" alt="" class="device" />
			</div>
			<!-- //.mockup.pc -->
			<div class="mockup mobile">
				<div class="mask"><img src="./image/project1_mobile.png" alt="" class="screen" /></div>
				<img src="./image/mobile.png" alt="" class="device" />
			</div>
			<!-- //.mockup.mobile -->
		</div>
		<!-- //.left -->
		<div class="item right bg1"></div>
	</div>
	<!-- //.right -->
</section>

섹션추가

.section5,
.section4 {
	position: relative;
}
.section5 .container,
.section4 .container {
	position: relative; /* absolute를 제어하는 relative 는 꼭 크기를 넣을것  */
	width: 100%;
	height: 100%;
}
.section5 .item,
.section4 .item {
	position: absolute;
	top: 0;
	height: 30vw;
}
.section5 .item.left,
.section4 .item.left {
	width: 60%; /*  */
	transition: left 1s ease-in-out;
	left: -100%;
}
.section5.is-animated .item.left,
.section4.is-animated .item.left {
	left: 0%;
	transition: left 2s ease-in-out;
}

.section5 .item.right,
.section4 .item.right {
	width: 40%;
	right: 0;
}
.section5 .mockup.pc .mask,
.section4 .mockup.pc .mask {
	z-index: 8;
	width: 32.3vw;
	height: 61.8%;
	overflow: hidden;
	left: 5.2%;
	top: 6%;
}

.section4 선택자에 .section5를 추가

const win = $(window);
const sections = $('section');
let speed = Math.floor(win.height() * 0.5);
let topArr = [];
let winSCT;
console.log(speed);
//sections.offsetTop
sections.each(function (i, o) {
	const sectionTop = $(o).offset().top;
	topArr.push(sectionTop);
});
win.on('scroll', () => {
	winSCT = win.scrollTop();
	if (winSCT > topArr[0] && winSCT < topArr[1]) {
		sections.eq(0).addClass('is-animated').siblings().removeClass('is-animated');
	}
	if (winSCT > topArr[1] - speed && winSCT < topArr[2]) {
		sections.eq(1).addClass('is-animated').siblings().removeClass('is-animated');
	}
	if (winSCT > topArr[2] - speed && winSCT < topArr[3]) {
		sections.eq(2).addClass('is-animated').siblings().removeClass('is-animated');
	}
	if (winSCT > topArr[3] - speed && winSCT < topArr[4]) {
		sections.eq(3).addClass('is-animated').siblings().removeClass('is-animated');
		pipScroll();
		console.log(topArr[4], winSCT);
	}
	if (winSCT > topArr[4] - speed) {
		sections.eq(4).addClass('is-animated').siblings().removeClass('is-animated');
		pipScroll();
	}
});

function pipScroll() {
	const devices = ['.mockup.pc', '.mockup.mobile'];
	$.each(devices, function (i, deviceEl) {
		const device = $(deviceEl);
		const screen = device.find('.screen');
		const mask = device.find('.mask');
		const hightDifference = screen.innerHeight() - mask.innerHeight();
		console.log('hightDifference', hightDifference);
		device.on({
			mouseenter: function () {
				screen.stop().animate({ top: -hightDifference }, 1000);
			},
			mouseleave: function () {
				screen.stop().animate({ top: 0 }, 1000);
			},
		});
	});
}
win.on('resize', function () {
	pipScroll();
});

위의 코드는 화면에는 보이지 않지만 버그가 있다. ['.mockup.pc', '.mockup.mobile'] 에는 단일 요소가 할당 되었으므로두개의 mockup.pc 가 추가 될 경우 각각 동작하지 않는다. 이것을 제이쿼리 객체로 변경하면 여러개 요소를 알아서 반복 처리 하기 때문에 각각 다른 이벤트를 처리할수 있다. 아래처럼 수정하자

	function pipScroll() {
		//const devices = ['.mockup.pc', '.mockup.mobile'];
		const devices = $('.mockup.pc, .mockup.mobile');
		//$.each(devices, function (i, deviceEl) {
		devices.each(function (i, deviceEl) {
			let device = $(this);
			let screen = device.find('.mask>img');

# 1.5. 가로 스크롤 효과

미리보기
https://qwerewqwerew.github.io/source/jq/14/step7/1.html

html
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title></title>
	</head>

	<body>
		<main class="container">
			<section id="section1" class="item">
				<h2 class="num">01</h2>
			</section>
			<section id="section2" class="item">
				<h2 class="num">02</h2>
			</section>
			<section id="section3" class="item">
				<h2 class="num">03</h2>
			</section>
			<section id="section4" class="item">
				<h2 class="num">04</h2>
			</section>
			<section id="section5" class="item">
				<h2 class="num">05</h2>
			</section>
			<section id="section6" class="item">
				<h2 class="num">06</h2>
			</section>
			<section id="section7" class="item">
				<h2 class="num">07</h2>
			</section>
			<section id="section8" class="item">
				<h2 class="num">08</h2>
			</section>
			<section id="section9" class="item">
				<h2 class="num">09</h2>
			</section>
		</main>
	</body>
</html>
css
.container {
	position: fixed;
	left: 0;
	top: 0;
	display: flex;
}
.item {
	width: 100vw;
	height: 100vh;
	position: relative;
}
#section1 {
	background-color: #111;
}
#section2 {
	background-color: #222;
}
#section3 {
	background-color: #333;
}
#section4 {
	background-color: #444;
}
#section5 {
	background-color: #555;
}
#section6 {
	background-color: #666;
}
#section7 {
	background-color: #777;
}
#section8 {
	background-color: #888;
}
#section9 {
	background-color: #999;
}

.num {
	position: absolute;
	bottom: 20px;
	right: 20px;
	color: #fff;
	font-size: 20vw;
	z-index: 10000;
}

자바스크립트는 gsap 을 사용

<script src="https://unpkg.com/gsap@3/dist/gsap.min.js"></script>

javascript
const cont = document.querySelector('.container');
const item = document.querySelector('.item');

function scroll() {
	let scrollTop = window.scrollY;
	let offsetLeft = cont.offsetWidth;
	document.body.style.height = offsetLeft + 'px';

	let viewWidth = offsetLeft - window.innerWidth;
	let viewHeight = offsetLeft - window.innerHeight;
	let goLeft = scrollTop * (viewWidth / viewHeight);

	gsap.to(cont, { left: -goLeft });

	requestAnimationFrame(scroll);
}
scroll();

window.addEventListener('resize', scroll);
jQuery
const cont = $('.container');
function scroll() {
	let scrollTop = $(window).scrollTop();
	let offsetLeft = cont.outerWidth();
	$('body').css('height', offsetLeft + 'px');

	let viewWidth = offsetLeft - $(window).innerWidth();
	let viewHeight = offsetLeft - $(window).innerHeight();
	let goLeft = scrollTop * (viewWidth / viewHeight);

	cont.css('left', -goLeft);
	console.log(scrollTop, offsetLeft, viewWidth, goLeft, viewHeight);
}
scroll();
$(window).on({
	resize: function () {
		scroll();
	},
	scroll: function () {
		scroll();
	},
});