- 게시물 상세를 default 레이아웃으로 통합해 홈↔게시물 레이아웃 교체 제거 - CSS 전환 베일·스크롤 정리 플러그인으로 이동 중 깜빡임 완화 Co-authored-by: Cursor <cursoragent@cursor.com>
158 lines
3.7 KiB
JavaScript
158 lines
3.7 KiB
JavaScript
import {
|
|
consumePendingScrollRestore,
|
|
scrollDocumentTo,
|
|
scrollDocumentToTop,
|
|
shouldUseSiteRouteVeil
|
|
} from '../lib/site-route-transition.js'
|
|
|
|
const SITE_ROUTE_VEIL_CLASS = 'site-page-veil'
|
|
|
|
/**
|
|
* 공개 페이지 이동 시 전환 베일을 표시하고, 베일 뒤에서 스크롤을 정리한 뒤 새 화면을 드러낸다.
|
|
*/
|
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
const router = useRouter()
|
|
const isSitePageVeiled = useState('site-page-veil', () => false)
|
|
|
|
/** @type {boolean} */
|
|
let activeVeilNavigation = false
|
|
|
|
/**
|
|
* 전환 베일을 즉시 표시한다.
|
|
* @returns {void}
|
|
*/
|
|
const showRouteVeil = () => {
|
|
if (document.documentElement.classList.contains(SITE_ROUTE_VEIL_CLASS)) {
|
|
return
|
|
}
|
|
|
|
activeVeilNavigation = true
|
|
isSitePageVeiled.value = true
|
|
document.documentElement.classList.add(SITE_ROUTE_VEIL_CLASS)
|
|
}
|
|
|
|
/**
|
|
* 전환 베일을 즉시 숨긴다.
|
|
* @returns {void}
|
|
*/
|
|
const hideRouteVeil = () => {
|
|
activeVeilNavigation = false
|
|
isSitePageVeiled.value = false
|
|
document.documentElement.classList.remove(SITE_ROUTE_VEIL_CLASS)
|
|
}
|
|
|
|
/**
|
|
* 내부 링크 클릭이 라우터보다 먼저 베일을 덮도록 한다.
|
|
* @param {MouseEvent} event - 클릭 이벤트
|
|
* @returns {void}
|
|
*/
|
|
const handleInternalLinkCapture = (event) => {
|
|
if (event.defaultPrevented || event.button !== 0) {
|
|
return
|
|
}
|
|
|
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
return
|
|
}
|
|
|
|
const target = event.target
|
|
if (!(target instanceof Element)) {
|
|
return
|
|
}
|
|
|
|
const anchor = target.closest('a')
|
|
if (!anchor || anchor.target === '_blank' || anchor.hasAttribute('download')) {
|
|
return
|
|
}
|
|
|
|
const href = anchor.getAttribute('href')
|
|
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) {
|
|
return
|
|
}
|
|
|
|
let url = null
|
|
try {
|
|
url = new URL(anchor.href, window.location.origin)
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
if (url.origin !== window.location.origin) {
|
|
return
|
|
}
|
|
|
|
const currentPath = router.currentRoute.value.path
|
|
if (!shouldUseSiteRouteVeil(url.pathname) && !shouldUseSiteRouteVeil(currentPath)) {
|
|
return
|
|
}
|
|
|
|
if (url.pathname === currentPath && !url.hash) {
|
|
return
|
|
}
|
|
|
|
showRouteVeil()
|
|
}
|
|
|
|
/**
|
|
* 페이지 렌더 완료 후 스크롤을 맞추고 베일을 해제한다.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const finishRouteTransition = async () => {
|
|
if (!activeVeilNavigation) {
|
|
return
|
|
}
|
|
|
|
const route = router.currentRoute.value
|
|
const restore = consumePendingScrollRestore()
|
|
|
|
if (restore) {
|
|
scrollDocumentTo(restore)
|
|
} else if (!route.hash) {
|
|
scrollDocumentToTop()
|
|
}
|
|
|
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
|
|
if (restore) {
|
|
scrollDocumentTo(restore)
|
|
} else if (!route.hash) {
|
|
scrollDocumentToTop()
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 48))
|
|
|
|
hideRouteVeil()
|
|
}
|
|
|
|
if (import.meta.client && 'scrollRestoration' in window.history) {
|
|
window.history.scrollRestoration = 'manual'
|
|
}
|
|
|
|
document.addEventListener('click', handleInternalLinkCapture, true)
|
|
|
|
router.beforeEach((to, from) => {
|
|
if (to.fullPath === from.fullPath) {
|
|
return
|
|
}
|
|
|
|
if (!shouldUseSiteRouteVeil(to.path) && !shouldUseSiteRouteVeil(from.path)) {
|
|
return
|
|
}
|
|
|
|
showRouteVeil()
|
|
})
|
|
|
|
router.onError(() => {
|
|
hideRouteVeil()
|
|
})
|
|
|
|
nuxtApp.hook('page:finish', () => {
|
|
finishRouteTransition()
|
|
})
|
|
|
|
nuxtApp.hook('app:error', () => {
|
|
hideRouteVeil()
|
|
})
|
|
})
|