WEB-DEVELOPMENT

  • account_tree
  • bug_report

화면 스크롤에 따른 효과 적용

웹 페이지를 스크롤시 뷰포트시에 진입할 경우 시각적 효과를 적용하여 집중과 전달력을 높히는 기본적인 설계에 대해 알아보자.

특정 위치로 화면을 스크롤

스크롤이 가능한 웹 페이지에서 특정 위치(x, y)로 화면을 스크롤하고자 할 때 다음의 몇 가지 API를 활용할 수 있다.

다음의 메소드를 사용하는 대상은 스크롤이 가능한 window, element일 수 있다.

scroll()

scroll() 메소드는 다음 두 가지 유형의 인수를 가질 수 있다.

x 좌표, y 좌표 값을 두 개의 인수로 지정

이동하고자 하는 위치를 직접 정수로 지정한다. window의 경우에는 뷰포트 상단, 요소의 경우 요소의 내부 상단으로 스크롤된다.

스크롤 시키고자 하는 위치 값과 스크롤 방식을 키로 갖는 Dictionary 타입으로 인수로 지정

다음 키를 갖는 Dictionary 타입으로 인수를 지정할 수 있다.

  • left: 스크롤하고자 하는 수평 위치 값
  • top: 스크롤하고자 하는 수직 위치 값
  • behavior: 스크롤 방법을 'auto''smooth' 중에서 선택한다. 'smooth'를 선택하면 부드럽게 스크롤되지만 'auto'는 즉시 스크롤된다. 기본 값은 'auto'이다.

scrollIntoView()

scrollIntoView() 메소드는scroll() 메소드와 마찬가지로 스크롤이 가능한 window 또는 element 내부를 특정 위치로 스크롤 시킨다. scroll() 메소드와의 차이점은 정수 타입의 위치가 아닌 스크롤 위치 기준을 키워드로 지정한다.

scollIntoView() 메소드는 다음 두 가지 타입의 인수를 가질 수 있다.

boolean 타입으로 스크롤 시킬 기준 위치를 인수로 지정한다.

다음의 의미를 갖는 boolean 타입으로 지정한다. 좀더 자세한 내용은 scrollIntoView() 메소드를 참고한다.

  • true : 기본 값이며 요소가 스크롤 영역의 상단에 맞춰  스크롤된다. dictionary 타입으로 지정할시 다음의 값과 동일한다.
    • {  block: "start", inline: "nearest" }
  • false : 요소가 스크롤 영역의 하단에 맞춰  스크롤된다. dictionary 타입으로 지정할시 다음의 값과 동일한다
    • {  block: "end", inline: "nearest" }

스크롤 시키고자 하는 위치 값와 스크롤 방식을 키로 갖는 Dictionary 타입으로 인수로 지정

다음 키를 갖는 Dictionary 타입으로 인수를 지정할 수 있다. 각 키에 대한 설정 값의 내용은 scrollIntoView() 메소드를 참고한다.

  • behavior : 스크롤시 스크롤 동작 방식을 'auto'와 'smooth'중에서 선택한다. 선택사항이며 기본 값은 'auto'이다. 'smooth'를 선택시 부드럽게 스크롤되어진다.
  • block : 수직 스크롤 위치 기준에 대한 정렬을 'start''center''end''nearest' 중에서 선택한다. 선택사항이며 기본 값은 'start'이다.
  • inline : 수평 스크롤 위치 기준에 대한 정렬을 'start''center''end''nearest' 중에서 선택한다. 선택사항이며 기본 값은 'nearest'이다.

샘플 코드

스크롤에 따른 효과 적용

위와 같은 방법으로 스크롤이 되는 시점에 특별한 효과를 적용하고자 할 때는 효과를 적용하고자 하는 요소의 스크롤 위치를 알아야 한다. 효과를 적용하는 대상은 실제 스크롤 대상일 수도 있거나 다른 요소일 수도 있다. 스크롤 위치를 알아낸 후 실제 스크롤 높이와 비교하여 뷰포트 상에 들어올 경우 효과를 적용한다.

효과를 적용할 때는 자바스크립트로 직접 애니메이션을 적용하거나 준비된 CSS로 적용할 수도 있다.

