feat(home): Featured 모바일 터치 스크롤·화살표 끝 비활성

- 트랙에 touch-pan-x·webkit 가로 스크롤·overscroll-x-contain 적용
- scroll·ResizeObserver로 이전/다음 disabled 동기화
- v0.0.60 문서 반영

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-11 12:29:27 +09:00
parent ed7709ab59
commit fd55d8af08
6 changed files with 108 additions and 5 deletions

View File

@@ -88,6 +88,67 @@ const featuredPosts = computed(() => posts.value.slice(0, 6))
const latestPosts = computed(() => posts.value.map(mapLatestPost))
const featuredTrackRef = ref(null)
/** Featured 트랙이 스크롤 시작에 붙었는지 — 이전 화살표 비활성 */
const featuredAtStart = ref(true)
/** Featured 트랙이 스크롤 끝에 붙었는지 — 다음 화살표 비활성 */
const featuredAtEnd = ref(true)
let unbindFeaturedScroll = () => {}
/**
* Featured 가로 스크롤 위치에 따라 이전/다음 버튼 상태를 갱신한다.
* @param {HTMLElement | null} el - 스크롤 컨테이너
* @returns {void}
*/
const updateFeaturedScrollEdges = (el) => {
const target = el || featuredTrackRef.value
if (!target) {
featuredAtStart.value = true
featuredAtEnd.value = true
return
}
const { scrollLeft, scrollWidth, clientWidth } = target
const maxScroll = Math.max(0, scrollWidth - clientWidth)
const epsilon = 2
featuredAtStart.value = scrollLeft <= epsilon
featuredAtEnd.value = scrollLeft >= maxScroll - epsilon
}
watch(featuredTrackRef, (el) => {
unbindFeaturedScroll()
unbindFeaturedScroll = () => {}
if (!import.meta.client || !el) {
updateFeaturedScrollEdges(null)
return
}
const onScroll = () => {
updateFeaturedScrollEdges(el)
}
onScroll()
el.addEventListener('scroll', onScroll, { passive: true })
const resizeObserver = new ResizeObserver(onScroll)
resizeObserver.observe(el)
unbindFeaturedScroll = () => {
el.removeEventListener('scroll', onScroll)
resizeObserver.disconnect()
}
}, { immediate: true })
watch(featuredPosts, () => {
if (!import.meta.client) {
return
}
nextTick(() => {
updateFeaturedScrollEdges(featuredTrackRef.value)
})
})
onMounted(() => {
if (!import.meta.client) {
@@ -107,6 +168,7 @@ onBeforeUnmount(() => {
return
}
unbindFeaturedScroll()
document.removeEventListener('pointerdown', onDocumentPointerDown)
})
@@ -120,6 +182,14 @@ const scrollFeatured = (direction) => {
return
}
if (direction === 'left' && featuredAtStart.value) {
return
}
if (direction === 'right' && featuredAtEnd.value) {
return
}
const firstCard = featuredTrackRef.value.querySelector('[data-featured-slide]')
const cardWidth = firstCard ? firstCard.getBoundingClientRect().width : 244
const gap = 24
@@ -161,13 +231,29 @@ const scrollFeatured = (direction) => {
<div class="flex items-end justify-between gap-2 border-b border-[var(--site-line)] pb-2">
<h2 class="text-sm font-medium uppercase site-muted">Featured</h2>
<div class="flex justify-between gap-2">
<button class="cursor-pointer p-1 hover:opacity-75" type="button" aria-label="Previous" @click="scrollFeatured('left')"></button>
<button class="cursor-pointer p-1 hover:opacity-75" type="button" aria-label="Next" @click="scrollFeatured('right')"></button>
<button
class="featured-nav-prev cursor-pointer p-1 text-[var(--site-text)] hover:opacity-75 disabled:cursor-not-allowed disabled:opacity-35 disabled:hover:opacity-35"
type="button"
aria-label="Featured 이전"
:disabled="featuredAtStart"
@click="scrollFeatured('left')"
>
</button>
<button
class="featured-nav-next cursor-pointer p-1 text-[var(--site-text)] hover:opacity-75 disabled:cursor-not-allowed disabled:opacity-35 disabled:hover:opacity-35"
type="button"
aria-label="Featured 다음"
:disabled="featuredAtEnd"
@click="scrollFeatured('right')"
>
</button>
</div>
</div>
<div
ref="featuredTrackRef"
class="mt-4 flex snap-x snap-mandatory gap-6 overflow-x-auto scroll-smooth pb-1 [--slides:1.4] sm:[--slides:1.6] lg:[--slides:2.6] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
class="featured-posts-track mt-4 flex snap-x snap-mandatory gap-6 overflow-x-auto overscroll-x-contain scroll-smooth pb-1 touch-pan-x [-webkit-overflow-scrolling:touch] [--slides:1.4] sm:[--slides:1.6] lg:[--slides:2.6] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
<NuxtLink
v-for="post in featuredPosts"