14 스크롤 효과
1. 스크롤 효과를 만들어보자
1.1. 1단계-섹션1
1.1.1. 좌우이동 - css 메서드 활용
- 예제
- HTML
- CSS
- JS
참고
<!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>* {
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;
}.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%);
}$(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%)');
}
});
});참고
참고
이 코드는 잠재적인 버그가 있다. 작성후 어떤 버그가 발생할수 있을지 예상해보자
<!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>* {
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;
}.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%);
}$(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%)');
}
});
});참고
첫번째 섹션에 도달시 애니메이트가 실행된다. 반복문 내에서 순회하는 this 의 거리를 비교하고 있으므로 조건 추가시 다음 섹션의 거리를 비교하여 애니메이트가 실행된다.
1.2. 2단계-섹션2
1.2.1. 상하이동 시간차-animate() 메서드 활용
- 예제
- HTML
- CSS
- JQ
<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>참고
.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;
}참고
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');
}
});:::note 1단계의 코드는 크게 4가지의 버그가 발생할수 있다.
- 중복된 조건: if (winSCT > tgtop)같은 조건을 세번 반복 하여 비교하고 있다. 같은 조건이 성립되면 .box 요소의 스타일 또한 여러번 설정될 수 있다.
- 섹션별 offsetTop 값을 배열로 저장하여 이벤트 핸들러에 전달한다.
- 부적절한 비교 연산자: 모든 else if 문의 조건이 winSCT > tgtop으로 설정되어 있으므로 첫 번째 if 문과 동일한 조건이다. 결국 두 번째와 세 번째 else if 문은 실행되지 않는다.
- 배열의 인덱스 번호를 활용하여 조건을 명확하게 지정한다.
- 변수 범위(scope): winSCT, sections, tg, 그리고 tgtop 변수는 모두 함수 내부에서 선언되었다. 각 섹션 반복문마다 새로운 변수 인스턴스가 생성되므로 원하는 결과를 얻을 수 없을 수 있습니다.
- 전역변수로 수정한다.
- 중첩된 스크롤 이벤트 핸들러: 이벤트 핸들러 함수를 최상위 레벨로 작성하게 될경우 추후 하위에 다른 이벤트핸들러를 포함하게 되어 예기치 못한 동작을 초래할수 있다.
- 반복문과 이벤트 핸들러를 분리한다. :::
<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>참고
section2의 구조를 수정한다.
.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;
}참고
css를 추가한다.
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');
}
});:::note 1단계의 코드는 크게 4가지의 버그가 발생할수 있다.
- 중복된 조건: if (winSCT > tgtop)같은 조건을 세번 반복 하여 비교하고 있다. 같은 조건이 성립되면 .box 요소의 스타일 또한 여러번 설정될 수 있다.
- 섹션별 offsetTop 값을 배열로 저장하여 이벤트 핸들러에 전달한다.
- 부적절한 비교 연산자: 모든 else if 문의 조건이 winSCT > tgtop으로 설정되어 있으므로 첫 번째 if 문과 동일한 조건이다. 결국 두 번째와 세 번째 else if 문은 실행되지 않는다.
- 배열의 인덱스 번호를 활용하여 조건을 명확하게 지정한다.
- 변수 범위(scope): winSCT, sections, tg, 그리고 tgtop 변수는 모두 함수 내부에서 선언되었다. 각 섹션 반복문마다 새로운 변수 인스턴스가 생성되므로 원하는 결과를 얻을 수 없을 수 있습니다.
- 전역변수로 수정한다.
- 중첩된 스크롤 이벤트 핸들러: 이벤트 핸들러 함수를 최상위 레벨로 작성하게 될경우 추후 하위에 다른 이벤트핸들러를 포함하게 되어 예기치 못한 동작을 초래할수 있다.
- 반복문과 이벤트 핸들러를 분리한다. :::
1.3. 3단계-섹션3
1.3.1. 리빌효과-addClass() 메서드 활용
마스크효과를 구현해보자
- 예제
- HTML
- CSS
- JQ
<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>.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);
}참고
...생략
if (winSCT > topArr[2] && winSCT < topArr[3]-speed) {
sections.eq(2).addClass('is-animated');
}참고
<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>.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);
}...생략
if (winSCT > topArr[2] && winSCT < topArr[3]-speed) {
sections.eq(2).addClass('is-animated');
}참고
조건문을 추가한다.
1.4. 4단계-섹션4
1.4.1. PIP스크롤
화면안의 화면이 스크롤 되는 효과를 만들어보자
- 예제
- HTML
- CSS
- JQ
참고
<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>.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%;
}$(() => {
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참고
<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>.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%;
}$(() => {
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참고
창크기 변경시 스크롤값을 재계산하는 함수 추가 function pipScroll() 내에 작성한다.
// 윈도우 크기가 변경될 때 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스크롤 발전형
여러 섹션에서 사용할수 사용할수 있도록 함수를 수정한다.
- 예제
- HTML
- CSS
- JS
<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();
});<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. 가로 스크롤 효과
- 예제
- HTML
- CSS
- JS
- JQ
<!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>.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>
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);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();
},
});<!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>.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>
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);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();
},
});