페이지를 늘리자 ‘프로젝트’ 메뉴가 멈췄다
/#projects 링크는 맞는데 이동하지 않았다. 모든 앵커 클릭을 취소하던 코드에서 원인을 찾았습니다.
홈에서 프로젝트 메뉴를 누르면 아래로 스크롤됩니다. 같은 메뉴를 개발 노트에 놓으면 홈으로 돌아가야 합니다. 링크 주소도 /#projects로 되어 있습니다. 그런데 이전 코드를 그대로 쓰면 글 페이지에서는 메뉴를 눌러도 그 자리에 머뭅니다. 주소의 문제가 아니라 클릭을 처리하는 순서의 문제였습니다.
한 페이지에서는 드러나지 않던 가정
사이트가 소개·프로젝트·연락처를 한 문서에 담고 있을 때에는 메뉴가 가리키는 요소가 모두 현재 페이지에 있었습니다. 기존 nav.astro는 그 전제로 #이 포함된 링크를 처리했습니다.수정 전 파일에서 문제가 된 부분을 줄이면 다음과 같습니다.
// 변경 전
anchor.addEventListener("click", function (e) {
e.preventDefault();
const target = e.currentTarget as HTMLAnchorElement;
const targetId = target.getAttribute("href")?.split("#")[1] || "";
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.scrollIntoView({ behavior: "smooth" });
}
});첫 줄의 preventDefault()가 링크의 기본 이동을 취소합니다. 그 뒤에 요소를 찾아서 직접 스크롤합니다. 대상이 있으면 괜찮지만, 없을 때는 아무 처리도 하지 않습니다. 기본 이동은 이미 취소됐으므로 브라우저가 대신 홈으로 이동해 주지도 않습니다.
현재 페이지: /notes/navigation/
누른 링크: /#projects
찾는 요소: 현재 문서의 id="projects"
검색 결과: null
최종 동작: 기본 이동도 취소, 스크롤도 없음getElementById()는 다른 HTML 파일 안을 검색하지 않습니다. 링크의 # 뒤쪽만 보면 대상 이름은 알 수 있지만, 그 대상이 어느 문서에 있는지는 잃어버립니다. 홈에만 있을 때는 우연히 성립했던 조건이 페이지를 추가하면서 깨진 셈입니다.
기본 이동을 취소하기 전에 확인할 것
다른 페이지로 가는 링크에는 별도 이동 코드를 붙이지 않았습니다. 지금 문서 안에서 스크롤할 수 있을 때만 이벤트를 가로채도록 범위를 좁혔습니다.
const target = e.currentTarget as HTMLAnchorElement;
const targetId = target.getAttribute("href")?.split("#")[1] || "";
const targetElement = document.getElementById(targetId);
const destination = new URL(target.href);
if (
destination.origin === location.origin &&
destination.pathname === location.pathname &&
targetElement
) {
e.preventDefault();
history.pushState(null, "", destination.hash);
targetElement.scrollIntoView({ behavior: "smooth" });
}origin은 같은 사이트인지, pathname은 같은 문서인지 확인합니다. 마지막으로 실제 요소가 있어야 합니다. 세 조건 중 하나라도 맞지 않으면 링크의 기본 동작을 남겨 둡니다. 상세 글에서 /#projects를 눌렀을 때에는 pathname이 다르므로 브라우저가 홈을 열고 해당 영역으로 이동합니다.
history.pushState()는 홈 안에서 부드럽게 스크롤할 때에도 주소에 #projects를 남기는 역할입니다. 스크롤만 하면 화면은 바뀌어도 주소가 그대로라 그 위치를 복사해 공유하기 어렵습니다. 다만 이 수정이 키보드 포커스 이동까지 처리하는 것은 아닙니다. scrollIntoView()는 보이는 위치를 옮기는 메서드이므로, 스크린 리더의 읽기 위치가 같은 방식으로 바뀐다고 가정하면 안 됩니다.
홈에서 눌러 보는 것만으로는 부족했다
이번 변경은 정적 빌드 결과를 Chromium에서 열어 데스크톱 1280px와 모바일 390px 너비로 확인했습니다. 핵심은 홈 안의 스크롤보다 글에서 홈으로 넘어가는 경로였습니다.
| 시작 위치 | 동작 | 확인 결과 |
|---|---|---|
| 홈 | 프로젝트 메뉴 클릭 | 주소가 /#projects로 바뀌고 프로젝트 영역으로 스크롤 |
| 이 글 | 프로젝트 메뉴 클릭 | 홈으로 이동한 뒤 프로젝트 영역 표시 |
| 이 글, JS 비활성화 | 프로젝트 메뉴 클릭 | HTML 링크만으로 홈의 프로젝트 영역에 도착 |
다시 확인하려면 이 글의 메뉴에서 프로젝트를 누른 뒤 주소와 도착 위치를 보면 됩니다. 자바스크립트가 꺼진 경우에는 부드러운 스크롤 효과는 없지만 목적지에 도착해야 합니다. 스타일이나 애니메이션을 확인하는 테스트와 링크의 목적지에 도착하는 테스트는 따로 봤습니다.
원래 링크 하나로 할 수 있는 이동을 스크립트가 먼저 막아 버린 것이 문제였습니다. 페이지를 늘릴 때 메뉴를 복사하는 것만으로 끝내지 않고, 클릭 핸들러가 현재 문서만 가정하고 있는지도 확인해야 했습니다.
변경 코드: 46ee3ca· 참고: MDN preventDefault()