페이지 이동 오버레이 해제 시점 보강
This commit is contained in:
46
app.vue
46
app.vue
@@ -22,25 +22,63 @@ const isSitePageNavigating = ref(false)
|
|||||||
|
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
/** @type {ReturnType<typeof setTimeout> | null} */
|
||||||
|
let hideOverlayTimer = null
|
||||||
|
let navigationOverlayToken = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 공개 페이지 이동 로딩 오버레이를 표시한다.
|
* 공개 페이지 이동 로딩 오버레이를 표시한다.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const showSitePageNavigationOverlay = () => {
|
const showSitePageNavigationOverlay = () => {
|
||||||
|
navigationOverlayToken += 1
|
||||||
|
clearTimeout(hideOverlayTimer)
|
||||||
|
hideOverlayTimer = null
|
||||||
isSitePageNavigating.value = true
|
isSitePageNavigating.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 공개 페이지 이동 로딩 오버레이를 숨긴다.
|
* 새 화면이 실제로 그려진 뒤 공개 페이지 이동 로딩 오버레이를 숨긴다.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const hideSitePageNavigationOverlay = () => {
|
const scheduleHideSitePageNavigationOverlay = () => {
|
||||||
|
const token = navigationOverlayToken
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
hideOverlayTimer = setTimeout(() => {
|
||||||
|
if (token !== navigationOverlayToken) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSitePageNavigating.value = false
|
||||||
|
hideOverlayTimer = null
|
||||||
|
}, 120)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 오류·취소 상황에서 공개 페이지 이동 로딩 오버레이를 즉시 숨긴다.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const hideSitePageNavigationOverlayImmediately = () => {
|
||||||
|
clearTimeout(hideOverlayTimer)
|
||||||
|
hideOverlayTimer = null
|
||||||
isSitePageNavigating.value = false
|
isSitePageNavigating.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
if (to.fullPath !== from.fullPath) {
|
||||||
|
showSitePageNavigationOverlay()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
router.onError(hideSitePageNavigationOverlayImmediately)
|
||||||
nuxtApp.hook('page:loading:start', showSitePageNavigationOverlay)
|
nuxtApp.hook('page:loading:start', showSitePageNavigationOverlay)
|
||||||
nuxtApp.hook('page:loading:end', hideSitePageNavigationOverlay)
|
nuxtApp.hook('page:finish', scheduleHideSitePageNavigationOverlay)
|
||||||
|
nuxtApp.hook('app:error', hideSitePageNavigationOverlayImmediately)
|
||||||
}
|
}
|
||||||
|
|
||||||
useHead(() => ({
|
useHead(() => ({
|
||||||
@@ -70,7 +108,7 @@ useHead(() => ({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="isSitePageNavigating"
|
v-if="isSitePageNavigating"
|
||||||
class="site-page-loading fixed inset-0 z-[90] grid place-items-center bg-[var(--site-bg)]/78 text-[var(--site-text)] backdrop-blur-[2px]"
|
class="site-page-loading fixed inset-0 z-[90] grid place-items-center bg-[var(--site-bg)] text-[var(--site-text)]"
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 배포 가이드
|
# 배포 가이드
|
||||||
|
|
||||||
> 로컬 기준 v1.10.47에서 `npm run lint`와 `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다.
|
> 로컬 기준 v1.10.48에서 `npm run lint`와 `npm run build` 검증을 통과했다. NAS 실제 컨테이너 기동과 도메인/프록시 접속 검증은 운영 배포 단계에서 진행한다.
|
||||||
|
|
||||||
## 빌드 유형
|
## 빌드 유형
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-07-06 v1.10.48 — 이동 오버레이는 로딩 표시가 아니라 화면 전환 막으로 동작해야 한다
|
||||||
|
|
||||||
|
게시물 상세를 블로킹 조회로 되돌려도 운영 환경에서는 로딩 스피너 뒤로 홈 헤더 이미지가 다시 보인 뒤 상세로 넘어가는 현상이 남았다. 원인은 로딩 오버레이가 반투명이라 이전 화면이 비쳐 보일 수 있고, `page:loading:end`가 새 화면의 실제 페인트보다 빠르게 발생할 수 있다는 점이었다.
|
||||||
|
|
||||||
|
따라서 전역 이동 오버레이는 단순 로딩 표시가 아니라 화면 전환을 완전히 가리는 막으로 다룬다. 배경은 불투명하게 만들고, 해제는 `page:finish` 이후 렌더 프레임을 기다린 다음 수행해 이전 홈 화면의 상단이 다시 노출되는 구간을 줄인다.
|
||||||
|
|
||||||
## 2026-07-06 v1.10.47 — 게시물 이동은 로딩 후 진입으로 확정한다
|
## 2026-07-06 v1.10.47 — 게시물 이동은 로딩 후 진입으로 확정한다
|
||||||
|
|
||||||
즉시 전환, 라우터 스크롤 비활성화, Nuxt `page:finish` 이후 스크롤 적용, 짧은 전환 베일을 차례로 시도했지만 홈에서 게시물로 이동할 때 이전 홈 헤더 이미지가 순간 노출되는 현상이 남았다. 홈 화면의 큰 헤더 이미지와 문서 스크롤, Nuxt 클라이언트 라우팅의 페인트 타이밍이 맞물리는 문제라 즉시 전환을 더 고집하면 코드 복잡도만 커지고 사용자 체감은 안정되지 않는다.
|
즉시 전환, 라우터 스크롤 비활성화, Nuxt `page:finish` 이후 스크롤 적용, 짧은 전환 베일을 차례로 시도했지만 홈에서 게시물로 이동할 때 이전 홈 헤더 이미지가 순간 노출되는 현상이 남았다. 홈 화면의 큰 헤더 이미지와 문서 스크롤, Nuxt 클라이언트 라우팅의 페인트 타이밍이 맞물리는 문제라 즉시 전환을 더 고집하면 코드 복잡도만 커지고 사용자 체감은 안정되지 않는다.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
| layouts/post.vue | 개별 게시물 — `default`와 동일, 오른쪽 컬럼 전체 높이 구분선 |
|
| layouts/post.vue | 개별 게시물 — `default`와 동일, 오른쪽 컬럼 전체 높이 구분선 |
|
||||||
| layouts/admin.vue | 관리자 전체, 320px 밝은 Ghost형 사이드바(대시보드 `/admin` 활성 링크·사이트 보기 새 창·콘텐츠 구간 여백), 우측 전체 높이 캔버스, 멤버 수 표시, 하단 사용자 드롭다운·설정, `내 프로필` 멤버 편집 이동, 게시글 `+` 새 글 진입, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금, **`/admin/settings`에서는 사이드바 숨김·본문 전체 화면·설정용 문서 스크롤 잠금** |
|
| layouts/admin.vue | 관리자 전체, 320px 밝은 Ghost형 사이드바(대시보드 `/admin` 활성 링크·사이트 보기 새 창·콘텐츠 구간 여백), 우측 전체 높이 캔버스, 멤버 수 표시, 하단 사용자 드롭다운·설정, `내 프로필` 멤버 편집 이동, 게시글 `+` 새 글 진입, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금, **`/admin/settings`에서는 사이드바 숨김·본문 전체 화면·설정용 문서 스크롤 잠금** |
|
||||||
| layouts/page.vue | 고정 페이지 전체 화면 |
|
| layouts/page.vue | 고정 페이지 전체 화면 |
|
||||||
| app.vue | 공개 사이트 설정 기반 파비콘 head 링크, 페이지 제목이 있으면 제목만 쓰고 없으면 사이트 이름을 쓰는 title template, 브랜드 컬러 `--site-accent` 적용, 페이지 이동 로딩 진행바(`NuxtLoadingIndicator`, 브랜드 컬러)와 전역 이동 로딩 오버레이 |
|
| app.vue | 공개 사이트 설정 기반 파비콘 head 링크, 페이지 제목이 있으면 제목만 쓰고 없으면 사이트 이름을 쓰는 title template, 브랜드 컬러 `--site-accent` 적용, 페이지 이동 로딩 진행바(`NuxtLoadingIndicator`, 브랜드 컬러)와 새 화면 렌더 후 해제되는 불투명 전역 이동 로딩 오버레이 |
|
||||||
| error.vue | 공개 404/서버 오류 화면, 홈 이동 버튼 |
|
| error.vue | 공개 404/서버 오류 화면, 홈 이동 버튼 |
|
||||||
|
|
||||||
## Composables
|
## Composables
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
### 공개 URL 구조
|
### 공개 URL 구조
|
||||||
|
|
||||||
- `/posts` - 게시물 전체 목록. 사이트 설정 POST 설정의 `postListPageSize` 값 기준으로 서버 페이지네이션을 사용하며, 페이지 번호가 바뀌면 화면을 상단으로 이동한다. 하단 페이지네이션은 이전·다음 화살표와 페이지 번호 직접 이동을 함께 제공하며, 전체 페이지가 많으면 첫 페이지·끝 페이지·현재 페이지 주변 번호를 유지하고 중간 구간은 `...`으로 접는다.
|
- `/posts` - 게시물 전체 목록. 사이트 설정 POST 설정의 `postListPageSize` 값 기준으로 서버 페이지네이션을 사용하며, 페이지 번호가 바뀌면 화면을 상단으로 이동한다. 하단 페이지네이션은 이전·다음 화살표와 페이지 번호 직접 이동을 함께 제공하며, 전체 페이지가 많으면 첫 페이지·끝 페이지·현재 페이지 주변 번호를 유지하고 중간 구간은 `...`으로 접는다.
|
||||||
- `/post/:slug` - 개별 게시물 상세. 직접 접속·SSR과 클라이언트 내부 이동 모두 본문을 먼저 조회한 뒤 완성된 상세 화면으로 진입한다. 이동 중에는 `NuxtLoadingIndicator` 상단 진행바, 중복 클릭 잠금, 전역 페이지 이동 로딩 오버레이로 피드백을 제공해 이전 홈 화면이 상단으로 튀어 보이지 않게 한다. 이전·다음 글은 상세 API 응답의 `previous`·`next`로 표시하며, `previous`는 더 과거 글, `next`는 더 최신 글을 뜻한다.
|
- `/post/:slug` - 개별 게시물 상세. 직접 접속·SSR과 클라이언트 내부 이동 모두 본문을 먼저 조회한 뒤 완성된 상세 화면으로 진입한다. 이동 중에는 `NuxtLoadingIndicator` 상단 진행바, 중복 클릭 잠금, 전역 페이지 이동 로딩 오버레이로 피드백을 제공한다. 오버레이는 불투명 배경으로 이전 화면을 완전히 가리고, 새 페이지 렌더 완료 후 다음 렌더 프레임까지 기다린 뒤 해제해 이전 홈 화면이 상단으로 튀어 보이지 않게 한다. 이전·다음 글은 상세 API 응답의 `previous`·`next`로 표시하며, `previous`는 더 과거 글, `next`는 더 최신 글을 뜻한다.
|
||||||
- `/` - 홈. 클라이언트 복귀 시에도 주요 데이터를 블로킹 조회한 뒤 전환한다.
|
- `/` - 홈. 클라이언트 복귀 시에도 주요 데이터를 블로킹 조회한 뒤 전환한다.
|
||||||
- `/sitemap.xml` - 공개 sitemap XML. 홈, 게시물 목록, 태그 목록, 공개 발행글, 공개 고정 페이지, 글이 있는 태그 URL을 요청 시점 DB 기준으로 자동 생성한다. 멤버십·비공개·초안·예약 대기 글과 `noindex` 글은 포함하지 않는다.
|
- `/sitemap.xml` - 공개 sitemap XML. 홈, 게시물 목록, 태그 목록, 공개 발행글, 공개 고정 페이지, 글이 있는 태그 URL을 요청 시점 DB 기준으로 자동 생성한다. 멤버십·비공개·초안·예약 대기 글과 `noindex` 글은 포함하지 않는다.
|
||||||
- `/robots.txt` - 크롤러 안내 파일. 관리자·API·회원 설정·로그인 경로를 제외하고 `Sitemap: /sitemap.xml` 절대 URL을 제공한다.
|
- `/robots.txt` - 크롤러 안내 파일. 관리자·API·회원 설정·로그인 경로를 제외하고 `Sitemap: /sitemap.xml` 절대 URL을 제공한다.
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.10.48
|
||||||
|
|
||||||
|
- 페이지 이동 로딩 오버레이 배경을 불투명하게 변경해 이전 홈 헤더 이미지가 비쳐 보이지 않도록 수정.
|
||||||
|
- 이동 오버레이 해제 시점을 `page:loading:end`에서 `page:finish` 이후 다음 렌더 프레임으로 늦춰 새 화면이 실제로 그려진 뒤 사라지도록 수정.
|
||||||
|
- 라우터 이동 시작 시에도 오버레이를 즉시 표시하고, 라우트 오류·앱 오류 시에는 즉시 해제하도록 보강.
|
||||||
|
- 홈에서 게시물로 이동할 때 로딩 뒤 이전 화면 상단이 다시 보이는 문제를 추가 완화.
|
||||||
|
|
||||||
## v1.10.47
|
## v1.10.47
|
||||||
|
|
||||||
- 게시물 상세 클라이언트 이동을 즉시 전환 방식에서 로딩 후 진입 방식으로 복귀.
|
- 게시물 상세 클라이언트 이동을 즉시 전환 방식에서 로딩 후 진입 방식으로 복귀.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.10.47",
|
"version": "1.10.48",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.10.47",
|
"version": "1.10.48",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.10.47",
|
"version": "1.10.48",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
Reference in New Issue
Block a user