본문 바로가기

UI 디자인 애니메이션 연구소

스크롤링 헤드라인 이라 불리는 뉴스티커 UI, 원리부터 구현 까지

반응형

이전 애니메이션

 

HTML, CSS 로 불타는 입력창(Input) 만들기

미리보기해당 포스트를 따라오시면, 아래와 같은 애니메이션을 구현하실 수 있게 됩니다. HTMLHTML 구조는 별게 없습니다. label 의 경우 인접 형제 선택자(+) 를 사용하기 위해 input 아래에 label 을

duklook.tistory.com

 


들어가기 전 | 포스트 작성 방식 변경 합니다.

오늘 부터 UI 디자인 애니메이션 연구소 라는 카테고리 타이틀에 맞게 포스트를 작성하는 방식을 바꿔볼까 합니다. Codepen 을 이용해서 여러 가지를 시도하다보니, 나중에 포스트에 작성할 때는 작성한 코드를 일일이 설명하는 방식이 되었고, 게시글의 목적과는 다른 방향으로 적어왔던 것 같습니다. 그래서 2024.06.25 부터는 새로운 방식으로 실제 애니메이션 등의 원리를 파악해보고, 이를 구현하는 알고리즘을 설명하고, 직접 구현하는 방향으로 글을 작성해볼까 합니다.

 


미리 보기

 

 

 

오늘의 주인공 | 뉴스티커

네이버 홈페이지에 접속하고 나서 뉴스스탠드 부분을 보시면 아래와 같은 바를 보셨을 지도 모릅니다. 보통 공지사항이나 빠르게 주요 정보를 휙휙 보여주고 싶을 때 사용하는 UI 로 뉴스 티커(news ticker) 또는 스크롤링 뉴스 헤드라인(scrolling news headlines)" 이라고 불립니다.

 

오늘은 이 친구의 원리를 분석하고, 직접 만들어보는 시간을 가져볼까 합니다.

 

원리 파악

네이버의 뉴스티커 애니메이션

앞서 UI 의 마크업을 뜯어 보면 아래와 같이 AutoRolling 이라는 클래스가 부여된 두 개의 태그가 있습니다. 

 

 

이 친구들이 어떻게 동작하고 있는지 style 을 건드려서 한 번 확인해봅시다. 현재 ContentHeaderSubView 라는 클래스가 부여된 div 태그에 적용된 overflow 속성을 제거 해봤습니다.

 

바로 부모 태그를 벗어나는 자식요소들이 2 개 보이는 군요.

 

이 친구들은 동작하면 아래 영상처럼 보여집니다. 혹시 영상이 보이지 않는다면, 새로고침 해보시길 바랍니다. 서브파트 쿠키 광고 어쩌구 하면서 차단이 되는데, 새로고침하면 대부분 해결 되더라구요.

 

애니메이션 동작 원리

대략 해당 영상을 보셨다면, 어떠한 원리로 동작하고 있는지 파악하셨을 거라 생각합니다. 이에 대해서 제가 이해한 방식대로 설명해보곘습니다.

 

우선, 해당 애니메이션에서 컨테이너 내의 요소들은 초기에 아래와 같이 배치가 됩니다. 이 때, 두 번째 자식 요소는 overflow:hidden 이 적용된 부모 컨테이너를 벗어나면서 보이지 않는 상태입니다.

 

 

일정 시간이 지나고, 내부 요소 전체를 감싸고 있는 wrapper 요소가 컨테이너 요소의 상단으로 내부 요소의 높이 만큼 스크롤 됩니다. 그 결과 기존에는 보이지 않았던 두 번째 요소가 사용자에게 보여지고, 첫 번째 요소는 컨테이너 요소의 상단으로 이동하여 보이지 않는 상태가 됩니다.

 

이후 다음 애니메이션이 동작하기 까지 잠깐의 지연 시간이 존재하고, 지연 시간이 끝나는 순간, 지정되어 있던 애니메이션을 제거하게 되고, 이 때  요소들의 위치를 초기 위치로 즉시 변경합니다. 동시에 첫 번째 자식 요소를 컨테이너의 마지막 자식요소로 이동 시켜, 애니메이션 실행 시 보여지는 요소의 우선순위를 바꿔줍니다.

 

 

