흐르는 주가 전광판 스타일의 무한 롤링 배너 만들기

2023-03-16 : 마우스 호버시에 배너가 멈췄다가 마우스가 배너를 벗어나면 배너가 다시 롤링이 시작되는 기능 추가.

--------------------------------------------------------------

고정된 크기를 가지는 이미지들을 전환하는 이미지 슬라이드 배너를 제외하고, 많은 웹사이트에서 가장 사용하는 배너 방식이 텍스트 롤링 배너입니다.

여러 개의 텍스트 내용 행, 또는 아이템을 가로나 세로 방향으로 무한 회전하면서 내용을 넘겨가면서 보여주는 배너입니다.

무한 롤링 배너를 만드는 방법은 CSS 키프레임 애니메이션으로 만드는 방법과, 자바스크립트로 만드는 방법으로 구분할 수 있습니다.

각각 장단점이 있기 때문에 어떤 방법이 더 낫다고 할 수는 없고, 용도에 맞춰 구현 방법을 선택하면 됩니다.

구현 난이도는 CSS 키프레임 애니메이션을 이용하는 방식이 훨씬 더 쉽습니다.

주가 전광판 스타일 롤링 배너

무한 롤링 배너 종류중 많은 아이템을 무한 회전시키는 주가 전광판 스타일의 배너를 제작해 보겠습니다.

내용 끝부분에 완성된 소스의 다운로드 링크가 있습니다.

다운로드하여 소스를 확인해가면서 내용을 읽으면 이해가 좀 더 쉽습니다.

기본 배너 데이터 확인

주가 정보나 경제 정보, 또는 여러 가지 아이템으로 된 요소들을 가로나 세로로 무한 스크롤되는 것처럼 보여야 하므로, 목록 형태로 HTML 태그를 생성하는 것이 관리면에서 좋습니다.

에제로 사용하는 데이터도 다음과 같은 목록 데이터를 가공해서 배너로 표현하게 됩니다.

배너로 사용할 목록 데이터

목록 아이템 1개는 다음과 같은 태그 구조로 되어 있습니다.

흘러가는 배너 아이템을 클릭하면 해당 정보와 관련된 웹 페이지로 이동할 수 있도록 목록 아이템 안에는 하이퍼링크 태그로 데이터 값 요소들을 감싸게 됩니다.

하이퍼링크 안의 표현 텍스트 데이터들은 한 줄로 표시할 내용들이기 때문에 크게 주의할 것은 없습니다.

<li class="kapi">
    <a href="#">
        <strong class="name">농수산물(KAPI)</strong>
        <span class="status down">
            <span class="num">172.33</span>
            <span class="rate"><em>-1.41%</em></span>
        </span>
    </a>
</li>

다만 지수나 주가가 오르거나 내리는 화살표 표시를 위해 CSS 클래스로 "status up", "status down"과 같이 구분할 수 있는 속성을 클래스로 표시하게 됩니다.

화살표 표시는 다음처럼 SVG 벡터 이미지를 이용해서 가상요소(::before)로 목록 항목에 붙입니다.

.roller > ul > li .status.up::before{
    position: absolute;
    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Arrow Forward</title><path fill="none" stroke="rgb(255,96,96)" stroke-linecap="square" stroke-miterlimit="10" stroke-width="48" d="M268 112l144 144-144 144M392 256H100"/></svg>');
    transform: rotateZ(-45deg);
    width: 18px;
    height: 18px;
    padding: 1px;
    left: 0;
}

기본 무한 롤링 배너 구조 만들기

무한 롤링 배너는 배너 내용이 표시되는 뷰포트 영역과, 뷰포트 영역 안에서 흘러가는 배너 내용 부분 2가지로 구분됩니다.

실제로는 배너 내용을 자바스크립트로 복제해서 2개의 배너 내용으로 사용하게 되지만, 어쨌든 뷰포트 영역은 뷰포트 영역 바깥에 있는 배너 내용은 보이지 않도록 해야 합니다.

따라서 뷰포트 요소에는 "overflow: hidden" 속성을 부여해서 원하지 않는 부분이 보이지 않도록 해야 합니다.

에제에서는 ".wrap" 클래스로 감싼 요소가 뷰포트 영역의 역할을 합니다.

<div class="rollingbanner"><!-- "배너 전체 선언자" -->
    <div class="wrap"><!-- "배너 뷰포트" -->
        <div class="roller"><!-- "배너 내용 래퍼" -->
            <ul><!-- "배너 데이터 리스트" -->
                <li class="kapi">
                    <a href="#">
                        <strong class="name">농수산물(KAPI)</strong>
                        <span class="status down">
                            <span class="num">172.33</span>
                            <span class="rate"><em>-1.41%</em></span>
                        </span>
                    </a>
                </li>
				...
            </ul>
        </div>
    </div>
