fix(project): 자체 스크롤 패널에 항상 보이는 스크롤 막대를 JS로 직접 그림

DevTools 박스모델 확인 결과 내 업무 패널은 정상 스크롤 컨테이너였지만
스크롤바 두께가 0 — 이 환경(엣지 오버레이 스크롤바)에선 ::-webkit-scrollbar
커스텀도 scrollbar-width 표준 속성도 모두 무시돼 CSS 로는 막대를 항상
보이게 할 방법이 없었다. 네이티브 막대는 숨기고 attachScrollIndicator()
가 내 업무/좌측 프로젝트 목록/홈 프로젝트 목록/보드 패널 안에 스크롤
위치를 따라 움직이는 막대를 그린다(sticky anchor + 절대배치 track/thumb,
드래그·트랙 클릭 지원, MutationObserver/ResizeObserver 로 갱신). 막대
자리로 각 패널 padding-right 14px 확보, 보드의 scrollbar-gutter 제거.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 23:47:05 +09:00
parent 17731a195a
commit 41b6165144
5 changed files with 102 additions and 38 deletions
+26 -32
View File
@@ -53,7 +53,7 @@
display: grid; grid-template-columns: minmax(0, 1fr) 300px; grid-template-rows: minmax(0, 1fr);
gap: 28px; align-items: stretch;
}
.pj-home-main { min-width: 0; min-height: 0; height: 100%; overflow-y: auto; padding-right: 4px; }
.pj-home-main { min-width: 0; min-height: 0; height: 100%; overflow-y: auto; padding-right: 14px; }
@media (max-width: 980px) {
.erp-page > .pj-wrap:not(.pj-project-view) { display: block; }
.pj-home { display: block; }
@@ -133,7 +133,7 @@
확정하고 자기 안에서만 스크롤. 300px 는 padding 포함(border-box). */
.pj-home-side {
width: 300px; box-sizing: border-box; min-height: 0; height: 100%; overflow-y: auto;
border-left: 1px solid #eef0f2; padding-left: 24px; padding-right: 4px;
border-left: 1px solid #eef0f2; padding-left: 24px; padding-right: 14px;
}
@media (max-width: 980px) { .pj-home-side { border-left: none; padding-left: 0; height: auto; overflow: visible; } }
.pj-mytask-list { list-style: none; margin: 0; padding: 0; }
@@ -162,33 +162,28 @@
display: grid; grid-template-columns: 230px 1fr 300px; grid-template-rows: minmax(0, 1fr);
gap: 22px; height: 100%; min-height: 0; align-items: stretch;
}
.pj-side { min-width: 0; min-height: 0; height: 100%; overflow-y: auto; padding-right: 4px; }
.pj-side { min-width: 0; min-height: 0; height: 100%; overflow-y: auto; padding-right: 14px; }
.pj-main { min-width: 0; min-height: 0; height: 100%; display: flex; flex-direction: column; }
.pj-project-mytasks { min-width: 0; min-height: 0; width: 300px; height: 100%; overflow-y: auto; padding-right: 4px; }
.pj-project-mytasks { min-width: 0; min-height: 0; width: 300px; height: 100%; overflow-y: auto; padding-right: 14px; }
/* 휠 스크롤은 되는데 막대가 안 보인다 — OS/브라우저의 오버레이(자동숨김)
스크롤바 때문(호버해야만 잠깐 보이는 스타일). 여기 자체 스크롤 패널들은
막대가 항상 보이도록 커스텀 스크롤바를 그린다(윈도우 설정과 무관하게
고정 표시). */
/* 주의: Chromium(크롬/엣지) 121+ 는 scrollbar-width/scrollbar-color 지정돼
있으면 ::-webkit-scrollbar 커스텀 스타일을 통째로 무시하고 OS 오버레이
(자동숨김) 스크롤바로 돌아간다 — 그래서 표준 속성은 ::-webkit-scrollbar 를
지원하지 않는 브라우저(Firefox)에만 준다. */
@supports not selector(::-webkit-scrollbar) {
.pj-home-side, .pj-home-main, .pj-project-mytasks, .pj-side, .pj-board {
scrollbar-width: thin; scrollbar-color: #b9bcc4 #f1f2f4;
}
}
.pj-home-side::-webkit-scrollbar, .pj-home-main::-webkit-scrollbar, .pj-project-mytasks::-webkit-scrollbar,
.pj-side::-webkit-scrollbar, .pj-board::-webkit-scrollbar { width: 9px; height: 9px; }
.pj-home-side::-webkit-scrollbar-track, .pj-home-main::-webkit-scrollbar-track, .pj-project-mytasks::-webkit-scrollbar-track,
.pj-side::-webkit-scrollbar-track, .pj-board::-webkit-scrollbar-track { background: #f1f2f4; border-radius: 999px; }
.pj-home-side::-webkit-scrollbar-thumb, .pj-home-main::-webkit-scrollbar-thumb, .pj-project-mytasks::-webkit-scrollbar-thumb,
.pj-side::-webkit-scrollbar-thumb, .pj-board::-webkit-scrollbar-thumb {
background-color: #b9bcc4; border-radius: 999px;
}
.pj-home-side::-webkit-scrollbar-thumb:hover, .pj-home-main::-webkit-scrollbar-thumb:hover, .pj-project-mytasks::-webkit-scrollbar-thumb:hover,
.pj-side::-webkit-scrollbar-thumb:hover, .pj-board::-webkit-scrollbar-thumb:hover { background-color: #979ba5; }
/* ── 자체 스크롤 패널(내 업무/좌측 목록/홈 프로젝트 목록/보드)의 스크롤 막대 ──
윈도우/엣지가 오버레이(자동숨김) 스크롤바를 쓰는 환경에선 네이티브 막대가
마우스를 올리거나 스크롤 중일 때만 잠깐 보이고, ::-webkit-scrollbar /
scrollbar-width 커스텀도 무시된다(DevTools 박스모델에서 두께 0 확인).
그래서 네이티브 막대는 아예 숨기고, JS(attachScrollIndicator)패널 안에
스크롤 위치에 맞춰 움직이는 막대를 직접 그려 어떤 환경에서도 항상
보이게 한다. 휠·키보드·드래그 스크롤은 그대로 동작. 막대가 들어갈 자리로
각 패널의 padding-right 를 14px 확보한다. */
.pj-home-side, .pj-home-main, .pj-side, .pj-board { scrollbar-width: none; }
.pj-home-side::-webkit-scrollbar, .pj-home-main::-webkit-scrollbar,
.pj-side::-webkit-scrollbar, .pj-board::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* anchor = 스크롤러의 첫 자식(높이 0, sticky) → 화면에 보이는 영역의 맨 위에
항상 붙어 있고, 그 안의 track 이 보이는 높이만큼 오른쪽 padding 자리에 걸린다. */
.pj-vbar-anchor { position: sticky; top: 0; height: 0; z-index: 5; }
.pj-vbar-track { position: absolute; top: 0; right: -11px; width: 8px; border-radius: 999px; background: #e6e8ec; }
.pj-vbar-thumb { position: absolute; left: 0; top: 0; width: 100%; border-radius: 999px; background: #b3b7bf; cursor: grab; }
.pj-vbar-thumb:hover, .pj-vbar-thumb.is-dragging { background: #8f949e; }
.pj-vbar-thumb.is-dragging { cursor: grabbing; }
@media (max-width: 1280px) {
.pj-layout { grid-template-columns: 230px 1fr; }
.pj-project-mytasks { display: none; }
@@ -276,13 +271,12 @@
높이를 정해준다). pj-board-canvas = 매이슨리 절대배치 캔버스 — 스크롤
컨테이너 자체 높이와 콘텐츠(캔버스) 높이 계산을 분리해야 스크롤이
올바르게 생긴다(캔버스만 커지고 뷰포트는 flex 크기를 유지). */
/* scrollbar-gutter:stable — 스크롤바 자리를 항상 미리 비워둔다. 이게 없으면
렌더 시점에 스크롤바가 아직 안 생긴 상태로 clientWidth를 재서 열 너비를
계산했다가, 막상 스크롤바가 생기면 그만큼 폭이 줄어 마지막 열이 스크롤바
에 가려 보였다. */
/* 네이티브 스크롤바는 숨기고 JS 가 그리는 막대를 쓴다(위 .pj-vbar-* 참고).
막대 자리는 padding-right 14px — 매이슨리 열 너비는 canvas(=padding 안쪽)
기준으로 계산되므로 열이 막대에 가려지지 않는다. */
.pj-board {
flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
scrollbar-gutter: stable; padding-right: 4px; padding-bottom: 8px;
padding-right: 14px; padding-bottom: 8px;
}
.pj-board-canvas { position: relative; }
/* 세션(보드 컬럼) — 배경이 페이지와 거의 같으면 구분이 안 되므로(#f6f5fb ≈