feat(showcase): add wheel navigation, horizontal thumbnail rail scroll, and image counter

- Add horizontal scroll to thumbnail rail on wheel event
- Add wheel-based image navigation in main image area (150 threshold)
- Add image counter showing current position (e.g., "1 / 12")
- Use tabular-nums and min-width: 2ch to prevent counter layout shift
- Respect params panel scrolling when using wheel navigation
This commit is contained in:
Will Miao
2026-02-07 00:35:29 +08:00
parent 4d9115339b
commit 5ffca15172
2 changed files with 96 additions and 0 deletions

View File

@@ -227,6 +227,44 @@
z-index: 5;
}
/* Image counter */
.showcase__counter {
position: absolute;
top: var(--space-2);
left: var(--space-2);
background: rgba(0, 0, 0, 0.6);
color: white;
padding: var(--space-1) var(--space-2);
border-radius: var(--border-radius-xs);
font-size: 0.85em;
font-weight: 500;
display: flex;
align-items: center;
gap: var(--space-1);
opacity: 0.8;
transition: opacity 0.2s ease;
pointer-events: none;
font-variant-numeric: tabular-nums;
}
.showcase__image-wrapper:hover .showcase__counter {
opacity: 1;
}
.showcase__counter-current {
font-weight: 600;
min-width: 2ch;
text-align: center;
}
.showcase__counter-separator {
opacity: 0.6;
}
.showcase__counter-total {
opacity: 0.8;
}
.showcase__image-wrapper:hover .showcase__controls {
opacity: 1;
transform: translateY(0);

View File

@@ -209,6 +209,12 @@ export class Showcase {
<!-- Media will be loaded here -->
</div>
<div class="showcase__counter">
<span class="showcase__counter-current">1</span>
<span class="showcase__counter-separator">/</span>
<span class="showcase__counter-total">${this.images.length}</span>
</div>
<div class="showcase__controls">
<button class="showcase__control-btn ${this.hasNsfwContent() ? '' : 'hidden'}"
data-action="toggle-global-blur"
@@ -425,6 +431,41 @@ export class Showcase {
fileInput?.click();
});
}
// Thumbnail rail horizontal scroll on wheel
const thumbnailRail = this.element.querySelector('.thumbnail-rail');
if (thumbnailRail) {
thumbnailRail.addEventListener('wheel', (e) => {
e.preventDefault();
thumbnailRail.scrollLeft += e.deltaY;
}, { passive: false });
}
// Main image navigation on wheel (with threshold to prevent accidental switching)
const mainImageWrapper = this.element.querySelector('.showcase__image-wrapper');
if (mainImageWrapper) {
let wheelAccumulated = 0;
const WHEEL_THRESHOLD = 150;
mainImageWrapper.addEventListener('wheel', (e) => {
// Don't interfere with params panel scrolling
const paramsPanel = this.element.querySelector('.showcase__params.visible');
if (paramsPanel && paramsPanel.contains(e.target)) {
return;
}
e.preventDefault();
wheelAccumulated += e.deltaY;
if (wheelAccumulated > WHEEL_THRESHOLD) {
this.nextImage();
wheelAccumulated = 0;
} else if (wheelAccumulated < -WHEEL_THRESHOLD) {
this.prevImage();
wheelAccumulated = 0;
}
}, { passive: false });
}
}
/**
@@ -637,10 +678,27 @@ export class Showcase {
item.classList.toggle('active', i === index);
});
// Update counter
this.updateCounter();
// Update params
this.updateParams(image);
}
/**
* Update image counter display
*/
updateCounter() {
const counterCurrent = this.element.querySelector('.showcase__counter-current');
const counterTotal = this.element.querySelector('.showcase__counter-total');
if (counterCurrent) {
counterCurrent.textContent = this.currentIndex + 1;
}
if (counterTotal) {
counterTotal.textContent = this.images.length;
}
}
/**
* Render media element (image or video)
*/