이미지 목록 화면 오른쪽에 스크롤바 추가
This commit is contained in:
@@ -7,6 +7,8 @@ import { buildSequentialName, getNameFormat } from './utils/file-names.js';
|
|||||||
const grid = document.getElementById('grid');
|
const grid = document.getElementById('grid');
|
||||||
const emptyState = document.getElementById('empty-state');
|
const emptyState = document.getElementById('empty-state');
|
||||||
const gridContainer = document.getElementById('grid-container');
|
const gridContainer = document.getElementById('grid-container');
|
||||||
|
const gridScrollbar = document.getElementById('grid-scrollbar');
|
||||||
|
const gridScrollbarThumb = document.getElementById('grid-scrollbar-thumb');
|
||||||
const fileCount = document.getElementById('file-count');
|
const fileCount = document.getElementById('file-count');
|
||||||
const selCount = document.getElementById('sel-count');
|
const selCount = document.getElementById('sel-count');
|
||||||
const inputPrefix = document.getElementById('input-prefix');
|
const inputPrefix = document.getElementById('input-prefix');
|
||||||
@@ -14,6 +16,103 @@ const inputDigits = document.getElementById('input-digits');
|
|||||||
const inputPostfix = document.getElementById('input-postfix');
|
const inputPostfix = document.getElementById('input-postfix');
|
||||||
|
|
||||||
let _cardObserver = null;
|
let _cardObserver = null;
|
||||||
|
let _scrollStateRaf = null;
|
||||||
|
let _scrollbarDrag = null;
|
||||||
|
|
||||||
|
function updateGridScrollState() {
|
||||||
|
if (_scrollStateRaf) cancelAnimationFrame(_scrollStateRaf);
|
||||||
|
_scrollStateRaf = requestAnimationFrame(() => {
|
||||||
|
_scrollStateRaf = null;
|
||||||
|
const isScrollable = gridContainer.scrollHeight > gridContainer.clientHeight;
|
||||||
|
gridContainer.classList.toggle('is-scrollable', isScrollable);
|
||||||
|
updateGridScrollbarFrame();
|
||||||
|
updateGridScrollbar();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGridScrollbarFrame() {
|
||||||
|
if (!gridScrollbar) return;
|
||||||
|
|
||||||
|
const rect = gridContainer.getBoundingClientRect();
|
||||||
|
const top = rect.top + 20;
|
||||||
|
const height = Math.max(0, rect.height - 40);
|
||||||
|
|
||||||
|
gridScrollbar.style.setProperty('--grid-scrollbar-top', `${top}px`);
|
||||||
|
gridScrollbar.style.setProperty('--grid-scrollbar-height', `${height}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGridScrollbar() {
|
||||||
|
if (!gridScrollbar || !gridScrollbarThumb) return;
|
||||||
|
|
||||||
|
const { clientHeight, scrollHeight, scrollTop } = gridContainer;
|
||||||
|
const maxScrollTop = scrollHeight - clientHeight;
|
||||||
|
if (maxScrollTop <= 0) {
|
||||||
|
gridScrollbarThumb.style.height = '';
|
||||||
|
gridScrollbarThumb.style.transform = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackHeight = gridScrollbar.clientHeight;
|
||||||
|
const thumbHeight = Math.max(36, Math.round((clientHeight / scrollHeight) * trackHeight));
|
||||||
|
const maxThumbTop = trackHeight - thumbHeight;
|
||||||
|
const thumbTop = Math.round((scrollTop / maxScrollTop) * maxThumbTop);
|
||||||
|
|
||||||
|
gridScrollbarThumb.style.height = `${thumbHeight}px`;
|
||||||
|
gridScrollbarThumb.style.transform = `translateY(${thumbTop}px)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollGridFromPointer(clientY) {
|
||||||
|
const trackRect = gridScrollbar.getBoundingClientRect();
|
||||||
|
const thumbHeight = gridScrollbarThumb.offsetHeight;
|
||||||
|
const maxThumbTop = gridScrollbar.clientHeight - thumbHeight;
|
||||||
|
const maxScrollTop = gridContainer.scrollHeight - gridContainer.clientHeight;
|
||||||
|
if (maxThumbTop <= 0 || maxScrollTop <= 0) return;
|
||||||
|
|
||||||
|
const targetThumbTop = Math.min(
|
||||||
|
maxThumbTop,
|
||||||
|
Math.max(0, clientY - trackRect.top - _scrollbarDrag.thumbOffset),
|
||||||
|
);
|
||||||
|
gridContainer.scrollTop = (targetThumbTop / maxThumbTop) * maxScrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _gridResizeObserver = new ResizeObserver(updateGridScrollState);
|
||||||
|
_gridResizeObserver.observe(gridContainer);
|
||||||
|
_gridResizeObserver.observe(grid);
|
||||||
|
|
||||||
|
gridContainer.addEventListener('scroll', updateGridScrollbar);
|
||||||
|
window.addEventListener('resize', updateGridScrollState);
|
||||||
|
|
||||||
|
gridScrollbar.addEventListener('pointerdown', (e) => {
|
||||||
|
if (!gridContainer.classList.contains('is-scrollable')) return;
|
||||||
|
|
||||||
|
const thumbRect = gridScrollbarThumb.getBoundingClientRect();
|
||||||
|
const isThumbPointer = e.target === gridScrollbarThumb;
|
||||||
|
_scrollbarDrag = {
|
||||||
|
thumbOffset: isThumbPointer ? e.clientY - thumbRect.top : thumbRect.height / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
gridScrollbar.classList.add('dragging');
|
||||||
|
gridScrollbar.setPointerCapture(e.pointerId);
|
||||||
|
scrollGridFromPointer(e.clientY);
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
gridScrollbar.addEventListener('pointermove', (e) => {
|
||||||
|
if (!_scrollbarDrag) return;
|
||||||
|
scrollGridFromPointer(e.clientY);
|
||||||
|
});
|
||||||
|
|
||||||
|
gridScrollbar.addEventListener('pointerup', (e) => {
|
||||||
|
_scrollbarDrag = null;
|
||||||
|
gridScrollbar.classList.remove('dragging');
|
||||||
|
gridScrollbar.releasePointerCapture(e.pointerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
gridScrollbar.addEventListener('pointercancel', (e) => {
|
||||||
|
_scrollbarDrag = null;
|
||||||
|
gridScrollbar.classList.remove('dragging');
|
||||||
|
gridScrollbar.releasePointerCapture(e.pointerId);
|
||||||
|
});
|
||||||
|
|
||||||
export function renderGrid() {
|
export function renderGrid() {
|
||||||
if (_cardObserver) { _cardObserver.disconnect(); _cardObserver = null; }
|
if (_cardObserver) { _cardObserver.disconnect(); _cardObserver = null; }
|
||||||
@@ -44,6 +143,7 @@ export function renderGrid() {
|
|||||||
refreshCardSelection();
|
refreshCardSelection();
|
||||||
updateNumberBadges();
|
updateNumberBadges();
|
||||||
updateNewNameLabels();
|
updateNewNameLabels();
|
||||||
|
updateGridScrollState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCard(file, idx) {
|
export function createCard(file, idx) {
|
||||||
@@ -138,4 +238,5 @@ export function showEmptyGrid() {
|
|||||||
emptyState.style.display = '';
|
emptyState.style.display = '';
|
||||||
fileCount.textContent = '0개 파일';
|
fileCount.textContent = '0개 파일';
|
||||||
updateSelCount();
|
updateSelCount();
|
||||||
|
updateGridScrollState();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,9 @@
|
|||||||
<span class="empty-state-hint">JPG · PNG · GIF · BMP · TIFF · WEBP · HEIC</span></p>
|
<span class="empty-state-hint">JPG · PNG · GIF · BMP · TIFF · WEBP · HEIC</span></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="grid" style="display:none;"></div>
|
<div id="grid" style="display:none;"></div>
|
||||||
|
<div id="grid-scrollbar" aria-hidden="true">
|
||||||
|
<div id="grid-scrollbar-thumb"></div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
+59
-6
@@ -24,6 +24,7 @@
|
|||||||
--sel-bg: rgba(124,106,247,0.22);
|
--sel-bg: rgba(124,106,247,0.22);
|
||||||
--sel-border: #7c6af7;
|
--sel-border: #7c6af7;
|
||||||
--thumb-size: 250px;
|
--thumb-size: 250px;
|
||||||
|
--grid-scroll-track: #282740;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
@@ -234,18 +235,70 @@ main {
|
|||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: var(--border) transparent;
|
scrollbar-color: var(--border) transparent;
|
||||||
}
|
}
|
||||||
.file-explorer::-webkit-scrollbar,
|
.file-explorer::-webkit-scrollbar { width: 6px; }
|
||||||
#grid-container::-webkit-scrollbar { width: 6px; }
|
#grid-container { scrollbar-width: none; }
|
||||||
|
#grid-container::-webkit-scrollbar { width: 0; height: 0; }
|
||||||
.file-explorer::-webkit-scrollbar-track,
|
.file-explorer::-webkit-scrollbar-track,
|
||||||
#grid-container::-webkit-scrollbar-track { background: transparent; }
|
.file-explorer::-webkit-scrollbar-track { background: transparent; }
|
||||||
.file-explorer::-webkit-scrollbar-thumb,
|
.file-explorer::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||||||
#grid-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
|
||||||
|
|
||||||
/* ── 그리드 컨테이너 ── */
|
/* ── 그리드 컨테이너 ── */
|
||||||
#grid-container {
|
#grid-container {
|
||||||
flex: 1; overflow-y: auto; padding: 20px;
|
flex: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
#grid-container.is-scrollable {
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
#grid-scrollbar {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--grid-scrollbar-top, 0);
|
||||||
|
right: 0;
|
||||||
|
height: var(--grid-scrollbar-height, 0);
|
||||||
|
z-index: 25;
|
||||||
|
display: none;
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#grid-container.is-scrollable #grid-scrollbar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#grid-scrollbar:hover,
|
||||||
|
#grid-scrollbar.dragging {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
#grid-scrollbar::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 0 0 auto;
|
||||||
|
width: 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--grid-scroll-track);
|
||||||
|
transition: width .16s ease;
|
||||||
|
}
|
||||||
|
#grid-scrollbar:hover::before,
|
||||||
|
#grid-scrollbar.dragging::before {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
#grid-scrollbar-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 6px;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--accent);
|
||||||
|
transition: width .16s ease, background .16s ease;
|
||||||
|
}
|
||||||
|
#grid-scrollbar:hover #grid-scrollbar-thumb,
|
||||||
|
#grid-scrollbar.dragging #grid-scrollbar-thumb {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── 그리드: 고정 열 폭 (--thumb-size) ── */
|
/* ── 그리드: 고정 열 폭 (--thumb-size) ── */
|
||||||
#grid {
|
#grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user