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:
@@ -2470,6 +2470,75 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ── 항상 보이는 스크롤 막대 ──
|
||||
// 윈도우/엣지가 오버레이(자동숨김) 스크롤바를 쓰면 네이티브 막대는 마우스를
|
||||
// 올리거나 스크롤 중일 때만 잠깐 보이고, ::-webkit-scrollbar 커스텀도
|
||||
// 무시된다(DevTools 박스모델에서 두께 0 확인). 그래서 자체 스크롤하는 패널은
|
||||
// 네이티브 막대를 CSS 로 숨기고(.pj-vbar-* 참고) 여기서 막대를 직접 그린다.
|
||||
// 휠·키보드 스크롤은 그대로 동작하고, 막대 드래그/트랙 클릭도 지원한다.
|
||||
function attachScrollIndicator(scroller) {
|
||||
if (!scroller || scroller.dataset.pjVbar) return;
|
||||
scroller.dataset.pjVbar = "1";
|
||||
const anchor = document.createElement("div");
|
||||
anchor.className = "pj-vbar-anchor";
|
||||
anchor.innerHTML = '<div class="pj-vbar-track" hidden><div class="pj-vbar-thumb"></div></div>';
|
||||
scroller.insertBefore(anchor, scroller.firstChild);
|
||||
const track = anchor.firstChild;
|
||||
const thumb = track.firstChild;
|
||||
let thumbH = 0, thumbTop = 0;
|
||||
|
||||
function update() {
|
||||
const sh = scroller.scrollHeight, ch = scroller.clientHeight;
|
||||
if (ch <= 0 || sh <= ch + 1) { if (!track.hidden) track.hidden = true; return; }
|
||||
if (track.hidden) track.hidden = false;
|
||||
thumbH = Math.max(28, Math.round(ch * ch / sh));
|
||||
thumbTop = Math.round((ch - thumbH) * scroller.scrollTop / (sh - ch));
|
||||
// 같은 값이면 안 쓴다 — MutationObserver 가 자기 변경에 반응해 도는 걸 막는다.
|
||||
const trackH = ch + "px", th = thumbH + "px", tf = "translateY(" + thumbTop + "px)";
|
||||
if (track.style.height !== trackH) track.style.height = trackH;
|
||||
if (thumb.style.height !== th) thumb.style.height = th;
|
||||
if (thumb.style.transform !== tf) thumb.style.transform = tf;
|
||||
}
|
||||
scroller.addEventListener("scroll", update, { passive: true });
|
||||
scroller.addEventListener("load", update, true); // 아바타 이미지 로드로 높이가 바뀔 때
|
||||
window.addEventListener("resize", update);
|
||||
if (window.ResizeObserver) new ResizeObserver(update).observe(scroller);
|
||||
if (window.MutationObserver) {
|
||||
new MutationObserver(function (records) {
|
||||
for (let i = 0; i < records.length; i++) {
|
||||
if (!anchor.contains(records[i].target)) { update(); return; }
|
||||
}
|
||||
}).observe(scroller, { childList: true, subtree: true, attributes: true, characterData: true });
|
||||
}
|
||||
|
||||
let dragging = false, startY = 0, startTop = 0;
|
||||
thumb.addEventListener("mousedown", function (e) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
dragging = true; startY = e.clientY; startTop = scroller.scrollTop;
|
||||
thumb.classList.add("is-dragging");
|
||||
});
|
||||
document.addEventListener("mousemove", function (e) {
|
||||
if (!dragging) return;
|
||||
const sh = scroller.scrollHeight, ch = scroller.clientHeight, room = ch - thumbH;
|
||||
if (room <= 0) return;
|
||||
scroller.scrollTop = startTop + (e.clientY - startY) * (sh - ch) / room;
|
||||
});
|
||||
document.addEventListener("mouseup", function () {
|
||||
if (dragging) { dragging = false; thumb.classList.remove("is-dragging"); }
|
||||
});
|
||||
track.addEventListener("mousedown", function (e) {
|
||||
if (e.target !== track) return;
|
||||
e.preventDefault();
|
||||
const y = e.clientY - track.getBoundingClientRect().top;
|
||||
scroller.scrollTop += (y < thumbTop ? -1 : 1) * scroller.clientHeight * 0.9;
|
||||
});
|
||||
update();
|
||||
}
|
||||
function initScrollIndicators() {
|
||||
document.querySelectorAll(".pj-home-side, .pj-home-main, .pj-side, .pj-board")
|
||||
.forEach(attachScrollIndicator);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
initHome();
|
||||
initProject();
|
||||
@@ -2477,5 +2546,6 @@
|
||||
initInbox();
|
||||
initLiveRefresh();
|
||||
wireMyTreeToggles();
|
||||
initScrollIndicators();
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user