diff --git a/assets/css/main.css b/assets/css/main.css index dda1e07..ba53e1b 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -94,6 +94,16 @@ overflow: hidden; } + html.site-navigating { + cursor: progress; + } + + html.site-navigating a, + html.site-navigating button, + html.site-navigating [role="button"] { + pointer-events: none; + } + body { min-width: 320px; margin: 0; diff --git a/docs/map.md b/docs/map.md index 3ef4694..d52fcba 100644 --- a/docs/map.md +++ b/docs/map.md @@ -361,6 +361,7 @@ | server/routes/admin/api/analytics/range-detail.get.js | 관리자 통계 추이 날짜·구간 상세 API | | plugins/site-analytics.client.js | 공개 라우트 pageview·heartbeat·read 클라이언트 전송 | | plugins/bfcache-page-restore.client.js | 뒤로가기 캐시(bfcache) 복원 시 라우터 경로와 실제 URL 어긋남을 감지해 새로고침(HTML 문서 모드 페이지 이동 후 뒤로가기 잔상 방지) | +| plugins/navigation-lock.client.js | 페이지 이동 중 `html.site-navigating` 클래스로 링크·버튼 클릭을 잠가 중복 클릭 차단(안전 타임아웃 포함) | ## 데이터베이스 diff --git a/docs/update.md b/docs/update.md index 832f039..98180be 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 이력 +## v1.10.34 + +- 페이지 이동 중 링크·버튼 중복 클릭(연타) 차단 추가. +- 이동 중 `html.site-navigating` 클래스로 상호작용 요소 pointer-events를 끄는 클라이언트 플러그인 `navigation-lock.client.js` 추가(예외 방지 안전 타임아웃 포함). +- 게시물 클릭 후 재클릭 시 상단으로 점프하던 문제 해결. + ## v1.10.33 - HTML 문서 모드 페이지 뒤로가기 시 홈으로 돌아가지 못하던 문제를 근본 수정. diff --git a/package.json b/package.json index f389750..814eef1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.10.33", + "version": "1.10.34", "private": true, "type": "module", "imports": { diff --git a/plugins/navigation-lock.client.js b/plugins/navigation-lock.client.js new file mode 100644 index 0000000..cec860b --- /dev/null +++ b/plugins/navigation-lock.client.js @@ -0,0 +1,40 @@ +/** + * 페이지 이동 중 링크·버튼 클릭을 잠가 중복 클릭(연타)을 막는다. + * + * 이동 시작 시 `html.site-navigating` 클래스를 붙여 상호작용 요소의 pointer-events를 끄고, + * 이동 종료·실패 시 해제한다. 예외로 클래스가 남는 것을 막기 위해 안전 타임아웃을 둔다. + */ +const NAVIGATION_LOCK_CLASS = 'site-navigating' +const NAVIGATION_LOCK_TIMEOUT_MS = 10000 + +export default defineNuxtPlugin((nuxtApp) => { + const root = document.documentElement + + /** @type {ReturnType | null} */ + let releaseTimer = null + + /** + * 이동 잠금을 건다(안전 타임아웃 포함). + * @returns {void} + */ + const lockNavigation = () => { + root.classList.add(NAVIGATION_LOCK_CLASS) + clearTimeout(releaseTimer) + releaseTimer = setTimeout(() => { + root.classList.remove(NAVIGATION_LOCK_CLASS) + }, NAVIGATION_LOCK_TIMEOUT_MS) + } + + /** + * 이동 잠금을 해제한다. + * @returns {void} + */ + const unlockNavigation = () => { + clearTimeout(releaseTimer) + releaseTimer = null + root.classList.remove(NAVIGATION_LOCK_CLASS) + } + + nuxtApp.hook('page:loading:start', lockNavigation) + nuxtApp.hook('page:loading:end', unlockNavigation) +})