스크롤 바 커스터마이징

✒️ 2025-05-16 12:36 내용 수정


참고 자료 : Digital Ocean How to Customize Scrollbars with CSS: Styling Guide with Examples, mdn web docs scrollbar

<div id="main-content">
	<p>내용 테스트. 긴 글로 테스트하는 것을 추천합니다</p>
</div>
#main-content { overflow-x: scroll; }

css_scrollbar_info.png

속성 설명
scroll-behavior 네비게이션이나 CSSOM 스크롤링 API 사용 시 스크롤방식을 지정
auto : 기본으로 지정된 위치로 바로 이동한다.
smooth : 스크롤이 부드럽게 이동한다.
scroll-padding,
scroll-margin
스크롤 요소들 사이에 시각적으로 보이는 영역의 오프셋 설정
각각 다른 요소들의 paddingmargin 속성 같은 역할
0 : 스크롤이 내려가면 타겟 요소가 화면에 꽉 참
0 이상 : 스크롤이 내려가면 이전 요소가 화면에 일부 보임
scrollbar-gutter 스크롤바의 gutter 설정
내부 경계와 외부 padding 사이의 공간 지정
scrollbar-color 스크롤바의 track과 thumb 색상 지정
:-webkit-scrollbar를 지원하지 않을 경우에도 사용 가능
scrollbar-width
scrollbar-height
스크롤바 두께 지정
:-webkit-scrollbar를 지원하지 않을 경우에도 사용 가능
@media (width < 472px) {
    #main-content { 
        overflow-x: scroll;
        scroll-behavior: smooth;
        scrollbar-color: #AAAAAA transparent;
        scroll-padding: 1px;
    }
}

css_scrollbar.png


커스텀 스크롤 메뉴 버튼

css_scrollbar_custom1.png
css_scrollbar_custom2.png
css_scrollbar_custom3.png

<nav id="channel-nav">

	<!-- 왼쪽 버튼 영역 -->
	<div id="left-btn-wrap" class="channel-nav-btn-wrap">
		<button id="ch-nav-left-btn">
			<svg></svg>
		</button>
	</div>

	<!-- 메뉴 탭 -->
	<div id="channel-nav-str">
		<ul id="channel-nav-list">
			<li class="select">HOME</li>
			<li>VIDEOS</li>
			<li>PLAYLISTS</li>
			<li>COMMUNITY</li>
			<li>CHANNELS</li>
			<li>ABOUT</li>
		</ul>
		<button type="submit" id="search-submit-btn">
			<img src="../images/search.svg" alt="검색돋보기이미지">
		</button>
	</div>
	
	<!-- 오른쪽 버튼 영역 -->
	<div id="right-btn-wrap" class="channel-nav-btn-wrap">
		<button id="ch-nav-right-btn">
			<svg></svg>
		</button>
	</div>
	
</nav>
#channel-nav-str {
    display: flex;
    scroll-behavior: smooth;
    overflow-x: auto;
}

스크롤 끝에 도달 시 버튼 숨기기

document.addEventListener("DOMContentLoaded", function () {})
속성 설명
scrollLeft 현재 스크롤의 위치
왼쪽으로부터 떨어진 픽셀 수
scrollWidth 스크롤 가능한 요소의 전체 길이
보이지 않는 부분도 포함
clientWidth 요소의 내부 폭 길이
inline 요소는 0을 반환
실제 보이는 스크롤 길이
const nav_container = document.getElementById("channel-nav-str");
const left_btn_wrap = document.getElementById("left-btn-wrap");
const right_btn_wrap = document.getElementById("right-btn-wrap");
const left_btn = document.getElementById("ch-nav-left-btn");
const right_btn = document.getElementById("ch-nav-right-btn");

// 버튼 표시 설정
function update_btn_visibility() {
	const scroll_left = nav_container.scrollLeft;
	const max_scroll_left = nav_container.scrollWidth - nav_container.clientWidth;

	// 화면이 클 때는 버튼이 안보이게 설정
	if (nav_container.scrollWidth <= nav_container.clientWidth) {
		left_btn_wrap.style.display = "none";
		right_btn_wrap.style.display = "none";
		return;
	}

	// 맨 왼쪽이면 left 버튼 숨김
	left_btn_wrap.style.display = scroll_left <= 0 ? "none" : "flex";

	// 맨 오른쪽이면 right 버튼을 숨김
	right_btn_wrap.style.display = scroll_left >= max_scroll_left ? "none" : "flex";
}

css_scrollbar_custom.gif

버튼을 누르는 동안 스크롤 이동 처리

// 스크롤 여부
let is_scrolling = false;
// 스크롤 방향
let scroll_direction = 0;
// 마지막 스크롤 위치
let last_timestamp = null;
const scroll_speed = 50; // px per ms