</div>

그림으로 표현하면 다음과 같이 배너 초기 상태가 만들어집니다.

롤링 배너 기본 구조

이 배너의 배너1(배너 내용)을 자바스크립트로 복제 배너를 만들어 배너1 옆에 배치해서 롤링할 수 있는 구조를 만들게 됩니다.

애니메이션 준비가 된 상태의 배너

좌우 페이드 인 아웃 영역 만들기

이동하는 배너 양끝 부분이 부드럽게 보이고 사라지게 만들면 배너가 회전하는 듯한 느낌을 낼 수 있습니다.

약간은 부가적인 CSS 작업이지만, 배너를 아주 고급스럽고 멋져보이게 만드는 간단한 기법입니다.

배너를 표시하는 뷰포트 영역 왼쪽과 오른쪽 끝이 자연스럽게 흘러가는 배너가 페이드인/아웃이 되도록 CSS를 만들 수 있습니다.

과거에는 반투명 PNG 이미지를 좌우에 붙여서 이런 효과를 만들기도 했지만, 지금은 CSS 가상 요소만으로 구현이 가능하기 때문에 CSS로 구현합니다.

흘러가는 배너를 감싸는 요소는 ".wrap" 클래스를 가지고 있습니다.

가상 요소 ::before, "::after" 를 이용해 배너를 감싸는 요소 좌우 끝 부분에 반투명 요소를 붙여 넣습니다.

.wrap::before{
    content:'';
    position: absolute;
    top: 0;
    left: 0;
    width: 20px;
    height: 100%;
    z-index: 1;
    background: linear-gradient(to right, rgba(170,0,0,1) 0%, rgba(170,0,0,0.75) 51%, rgba(170,0,0,0) 100%);
}
.wrap::after{
    content:'';
    position: absolute;
    top: 0;
    right: 0;
    width: 20px;
    height: 100%;
    z-index: 1;
    background: linear-gradient(to right, rgba(170,0,0,0) 0%, rgba(170,0,0,0.75) 51%, rgba(170,0,0,1) 100%);
}

구현의 핵심은 "background" 속성의 선형 그라데이션 입니다.

3개의 컬러를 사용해서 그라데이션을 만들었지만, 조금 더 입체적인 효과를 내기 위한 것이고, 단순하게 구현하려면 처음과 끝의 2가지 색상만 있으면 됩니다.

예제에서는 "#a00", 또는 rgb(170,0,0) 컬러(어두운 빨강)를 사용했습니다.

이 컬러에 투명도를 부여(rgba())해서 불투명 빨강에서 완전 투명한 컬러 사이의 그라데이션을 만듭니다.

반대 방향은 컬러 표시 순서만 반대로 해주며 됩니다.

좌우에 붙여진 반투명 그라데이션

영역의 너비는 "width" 속성으로 지정합니다.

위치 속성(position)이 "absolute" 이므로 ".wrap" 클래스를 가진 부모 요소는 "position: relative" 속성을 가지고 있어야 가상 요소를 배너 뷰포트 영역 좌우에 붙일 수 있습니다.

무한 롤링 배너의 동작 방식 이해하기

무한 롤링 배너는 배너가 돌면서 무한 회전하는 듯한 느낌을 만드는 것이 핵심입니다.

배너가 한쪽 방향으로 끝까지 이동한 후 처음 시작 위치로 점프해서 다시 진행하는 배너보다는 훨씬 더 자연스러운 배너를 표현할 수 있기 때문에 할 수 있으면 무한 롤링 배너를 사용하는 것이 웹 페이지의 완성도 면에서 유리합니다.

무한 롤링 배너를 만들기 위해서는 2개의 배너가 필요합니다.

굳이 HTML 배너 태그를 2개를 생성할 필요는 없고, 자바스크립트를 이용해 복제 배너를 하나 더 만들어서 붙이면 되기 때문에 먼저 자바스크립트로 복제 배너를 하나 만들어야 합니다.

그리고 이 두 배너를 이용해 무한 회전하는 효과를 만들어냅니다.

기본적으로는 배너 길이가 뷰포트 너비보다는 길어야 자연스러운 배너 롤링이 구현됩니다.

무한 롤링 배너는 다음과 같은 순서로 동작합니다.

복제 배너는 원본 배너가 왼쪽 바깥 영역으로 완전히 이동하면서 생기는 배너 뷰포트 영역의 빈 공간을 채워 들어오는 역할을 합니다.

실제로는 같은 데이터를 가진 배너 2개가 번갈아가면서 뷰포트를 채우면서 왼쪽으로 이동하는 것이지만, 실제 동작은 하나의 배너가 무한 롤링을 하는 것처럼 보이게 됩니다.

무한 롤링 배너를 구현하는 핵심은 3번의 배너1 위치를 오른쪽 바깥 영역으로 이동하는 것입니다.

