Scroll 현재 값 계산하기

Apple 웹 사이트를 보다가 제품을 소개하는 페이지에 이용자가 scroll을 할 때마다 특정 구간에서 다양하고 화려한 애니메이션이 동작하는 것을 보고 감탄을 하게 되었다.
웹페이지가 이렇게 화려하고 스토리가 있을 수 있구나!!! 너무 빠져든 나머지 나는 그렇게 Scroll에 대해 공부를 시작하게 되었다.

❗️ 애플 사이트에서 Scroll 애니메이션 효과는 Chrome, Safari에서만 동작한다.


📝 전체적인 구현 컨셉

파란색 숫자 0 1 2 3 은 Scroll할때 특정 영역 Scene을 의미한다.
특정 영역(Scene)에 도달했을때 그 영역에 구현한 Animation이 동작한다. (각 구간에 대한 Animation 처리)

시작에 앞서 가장 우선시 되어야 할 것은 우리가 Scroll을 할 때마다 현재 또는 이전에 대한 Scroll 위치 값을 알아야 한다.


0 1 2 3 의 Scene 에 대한 HTML 코드

HTML

 <section class="scroll-section" id="scroll-section-0">
        <h1>Apple WonPro</h1>
        <div class="sticky-elem main-message">
          <p>누가 야수에게 힘을 허락하였는가</p>
        </div>
 </section>

<section class="scroll-section" id="scroll-section-1">
        <p class="description">
          야수 같은 파워와 판도를 바꾸는 배터리 사용 시간. 
          WonKeun Silicon의 마법은 그 효율성에서 비롯되죠. 
        </p>
</section>

<section class="scroll-section" id="scroll-section-2">
        <div class="sticky-elem desc-message">
          <p>
            역대 가장 강력한 Won Pro가 등장했습니다. 최초의 프로용 Apple
            Silicon인 Keun Pro 또는 Keun Max 칩을 탑재해 쏜살같이 빠른 속도
          </p>
        </div>
</section>

<section class="scroll-section" id="scroll-section-3">
        <p class="canvas-caption">
          WonMotion에는 처음으로 도입되는 기술이 웹페이지 스크롤부터 게임
          플레이까지 엄청나게 매끄러운 움직임과 탁월한 반응성.
        </p>
</section>

스크롤의 전체 높이 지정하기

  1. 각 scene에 대한 정보를 담고 있는 배열을 생성한다.
  2. Scroll section 0 1 2 3 에 대한 객체를 3개 만든다.

3가지의 객체

  • 스크롤 높이에 대한 객체 (스크롤 구간의 높이에 따라서 Animation의 동작이 빠르냐 느리냐를 결정할 수 있다. )
  • heightNum : 브라우저 높이의 5배로 scrollHeight를 셋팅해준다.
    • 디바이스 마다 높이가 다르기 때문에 특정 숫자로 높이를 고정하면 기기 마다 스크롤 값이 모두 다르게 된다.
    • 각 기기의 높이를 우선 읽어온 다음에 높이 곱하기 heightNum을 해주면 모든 기기는 5배 높이의 값을 동등하게 적용된다.
  • objs :
    • container : 각 구간의 section 컨테이너 역할이며, (HTML : id가 scroll-section-__ 인 태그에 대한 정보를 담는다. )

JavaScript