// 스크롤 이동 설정
function step(timestamp) {
	// 스크롤 중지
	if (!is_scrolling) return;
	// 스크롤 진행
	if (last_timestamp !== null) {
		// 마지막 위치와 이동할 위치의 차이
		const delta = timestamp - last_timestamp;
		// 특정 방향으로 스크롤을 이동시킴
		nav_container.scrollBy({ left: scroll_direction * scroll_speed * delta });
	}
	// 마지막 스크롤 위치 업데이트
	last_timestamp = timestamp;
	requestAnimationFrame(step);
}

// 스크롤 중지
function stop_scrolling() {
	is_scrolling = false;
}

// 버튼 꾹 누르면 스크롤 이동 효과
function start_scrolling(direction) {
	is_scrolling = true;
	scroll_direction = direction;
	requestAnimationFrame(step);
	nav_container.scrollBy({left: direction * scroll_speed});
}

// 초기 설정 진행
update_btn_visibility();

// 버튼 눌렀을 때 반응
left_btn.addEventListener("mousedown", () => start_scrolling(-1));
right_btn.addEventListener("mousedown", () => start_scrolling(1));

// 마우스를 떼거나 멈췄을 때
left_btn.addEventListener("mouseup", stop_scrolling);
left_btn.addEventListener("mouseleave", stop_scrolling);
right_btn.addEventListener("mouseup", stop_scrolling);
right_btn.addEventListener("mouseleave", stop_scrolling);

// 스크롤에 반응한 버튼 표시 처리
nav_container.addEventListener("scroll", update_btn_visibility);
// 화면 크기가 변동되면 버튼 표시 처리
window.addEventListener("resize", update_btn_visibility);

// DOM이 로드된 후 지연된 layout 계산을 위해 requestAnimationFrame 사용
requestAnimationFrame(update_btn_visibility);

css_scrollbar_custom2.gif

전체 코드

// 메뉴바 이동
document.addEventListener("DOMContentLoaded", function () {
    const nav_container = document.getElementById("channel-nav-str");
    const left_btn_wrap = document.getElementById("left-btn-wrap");
    const right_btn_wrap = document.getElementById("right-btn-wrap");
    const left_btn = document.getElementById("ch-nav-left-btn");
    const right_btn = document.getElementById("ch-nav-right-btn");

    // 버튼 표시 설정
    function update_btn_visibility() {
        const scroll_left = nav_container.scrollLeft;
        const max_scroll_left = nav_container.scrollWidth - nav_container.clientWidth;

        // 화면이 클 때는 버튼이 안보이게 설정
        if (nav_container.scrollWidth <= nav_container.clientWidth) {
            left_btn_wrap.style.display = "none";
            right_btn_wrap.style.display = "none";
            return;
        }

        // 맨 왼쪽이면 left 버튼 숨김
        left_btn_wrap.style.display = scroll_left <= 0 ? "none" : "flex";

        // 맨 오른쪽이면 right 버튼을 숨김
        right_btn_wrap.style.display = scroll_left >= max_scroll_left ? "none" : "flex";
    }

    let is_scrolling = false;
    let scroll_direction = 0;
    let last_timestamp = null;
    const scroll_speed = 50; // px per ms

    function step(timestamp) {
        if (!is_scrolling) return;
        if (last_timestamp !== null) {
            const delta = timestamp - last_timestamp;
            nav_container.scrollBy({ left: scroll_direction * scroll_speed * delta });
        }
        last_timestamp = timestamp;
        requestAnimationFrame(step);
    }

    // 스크롤 중지
    function stop_scrolling() {
        is_scrolling = false;
    }

    // 버튼 꾹 누르면 스크롤 이동 효과
    function start_scrolling(direction) {
        is_scrolling = true;
        scroll_direction = direction;
        requestAnimationFrame(step);
        nav_container.scrollBy({left: direction * scroll_speed});
    }

    // 초기 설정 진행
    update_btn_visibility();

    // 버튼 눌렀을 때 반응
    left_btn.addEventListener("mousedown", () => start_scrolling(-1));
    right_btn.addEventListener("mousedown", () => start_scrolling(1));

    // 마우스를 떼거나 멈췄을 때
    left_btn.addEventListener("mouseup", stop_scrolling);
    left_btn.addEventListener("mouseleave", stop_scrolling);
    right_btn.addEventListener("mouseup", stop_scrolling);
    right_btn.addEventListener("mouseleave", stop_scrolling);

    // 스크롤에 반응한 버튼 표시 처리
    nav_container.addEventListener("scroll", update_btn_visibility);
    window.addEventListener("resize", update_btn_visibility);

    // DOM이 로드된 후 지연된 layout 계산을 위해 requestAnimationFrame
    requestAnimationFrame(update_btn_visibility);
});