CSS 키프레임 애니메이션, 또는 자바스크립트로 롤링 배너를 구현할 때 이 부분은 별도로 예외처리를 하게 됩니다.

무한 롤링을 위한 클론 배너 생성

크게 어려운 내용은 없고 clonNode() 메서드로 복제 요소를 생성해서 appendChild() 메서드로 상위 요소의 맨 끝 자식 요소로 붙여 넣는 간단한 자바스크립트 코드입니다.

"roller1", "roller2" ID를 부여하고 "original", "clone" 클래스를 부여하는 것은 사용상의 편의를 위한 것입니다. 실제로는 둘 중 하나만 사용해서 원본 배너와 복제 배너를 식별해서 사용하는데 아무런 문제가 없습니다.

cloneNode() 메서드로 복제 요소를 생성할 때는 파라메터로 "true"를 넣어서 하위 자식 노드들까지 모두 복제가 되도록 해야 합니다.

//롤링 배너 복제본 생성
let roller = document.querySelector('.roller');
roller.id = 'roller1';

let clone = roller.cloneNode(true);
clone.id = 'roller2';
document.querySelector('.wrap').appendChild(clone); //부착

//원본, 복제본 배너 위치 지정
document.querySelector('#roller1').style.left = '0px';
document.querySelector('#roller2').style.left = document.querySelector('.roller ul').offsetWidth+'px';

//클래스 할당
roller.classList.add('original');
clone.classList.add('clone');

CSS 키프레임 애니메이션으로 무한 회전 구현하기

원본 배너와 복제해서 생성한 배너에 키프레임 애니메이션을 추가해서 무한 회전하는 애니메이션을 추가합니다.

애니메이션은 2개를 만들어야 합니다. 원본 배너와 복제 배너의 시작 위치가 다르기 때문에 애니메이션 루프의 이동 크기와 좌표가 다릅니다.

rollingleft1 은 원본 배너, rollingleft2는 복제 배너용입니다.

먼저 복제 배너용은 오른쪽 바깥의 시작 위치에서 배너 2개 너비만큼 왼쪽으로 주욱 이동하고 종료하기 가장 단순한 키프레임 이동 애니메이션이 됩니다.

이때 중요한 점이 있는데 이동 크기(translateX(-200%))를 픽셀 단위로 표기하지 않아도 됩니다.

퍼센트 값으로 배너 자신의 너비를 알 수 있기 때문에 배너 너비를 기준으로 이동 크기를 정할 때는 퍼센트 값으로 표기를 하면 됩니다.

원본 배너는 애니메이션 시작 시점에 기본으로 표시되는 배너입니다.

배너 너비만큼 왼쪽으로 이동해서 안 보이게 되면 오른쪽 바깥 안 보이는 위치로 순간 이동을 한 후 다시 배너 너비만큼 왼쪽으로 이동해서 초기 시작 위치에서 멈추게 됩니다.

중요한 키포인트는 애니메이션 중간에 왼쪽 바깥 위치에서 오른쪽 바깥 위치로 순간 이동을 하는 것입니다.
퍼센트 값으로 표시하는 애니메이션 진행 정도는 소수점 둘째 자리까지 표시할 수 있고, 소수점 2째 자리 애니메이션은 아주 짧은 순간이기 때문에 아주 아주 느린 애니메이션이 아닌 이상 순간이동을 하게 되기 때문에 보이지 않게 됩니다.
실전에서 쓰는 일종의 꼼수입니다.

50% 애니메이션 진행 시점에 배너가 가져져서 안 보이게 되는 위치만큼 왼쪽으로 이동한 후, 50.01% 시점에 오른쪽 가려져서 안 보이는 위치로 배너가 순간 이동을 하게 됩니다.

그리고 100% 시점까지 배너가 자시 왼쪽으로 배너 너비만큼 이동한 후 멈추게 됩니다.

복잡하게 구현을 하자면 애니메이션 2개를 조합해서 만들 수도 있지만, 구조가 복잡해지기 때문에 유지보수 하기가 그만큼 어려워지게 됩니다.

관리가 안 되는 코드는 그냥 쓰레기이므로 구조가 이해하기 쉽고 관리하기 편한 단일 애니메이션을 사용하는 것이 더 나은 방법입니다.

/* 애니메이션 */
.roller.original{
    animation: 33s linear 0s infinite normal forwards running rollingleft1;
}
.roller.clone{
    animation: 33s linear 0s infinite normal none running rollingleft2;
}
@keyframes rollingleft1 { /* 원본용 */
    0% {
        transform: translateX(0);
    }
    50% {
        transform: translateX(-100%);
    }
    50.01%{
        transform: translateX(100%);
    }
    100%{
        transform: translateX(0);
    }
}