스크롤 위치 구하기

스크롤 대상의 스크롤 위치 알아내기 위해서는 스크롤 처리를 하는 요소에게 scroll 이벤트를 등록한다. 이벤트 등록 대상은  window이거나 element 일 수 있다.

스크롤 대상의 상대적 위치 알아내기

offsetLeft 또는 offsetTop 프로퍼티로 상대적으로 가장 가까운 상위 컨테이너를 기준으로 상단 거리를 픽셀 단위로 조회한다.

뷰포트로부터 절대적 위치 알아내기

getBoundingClientRect() 메소드로 뷰포트로 부터 절대적 위치를 알아낸다.  주의할 점은 로드 후, 최초 뷰포트 상단(또는 하단)으로 이동시킨 후 바로 이어서 다시 한번 동일한 메뉴를 재선택할 경우 오작동이 발생할 수 있다. 이는 뷰포트 상단(또는 하단)으로 부터의 거리를 다시 계산하여 사용하는 것으로 이동 후에는 뷰포트로 부터의 거리가 달라지게 되는 점을 코드에 반영해야 한다.

효과 적용하기

위의 샘플 코드를 가지고 스크롤시 각 단락의 제목이 화면 상단에 다다를 때 텍스트 컬러를 변경하는 효과를 적용해보자.

우선 각 단락의 제목이 뷰포트 상단으로부터의 위치한 값을 찾아 배열에 담아 둔다. 나중에 이 배열은 스크롤 이벤트시 스크롤 위치에 대한 범위을 검사하는 용도로 사용된다.

const elHeading = document.querySelectorAll("main > h2");
let aTops = [];
elHeading.forEach(function(heading) {
    aTops.push(heading.getBoundingClientRect().top);
});

scroll 이벤트 적용시에는 주의할 점이 있다. scroll 이벤트 스크롤이 발생할 때 지속적으로 이벤트가 호출된다. 따라서 스크롤시 동일한 로직을 반복하게 되는 문제가 발생한다. 겉으로는 문제가 없어 보이더라도 좋은 방식은 아니다.

그렇다면 이것을 해결할 방법이 있는가?  당연히 존재한다. setTimeout() 메소드를 활용하면 쉽게 해결할 수 있다. 다음 처럼 최종 스크롤이 끝날때만 로직을 동작하도록 clearTimeout() 메소드로 동작을 취소하는 로직을 만들어 준다.

let scrollTimer;
window.addEventListener("scroll", function() {
    clearTimeout(scrollTimer);
    scrollTimer = setTimeout(function() {
        // 로직
    }, 50);
});

다음은 scroll 이벤트 발생시 미리 준비해 둔 각 단락의 제목의 위치 값이 범위에 해당하는지 검사한다. 검사 기준은 스크롤시 뷰포트 상단 위치(scrollY) 값보다 작거나 같은 경우이다. 만약에 해당된다면 제목의 텍스트 컬러를 변경한다. 또한 범위에 해당하지 않는다면 원래의 텍스트 컬러로 바꾼다.

let scrollTimer;
window.addEventListener("scroll", function() {
    clearTimeout(scrollTimer);
    scrollTimer = setTimeout(function() {
        elHeading.forEach(function(heading) {
            heading.style.color = "#000";
        });
        
        aTops.forEach(function(top,idx) {
            if(top <= window.scrollY) elHeading[idx].style.color = "#FF0000";
        })
    }, 50);
});
 
적용 샘플 코드

필요에 따라 업데이트

그러나, 위의 코드의 경우는 4번째 항목에 대한 CSS 반응이 없다. 마지막 섹션의 경우 뷰포트 상단까지 스크롤할 만한 콘텐츠가 부족하므로 원하는 조건과 맞을 수 없기때문이다. 이 문제를 해결하기 위해서는 선택한 목차의 항목에 대한 인덱스를 활용하면 된다. 다음 아래의 코드에서 변수가 applyIdx가 그 역할을 한다. 

하지만 마우스로 직접 스크롤하는 경우에는 해당되지 않으므로 창의 스크롤바를 보이지 않도록 해야 한다.