반응형

🎞️ 롤링 슬라이드

주식이나 뉴스같은 곳에서 한줄로 끊임없이 텍스트가 물처럼 한방향으로 흐르거나 요즘 트렌드의 스타일로 작성된 사이트들을 구경하다 보면 자주 접할 수 있는 스타일의 UI이다.
텍스트뿐만 아니라 관련된 이미지를 통해서도 좀 더 인터렉티브하고 동적으로 움직이다보니 시각적으로 집중이 되는 효과를 확인 할 수 있다.
대표적으로 찾은 기능으로 네이버의 vibe사이트에서 추천 플레이리스트 부분에 아티스트들의 앨범 정보가 끊임없이 흐르는 애니메이션도 볼 수 있는데, 해당 기능을 참고하여 구현하고자 한다.


바이브 url: https://vibe.naver.com/about/.

🤔 동작 방식

구현하기 전 구현에 대한 설명이 필요하다.

  1. 먼저 슬라이드 형태로 흘러가야할 요소들을 한줄로 세워준다.
  2. 똑같은 요소들을 하나 더 만들어준다.
  1. css 속성을 통해 한줄로 이어서 붙여준다. (부모속성에 flex와 nowrap처리를 통해 쉽게 구현이 가능하다.)
  1. 한줄이 된 슬라이드를 각기 다른 옵션으로 애니메이트를 처리한다.
  2. 첫번째 원본 영역을 10초간 이동하면 그동안 빈 공백의 10초를 기다려야 하는데, 이때 복사 영역으로 똑같이 붙여서 10초간 이동시키고 무한으로 돌린다.
  3. 슬라이드 영역에 마우스를 올리면 animation-play-state의 값을 paused처리 하는 클래스를 토글 형태로 처리하면 된다.
    .stop{
     animation-play-state: paused;
    }

⚙️ 구현에 필요한 기술

css 레이아웃 개념과 animation의 개념, @keyframe 사용법 익히면 별다른 라이브러리 없이 구현이 가능하다.
hook또한 useState하나만 사용하여 처리 할 예정이다.
아래 구현 예시를 확인해보자.

🪄 구현 시작

HomePage.tsx

import { useState } from "react";
import "./Homepage.scss";

const slides = [
    { color: "red", target: "#" },
    { color: "orange", target: "#" },
    { color: "yellow", target: "#" },
    { color: "green", target: "#" },
    { color: "blue", target: "#" },
    { color: "navy", target: "#" },
    { color: "purple", target: "#" },
];

export default function Homepage() {
    const [animate, setAnimate] = useState(true);
    const onStop = () => setAnimate(false);
    const onRun = () => setAnimate(true);

    return (
        <div className="wrapper">
            <div className="slide_container">
                <ul
                    className="slide_wrapper"
                    onMouseEnter={onStop}
                    onMouseLeave={onRun}
                >
                    <div
                        className={"slide original".concat(
                            animate ? "" : " stop"
                        )}
                    >
                        {slides.map((s, i) => (
                            <li
                                key={i}
                                className={i % 2 === 0 ? "big" : "small"}
                            >
                                <div
                                    className="item"
                                    style={{ background: s.color }}
                                ></div>
                            </li>
                        ))}
                    </div>
                    <div
                        className={"slide clone".concat(animate ? "" : " stop")}
                    >
                        {slides.map((s, i) => (
                            <li
                                key={i}
                                className={i % 2 === 0 ? "big" : "small"}
                            >
                                <div
                                    className="item"
                                    style={{ background: s.color }}
                                ></div>
                            </li>
                        ))}
                    </div>
                </ul>
            </div>
        </div>
    );
}

Homepage.scss

* {
    padding: 0;
    margin: 0;
}
ul,
li {
    list-style: none;
}

.wrapper {
    .slide_container {
        overflow: hidden;

        .slide_wrapper {
            display: flex;
            flex-wrap: nowrap;
        }
        .slide {
            display: flex;
            align-items: center;
            flex-wrap: nowrap;
            position: relative;
            border-top: 1px solid #bbb;
            border-bottom: 1px solid #bbb;
            padding: 40px 0;

            &::before {
                content: "";
                display: block;
                width: 100%;
                height: 1px;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background-color: #bbb;
                z-index: 1;
            }
            &.original {
                animation: 10s linear infinite normal none running
                    infiniteAnimation1;
            }
            &.clone {
                animation: 10s linear infinite infiniteAnimation2;
            }
            &.stop {
                animation-play-state: paused;
            }

            li {
                margin: 0 80px;
                cursor: pointer;
                z-index: 2;
                transition: 0.3s;
                transform: scale(1);
                &:hover {
                    transform: scale(0.98);
                    &::after {
                        content: "";
                        position: absolute;
                        top: 0;
                        left: 0;
                        bottom: 0;
                        right: 0;
                        width: 100%;
                        height: 100%;
                        background-color: rgba(0, 0, 0, 0.2);
                    }
                }
                &.big {
                    width: 280px;
                    height: 280px;
                }
                &.small {
                    width: 200px;
                    height: 200px;
                }

                .item {
                    width: 100%;
                    height: 100%;
                }
            }
        }
    }
}

@keyframes infiniteAnimation1 {
    0% {
        transform: translateX(0%);
    }
    50% {
        transform: translateX(-100%);
    }
    50.1% {
        transform: translateX(100%);
    }
    100% {
        transform: translateX(0%);
    }
}
@keyframes infiniteAnimation2 {
    0% {
        transform: translateX(0%);
    }
    100% {
        transform: translateX(-200%);
    }
}

✨ 구현 결과물

아주 잘된다.
바이브처럼 뒷부분에 선도 그어봤다.
괜찮은 배경이미지와 색상 대신 이미지를 활용하면 더욱 좋은 결과물을 볼 수 있을 것 같다.

(아래는 위 소스를 기반으로 작성한 리액트 샘플 소스를 받아볼 수 있는 github주소입니다.)

github: [https://github.com/myhappyman/infinite_rolling]

 

반응형