앞서 과정까지 완료 후 애니메이션을 다시 설정 후 실행시키게 되면, 무한히 반복되는 스크롤링이 형성 됩니다.

 

구현해보기

원리는 대강 파악하였으니, 실제 구현해보는 시간을 가져봅시다. 

HTML

우선 네이버의 뉴스티커의 마크업과 최대한 유사한 방식으로 아래와 같이 HTML 을 작성해줍니다.

<div class='ticker_container'>
  <div class='ticker_wrapper'>
    <div class="ticker">[속보] 이수떙,포기란 김장할 때나 세는 것</div>
    <div class="ticker">[속보] 김정금, 푸딩과 유튜브 찍어..</div>
  </div>
</div>

 

여기서 ticker_container 는 overflow:hidden 을 적용할 대상이 되고, 그 내부의 요소가 ticker_wrapper 로 감싸진 ticker 요소가 됩니다. 향후 애니메이션을 구현할 때 움직이는 대상은 ticker 클래스가 부여된 각각의 요소가 아니라. 해당 요소를 둘러싸고 있는 ticker_wrapper 가  될 것입니다.

 

CSS

각 요소에 적용된 설명은 주석으로 대체합니다. 이전 포스트 까지는 일일이 뜯어서 설명을 했는데, 시선이 분산되어 괜히 이해보다는 가독성이 떨어지는 문제를 경험했습니다. 따라서 굳이 그럴 필요가 없어 보인다고 판단하여 하나의 스니펫에 필요한 부분에 대한 설명을 주석으로 추가하는 작성 방식으로 변경하였습니다.
/* margin, padding: 브라우저의 기본 여백을 0으로 초기화합니다.
 * box-sizing: 콘텐츠 넓이를 계산할 때 여백을 모두 포함하여 넓이를 계산합니다.
*/ 
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* .ticker_container 요소를 화면의 가로세로 중앙에 정렬하기 위한 설정을 수행합니다.*/
body {
  width: 100%;
  height: 100vh;
  background: #333;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* container 는 ticker 를 감싸고 있는 노란색 박스가 되는 부분입니다.
 overflow:hidden 을 적용하여 해당 요소를 벗어난 자식 요소가 화면에 보이지 않도록 합니다.
*/
.ticker_container {
  width: 300px;
  height: 60px;
  background: goldenrod;
  padding: 1px;
  border-radius: 10px;
  display: flex;
  align-items: center;
  jsutify-content: center;
  overflow: hidden;
  white-space: nowrap; /* 텍스트 노드가 있으면 텍스트가 자동으로 개행되지 못하도록 막습니다. */

/* 실질적으로 스크롤 되는 요소 입니다.*/
  .ticker_wrapper {
    transition: 0.5s; /* 500ms 동안 트랜잭션을 수행합니다.*/
    width: 100%;
    height: 100%;
    border-radius: 10px;
    line-height: 50px;
    transform: translateY(0);/* 애니메이션의 시작 지점을 명시한 것입니다. Y 축을 기준으로 이동시킵니다*/

/* 각 텍스트에 해당하는 부분입니다. 텍스트를 표현하므로 크게 신경 쓸 것이 없습니다.*/
    .ticker {
      width: 100%;
      height: 100%;
      padding: 5px;
      color: white;
    }
  }
}

 

여기 까지 지정했다면, ticker_wrapper 의 자식요소인 각 .ticker 요소들은 높이가 58px 정도로 지정될 것입니다. 왜 높이를 언급하고 가냐면 자바스크립트에서 ticker_wrapper 요소의 위치를 Y 축으로 이동 시킬 떄, 해당 .ticker 요소의 높이 만큼 이동 시켜야 그 다음 ticker 요소가 보여지기 때문입니다. 

 

지정된 높이를 알고 싶으시면 아래와 같이 DevTools 을 여시고, '검사할 페이지 요소' 선택 도구를 클릭 후 해당 요소 위에 마우스 호버 하면 지정된 높이 값을 알 수 있습니다.

 

 

JS

자바스크립트에서 핵심이 되는 부분은 setInterval 을 통한 애니메이션 반복 재생, count 변수를 활용한 스크롤링 효과,  setTimeout  통한 위치 초기화, appendChild 를 통한 자식 요소의 위치 변경 입니다.

// 스크롤할 대상인 tickerWrapper 요소를 가져옵니다.
const tickerWrapper = document.querySelector(".ticker_wrapper");

// 스크롤 위치를 변경 시 사용할 인덱스 변수를 0으로 초기화합니다.
let count = 0;

// 4초 마다 애니메이션을 동작 시키기 위해 setInterval 을 사용합니다.
setInterval(() => {

  // 스크롤 위치 변경을 위해 인덱스를 1 증가 시킵니다.
  count++; 

  // 위치를 초기화 시킬 때 transition을 none 으로 지정하는데, 다시 애니메이션을 걸어주기 위해 설정
  tickerWrapper.style.transition = "0.5s";
  
  // 요소의 위치를 Y축으로 요소 높이*count 만큼 이동 시킵니다.
  tickerWrapper.style.transform = `translateY(${-58 * count}px)`; 

  // 두 번째 요소가 화면에 보이고, 첫 번째 요소가 화면에서 보이지 않는 동안 잠깐의 지연 시간(4000ms) 이후
  // 즉시, 애니메이션을 해제시키고, 최초 애니메이션의 시작 위치로 초기화 합니다.
  if (count === 2) {
    setTimeout(() => {
      tickerWrapper.style.transition = "none"; // 애니메이션 적용 해제
      tickerWrapper.style.transform = `translateY(0)`; // 그 즉시 위치 초기화

      const firstChild = tickerWrapper.firstElementChild; // 첫 번째 요소

      tickerWrapper.appendChild(firstChild); // 첫 번째 요소를 컨테이너 마지막 요소로 삽입

      count = 0;
    }, 0);
  }
}, 4000);

 

위 코드에서 끊임없이 이어지는 흐름을 만드는 핵심 로직은 count ===2 가 되는 지점에  setTimeout 이 호출되는 부분입니다.

 

여기서 count 는 각 요소의 높이(예시에서는 각각 height 가 58px 입니다) 만큼 ticker_wrapper 요소를 y 축으로 이동 시키는 역할을 합니다. 아래 예시 이미지를 보면 0과 1 일 때 요소의 위치를 변경시키는 것을 볼 수 있습니다.

이 때 count 가 2가 되는 경우를 살펴보면, count 1 에서 2가 되는 지점 사이에 setInterval  이 실행되는 반복 시간인 4000ms 만큼 지연시간이 존재하는 것을 볼 수 있습니다.

 

즉, count 가 1이 된 상태에서  4초가  지나 count = 2가 되면, 실행시간을 0초로 지징한  setTimeout 을 호출하여, 기존 애니메이션을 tickerWrapper.style.transition = "none";  으로 취소하게 되고,   tickerWrapper.style.transform = `translateY(0)` 을 통해 애니메이션이 시작되기 전의 위치로 요소를 이동 시킵니다.

 

이와 동시에 컨테이너의 상단에 가려진 첫 번째 자식 요소를 마지막 자식 요소 위치로 이동하게 되면, 결과적으로 무한히 스크롤 되는 착시현상이 나타나게 됩니다. 

 

구현 결과 | 시연영상

티커 요소가 몇 개이든 상관없이 무한으로 스크롤링 됩니다. 이는 count 가 2개 되는 시점에 초기화하는 연산을 반복 수행하기 때문이죠.

컨테이너에 overflow:hidden 을 적용하기 전: 요소 2개

 

컨테이너에 overflow:hidden 을 적용하기 전 : 요소 4개

 

구현 결과 | CodePen

See the Pen Untitled by youngwan2 (@youngwan2) on CodePen.

 

 

나가는 말 

오늘은 뉴스티커를 만들어보는 시간을 가져보았는데, 제가 가독성 좋게 잘 작성하였는지 모르겠습니다. 만일 부족한 부분이 있다면 조금씩 나은 방향으로 작성하기 위해 더 노력 하겠습니다.

 

부디 많은 분들에게 도움이 되는 글이길 바라며 이만 물러가 봅니다.

 

 

반응형