const sceneInfo = [
  {
    // 0
    heightNum: 5, // 브라우저 높이의 5배로 scrollHeight 셋팅.
    scrollHeight : 0, // 다른 funtion에서 높이 셋팅을 시켜줄 것이다. 
    // (다양한 기기에 따른 높이 값과 창 사이즈 변화에 대한 처리 때문에 따로 함수로 처리한다.)
    objs: {
      container: document.querySelector('#scroll-section-0')
    }
  },
  {
    // 1
    heightNum: 5,
    scrollHeight : 0,
     objs: {
      container: document.querySelector('#scroll-section-1')
    }
  },
  {
    // 2  
    heightNum: 5,
    scrollHeight : 0,
     objs: {
      container: document.querySelector('#scroll-section-2')
    }
  },
  {
    // 3
    heightNum: 5,
    scrollHeight : 0,
     objs: {
      container: document.querySelector('#scroll-section-3')
    }
  },
];
function setLayout(){
  // 각 Scroll 섹션에 높이를 셋팅하는 함수.
  for(let i = 0; i < sceneInfo.length; i++) {
    sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight;
    // sceneInfo의 scrollHeight 값은 (window.innerHeight)웹페이지 전체 높이 x (heightNum) 5 이다.
    sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`
    // objs객체 안에 container에 id가 scroll-section-__ 인 태그들의 style 속성을 변경 시키는데.
    // 높이 값을 scrollHeight 값으로 모두 적용 시켜준다.
  }
}

window.addEventListenr('load',setLayout); // 웹페이지가 Load되면 setLayout함수 실행시켜주기.
window.addEventListenr('resize',setLayout); // 웹페이지 창 크기가 변경되면 seyLayout의 함수를 재실행 시킨다.

정리 :
1. setLayout() 함수는 sceneInfo 배열에 있는 scrollHeight = 0 값을
웹페이지의 전체 높이에 5배(heightNum)로 값을 scrollHeight에 넣어준다.

{
    // 0, 1, 2, 3
    heightNum: 5,
    scrollHeight : heightNum * window.innerHeight,
     objs: {
      container: document.querySelector('#scroll-section-__')
    }
  }

그 후에
HTML 코드에 보이는 4개의<section id='scroll-section-__'> 태그들은 setLayout() 함수를 통해 각각 각각 scrollHeight 값을 height 값으로 갖는다.

웹페이지의 전체 높이(innerHeight)가 1041 라고 했을때. 1041 x 5(heightNum) = 5205
즉, 각 <section id='scroll-section-__'> 의 높이는 5205이고 4개의 section이 있다면 웹페이지의 전체 높이는 5205 x 4 = 20820 이다.


몇 번째 Scroll section이 우리 눈앞에 있는지
몇 번째 section을 scroll 중인지 판별하기.

(MDN) 스크롤 이벤트의 조절
Scroll이 될 때 그 Scroll에 대한 값을 처리를 하기 위해서 window.addEventListener('scroll')을 사용한다.

스크롤 값을 얻을 때 사용되는 pageYOffset, scrollY
scrollY : scrollY는 IE에서만 동작한다. 즉 최신 브라우저에서 동작한다.
pageYOffset : 구형 브라우저까지 신경써야 한다면 pageYoffset를 사용하는게 좋다.


scrollLoop() 함수의 원리 파악하기

🔎 전체 scroll의 값인 scrollY의 값 만으로 현재 몇번째 section인지 판별하기는 어렵다. 판별의 기준은 👇


let prevScrollHeight = 0; // 현재 스크롤 scrollY보다 이전에 위치한 스크롤 섹션들의 높이값의 합
let scrollY = 0; //pageYOffset 대신 쓸 변수.
let currentScene = 0; // 현재 활성화 된 section (눈 앞에 보고있는 section)

function scrollLoop(){ // 현재 눈앞에 몇번째 스크롤이 실행되고 있는지를 판별하는  함수.

}

window.addEventListener('scroll', () => { // 익명함수를 넣은 이유는 스크롤은 복잡게하게 동작하기 때문이다.
  scrollY = window.pageYOffset || document.documentElement.scrollTop; // 브라우저 버전 호환을 위해 조건문을 사용해서 두가지를 적용시키는 것이 안전하다.
  scrollLoop(); // 스크롤을 하면 기본적으로 실행되는 함수.
})
"function scrollLoop() { } 작성"

function scrollLoop() {
  prevScrollHeight = 0; // 원하는 값에서 그 값을 또 더한 값들이 스크롤할 때마다 기하급수적으로 더해져서 스크롤 할때 0으로 초기화를 시켜버림.
  for(let i = 0; i < currentScene; i++) {
  prevScrollHeight = prevScrollHeight + sceneInfo[i].scrollHeight;
  // prevScrollHeight에 모든 section(0,1,2,3)의 높이의 값을 더해서 넣는다.
  // prevScrollHeight에 = 15615
  }
  
  "스크롤 값에 의해 객체 이동 원리"
  if(scrollY > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { 
    currentScene++;
   
    // sceneInfo 배열에 4번째까지 객체가 존재하는데,
    // sceneInfo 배열안에 N번째 객체의 scrollHeight 값 보다 자신이 현재 스크롤한 값(scrollY)이 더 크면
    // currentScene는 1 증가하고 다음 sceneInfo에 있는 다음 N번째 객체로 이동한다.
    // sceneInfo[0].scrollHeight <-- sceneInfo[currentScene].scrollHeight
    // sceneInfo[1].scrollHeight <-- sceneInfo[currentScene].scrollHeight
    // sceneInfo[2].scrollHeight <-- sceneInfo[currentScene].scrollHeight
    // sceneInfo[3].scrollHeight <-- sceneInfo[currentScene].scrollHeight
  
  }
  
  if(scrollY < prevScrollHeight) { 
    if(currentScene === 0) return // 브라우저 바운스 효과로  인해 마이너스가 되는 것을 방지(모바일)
   currentScene--;
  }
}


☘️ Scene이 바뀔 때마다 scroll 값 0에서 재시작 시키기

지금까지는 총 scroll 값에 대한 것만 지정해 주었다.
하지만 currentScene이 바뀔 때마다 해당 Scene에서 얼마나 스크롤을 했는지에 대한 값도 알아낼 필요가 있다.
currentScene : 0, 1, 2, 3 은 4개의 영역이 존재한다.
계산은 간단하다.
지나가 버린 Scene들의 스크롤의 높이 값을 더해서 변수에 담아주었던 prevScrollHeight 변수에서 총 스크롤 값인 scrollY의 값을 빼주면 된다.

prevScrollHeight
0번째 Scene : 0
1번째 Scene : 5205
2번째 Scene : 10410
3번째 Scene : 15615

const currentYScrollSet = scrollY - prevScrollHeight;




☘️ 스크롤 값에 따라 opacity 값 변경해주기

특정 섹션에 있는 text 글씨가 스크롤의 값에 따라 나타나고 사라지는 효과를 적용할 것이다.
우선 opacity 특징은 대표적으로 0 과 1 의 값을 지정할 수 있다.
그렇기 때문에 0 ~ 1 사이의 값 즉 스크롤의 값을 0 . ___ 인 소수점으로 나타내야 한다.

const sceneInfo = [
    {
      scrollHeight: 0,
      objs: {
        //?: HTML 객체들을 모아두는 곳.
        container: document.querySelector('#scroll-section-0'),
        messageA: document.querySelector('#scroll-section-0 .main-message.a'),
        messageB: document.querySelector('#scroll-section-0 .main-message.b'),
        messageC: document.querySelector('#scroll-section-0 .main-message.c'),
        messageD: document.querySelector('#scroll-section-0 .main-message.d'),
      },
      values: {
        messageA_opacity: [0, 1], // opacity의 시작값과 종료값 설정해주기.
      },
    }

1. 얼만큼 스크롤 되었는지 비율로 계산하기

function calcValue(values, currentYScrollSet){ 
  // values : sceneInfo의 messageA_opacity
  // currentYScrollSet : 현재 Scene에 스크롤한 값
  
  // 현재 Scene의 전체범위 분의 현재 스크롤 값(currentYScrollSet)을 지정해줘야 한다.
 let scrollRatio = currentYScrollSet / sceneInfo[currentScene].scrollHeight;
 let rv = scrollRatio * (values[1] - values[0]) + values[0]; // 전체 범위 구하는 식
}

// 전체 범위 구하기 ! 
"각 텍스트는 opacity로 나타나서 사라지는 영역이 모두 다르다 어떤 텍스트는 (최솟값)200 ~ 500(최대값) 영역까지 동작하거나"
"어떤 텍스트의 opacity 시작점은 (최솟값)500 ~ 1000(최대값)일 수 있다"
"현재는 messageA_opacity: [0, 1] 이지만 messageA_opacity: [200, 900] 즉 시작점 200 ~ 900으로 끝낼 수 있다."

values: {
   messageA_opacity: [0, 1], // opacity의 시작값과 종료값 설정해주기.
}

let rv = scrollRatio * (values[1] - values[0]) + values[0];
   
values[1] = 1 
values[0] = 0 

🧩 messageA_opacity: [200, 900] 인 경우

values: {
   messageA_opacity: [200, 900], // opacity의 시작값과 종료값 설정해주기.
}
   
 let rv = scrollRatio * (values[1] - values[0]) + values[0]; // (200 - 900) 계산이 끝나면 시작값 + 200 을 더해준다.
   
values[1] = 900
values[0] = 200
  
values[1] : 900 - values[0] : 200 = 700 
700 + values[0] : 200 = 900

🧩 calcValue() 함수를 동작 시키고 opacity의 효과를 동작하는 function 생성하기


function playAnimation() {
    const objs = sceneInfo[currentScene].objs; //TODO: DOM 객체 요소들
    const values = sceneInfo[currentScene].values;
    const currentYScrollSet = scrollY - prevScrollHeight; //TODO: Scene이 바뀌면 scrollY 값이 다시 0에서 시작
  
    switch (currentScene) {
      case 0:
        let messageA_opacity_in = calcValues(values.messageA_opacity,currentYScrollSet);
        objs.messageA.style.opacity = messageA_opacity_in;
        
     // messageA: document.querySelector('#scroll-section-0 .main-message.a'),
      
        break;
      case 1:
        break;
      case 2:
        break;
      case 3:
        break;
    }
  }



🌿 애니메이션이 동작하는 영역 나누기

const sceneInfo = [
    {
      scrollHeight: 0,
      objs: {
        //?: HTML 객체들을 모아두는 곳.
        container: document.querySelector('#scroll-section-0'),
        messageA: document.querySelector('#scroll-section-0 .main-message.a'),
        messageB: document.querySelector('#scroll-section-0 .main-message.b'),
      },
      values: {
        messageA_opacity_in: [0, 1, { start: 0.1, end: 0.2 }], // 비율로 계산했기 때문에 0.1 ~ 0.2 약 10% 구간에서 동작 하도록 설정.
        messageB_opacity_in: [0, 1, { start: 0.3, end: 0.4 }], // 나타나기 30% ~ 40% 지점
        messageA_opacity_out: [1, 0, { start: 0.25, end: 0.3 }], // 사라지기 25% ~ 30% 지점
        messageB_opacity_out: [1, 0, { start: 0.1, end: 0.2 }], // 사라지기 10% ~ 20% 지점
      },
    },
  ]

function calcValues(values, currentYScrollSet) {
  //? currentYScrollSet: 현재 Scene에서 얼마나 스크롤 됬는지.
  const scrollHeight = sceneInfo[currentScene].scrollHeight;

  //? scrollRatio : 현재 (Scene)에서 스크롤된 범위를 비율로 구하기
  const scrollRatio = currentYScrollSet / scrollHeight;
  let rv;

  if (values.length === 3) { // values 객체 안에 3번째 index로 start, end 값이 존재하는지 여부 확인.
    //? start ~ end 사이에 animation 실행.
    
    const partScrollStart = values[2].start * scrollHeight; // 시작점(start) : 0.1 x 5205 
    const partScrollEnd = values[2].end * scrollHeight; // 종료지점(end) : 0.2 x 5205 
    const partScrollHeight = partScrollEnd - partScrollStart; // 시작부터 종료점까지의 높이
    				 // 1041 - 520.5 = 520.5
   } else {
     rv = scrollRatio * (values[1] - values[0]) + values[0];
    }

    return rv;
  }

좋은 웹페이지 즐겨찾기