@keyframes rollingleft2 { /* 클론용 */
    0% {
        transform: translateX(0);
    }
    100% {
        transform: translateX(-200%);
    }
}

자바스크립트로 무한 회전 구현하기

디자인은 앞서 만든 것을 그대로 사용합니다.

애니메이션만 키프레임 애니메이션을 대신해서 자바스크립트로 구현합니다.

먼저 CSS 코드에서 앞서 구현한 키프레임 애니메이션 부분만 삭제합니다.

그리고 다음의 자바스크립트 코드를 복제본 배너를 생성하는 자바스크립트 뒤에 추가로 작성합니다.

//인터벌 메서드로 애니메이션 생성
let rollerWidth = document.querySelector('.roller ul').offsetWidth;//회전 배너 너비값
let betweenDistance = 1;//이동 크기 - 정수여야 함
originalID = window.setInterval(betweenRollCallback, parseInt(1000/100), betweenDistance, document.querySelector('#roller1'));
cloneID = window.setInterval(betweenRollCallback, parseInt(1000/100), betweenDistance, document.querySelector('#roller2'));

//인터벌 애니메이션 함수(공용)
function betweenRollCallback(d, roller){
    let left = parseInt(roller.style.left);
    roller.style.left = (left - d)+'px';//이동
    //조건부 위치 리셋
    if(rollerWidth + (left - d) <= 0){
        roller.style.left = rollerWidth+'px';
    }
}

작성된 코드는 아주 단순합니다. 일정 시간으로 반복 실행되는 인터벌(setInterval()) 메서드 2개를 생성해서 원본과 복제본 배너 2개를 반복 이동시키는 애니메이션을 생성합니다.

이동 콜백 함수(betweenRollCallback)는 배너 객체를 파라메터(두 번째 파라메터인 roller)로 넘겨서 공용으로 사용할 수 있도록 한 개로 만듭니다.

자바스크립트 롤링 코드에서 중요한 주의사항은 딱 두 가지입니다.

> 첫 번째로 betweenDistance 변수입니다.

인터벌 메서드가 실행될 때마다 배너가 이동하는 크기를 결정합니다.

지나치게 계산적인 나머지 1초에 60번 인터벌을 호출하고 30초 동안 루프를 한번 도니까, 길이가 2000px인 배너는 한 번 이동할 때 2000 / 30 * 60 만큼씩 이동하면 된다고 생각을 하면 안 됩니다.

이동은 어차피 1px 단위인 픽셀 단위로만 이동하며, 계산된 이동 값이 소수점 단위라고 해도 결국에는 픽셀 단위 정수 값으로 표시되게 됩니다.

계산 값이 1.11111111 이 된다고 해서 1.11111111 픽셀씩 이동하는 게 아닙니다.

1픽셀씩 이동하다 어느 시점에는 반올림이 되면서 2픽셀씩 이동하는 중간 프레임이 하나씩 끼어들게 되고, 미세하게 애니메이션이 튀는 느낌적인 느낌이 들게 됩니다.

실제로 튀는 현상이 발생하고 반복적으로 애니메이션이 빨라졌다 느려지는 것 같은 현상이 발생합니다.

이 현상을 피하려면 parseInt()로 계산된 결과 값을 정수로 캐스팅을 해서 계산 결과를 명확하게 고정해야 튀는 문제가 생기지 않습니다.

그리고 실제로 사용하다 보면 1자리 정수 값 범위를 넘는 값을 사용할 일이 없기 때문에 그냥 정수로 값을 명시해서 사용하는 쪽이 더 단순하고 효율적입니다.

> 두 번째는 조건부로 위치를 리셋하는 if문입니다.

애니메이션을 2개의 배너에 적용하지만, 최종적으로는 배너가 왼쪽 바깥으로 완전히 벗어나는 시점에 오른쪽 안 보이는 같은 위치로 옮기는 처리만 하면 되기 때문에 화면 표시 영역 왼쪽 바깥으로 완전히 벗어나는지만 체크하면 됩니다.

두 배너는 이동 시작 위치는 다르지만 최종적으로는 왼쪽 바깥으로 배너 너비만큼 이동했는지만 알면 됩니다.

완성 코드 다운로드

완성된 코드를 다운로드해서 구현 방식에 따른 차이를 확인해볼 수 있습니다.

롤링 배너를 구현하는 방법은 여러가지가 있고, 그중 한 가지 방식을 구현한 것이므로 참고 삼아 보는 것을 추천합니다.

"rollingbanner-kf"는 키프레임 방식으로 구현된 것이고 "rollingbanner-js"는 자바스크립트 방식으로 구현한 것입니다.

HTML과 CSS 파일 외에 별도의 리소스는 필요 없으며, 배너에 사용한 화살표 이미지 아이콘은 SVG 이미지를 CSS안에 임베드해서 사용합니다.

rollingbanner.zip0.00MB