jQuery / 맨 위로 부드럽게 이동하는 링크 만들기
맨 위로 부드럽게 이동하려면 실제 상단 목적지를 가진 href="#top" 링크를 만들고, 클릭 시 jQuery의 .animate({ scrollTop: 0 })로 문서 스크롤 요소를 움직입니다. 클릭의 기본 점프는 막되, JavaScript가 없을 때도 이동할 수 있도록 앵커 주소는 남깁니다. 동작 줄이기 설정에서는 즉시 이동하고, 완료 후 상단 제목으로 키보드 포커스를 옮깁니다.
이동할 문서와 기본 링크 정하기
페이지 전체를 움직이는 예제입니다. document.scrollingElement는 문서를 실제로 스크롤하는 요소를 반환하며, 표준 모드에서는 문서 루트입니다. 예제의 <!doctype html> 선언을 유지합니다. 실제 스크롤 요소 하나를 선택하면 html과 body 양쪽의 효과·완료 콜백을 따로 다루지 않아도 됩니다.
상단 제목에는 id="top"을 주고 링크는 그 id를 가리킵니다. tabindex="-1"은 제목을 일반 Tab 이동 순서에 추가하지 않으면서 코드로 포커스할 수 있게 합니다. 스크롤만 위로 이동시키면 키보드 포커스는 하단 링크에 남을 수 있으므로 화면 이동과 포커스 이동을 따로 처리합니다.
한 파일로 실행하는 전체 예제
아래 전체 코드를 UTF-8로 back-top-demo.html에 저장하고 브라우저로 엽니다. 인터넷으로 공식 jQuery 전체 파일 4.0.0을 읽습니다. 이 코드의 시간 지정 애니메이션에는 effects 모듈이 필요하므로 slim 파일로 바꾸지 않습니다. 별도 설치나 서버는 필요하지 않습니다.
Go to 900px는 비교 시작 위치를 만들고, 화면 오른쪽 아래에는 항상 누를 수 있는 링크를 둡니다. 문서가 짧으면 스크롤할 공간이 없으므로 실습 문서의 높이를 충분히 길게 했습니다. 고정 툴바는 실습용 조작부이며 실제 사이트에서는 기존 헤더·광고와 겹치지 않게 배치합니다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Smooth back-to-top link</title>
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<style>
html { scroll-behavior: auto; }
body { font: 16px/1.6 sans-serif; margin: 0; padding: 100px 20px; min-height: 2400px; box-sizing: border-box; }
#top { max-width: 760px; margin: 0; }
#top:focus { outline: 3px solid teal; }
#toolbar { position: fixed; top: 0; left: 0; right: 0; background: #fff; padding: 12px; z-index: 2; }
button, a { font: inherit; }
button { margin: 4px; }
#back { position: fixed; right: 16px; bottom: 16px; padding: 10px 14px; background: #075d55; color: #fff; border-radius: 6px; z-index: 2; }
#status { position: fixed; left: 12px; bottom: 70px; max-width: calc(100% - 48px); padding: 8px; background: #fff; white-space: pre-wrap; overflow-wrap: anywhere; }
.spacer { margin-top: 700px; }
</style>
</head>
<body>
<div id="toolbar"><button type="button" id="down">Go to 900px</button><button type="button" id="stop">Stop movement</button></div>
<h1 id="top" tabindex="-1">Top of the example page</h1>
<p>Use the fixed link after scrolling down.</p>
<p class="spacer">Middle of the page</p>
<p class="spacer">Lower content</p>
<a id="back" href="#top">Back to top</a>
<pre id="status" aria-live="polite"></pre>
<script>
$(function () {
const root = document.scrollingElement;
if (!root) return;
const $root = $(root);
let completed = 0, state = "ready";
function report() { $("#status").text(`scrollY=${Math.round(window.scrollY)}\ncompleted=${completed}\nstate=${state}`); }
function interrupt() {
if (state !== "moving") return;
$root.stop(true, false); state = "interrupted"; report();
}
$("#back").on("click.backTopLesson", function (event) {
event.preventDefault();
$root.stop(true, false);
state = "moving"; report();
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
$root.animate({ scrollTop: 0 }, { duration: reduce ? 0 : 600, complete: function () {
completed++; state = "done";
document.getElementById("top").focus({ preventScroll: true });
report();
} });
});
$("#down").on("click", function () { $root.stop(true, false).scrollTop(900); completed = 0; state = "ready"; report(); });
$("#stop").on("click", interrupt);
$(window).on("scroll.backTopLesson", report);
$(window).on("wheel.backTopLesson touchstart.backTopLesson", interrupt);
$(document).on("keydown.backTopLesson", function (event) {
if (["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", " "].includes(event.key)) interrupt();
});
report();
});
</script>
</body>
</html>
페이지 맨 위의 초기 출력입니다. 출력용 반올림은 위치를 읽기 쉽게 보여 주려는 것이며, 애니메이션 계산을 정수로 제한하지 않습니다.
scrollY=0
completed=0
state=ready
- Go to 900px를 눌러 시작 위치를 만듭니다. 창이 너무 높아 900px까지 내려갈 수 없으면 창 높이를 줄입니다.
- Back to top을 누르면 기본 설정에서 600ms 동안 맨 위로 이동합니다. 중간 scrollY가 줄어든 뒤 0이 되고 completed=1, state=done입니다.
- 완료 시 제목에 포커스 윤곽선이 보입니다. 하단 링크에 포커스를 둔 뒤 Enter로도 같은 이동을 확인합니다.
- 다시 900px에서 이동 중 여러 번 눌러도 대기열이 늘어 차례로 재생되지 않습니다. 기존 효과를 멈추고 현재 위치에서 새로 시작합니다.
- Stop movement는 중간 위치에서 정지하고 state=interrupted로 표시합니다. 다시 시작하려면 상단 링크를 누르거나 Go to 900px로 비교 위치를 복원합니다.
위 동작과 키보드 활성화, 연속 클릭, 중간 정지, 동작 줄이기 설정을 jQuery 3.7.1·4.0.0 및 Windows Edge에서 실행했습니다. JavaScript를 끈 별도 확인에서도 링크가 #top 목적지로 이동했습니다.
연속 클릭·중단·동작 줄이기 처리
.animate()의 scrollTop 대상은 숫자 0입니다. event.preventDefault()가 없으면 앵커의 즉시 이동과 애니메이션이 겹칠 수 있습니다. 효과 직전의 .stop(true, false)는 기다리는 효과를 비우고 현재 효과를 도착점으로 강제 완료하지 않은 채 정지합니다. 따라서 반복 클릭도 현재 위치에서 다시 시작하며 예전 완료 콜백을 임의로 실행하지 않습니다.
사용자가 휠·터치 시작·스크롤용 키로 직접 움직이려 하면 interrupt()가 효과를 멈추도록 등록했습니다. 이 동작에서 preventDefault()로 사용자의 스크롤까지 막지는 않습니다. Stop 버튼도 같은 함수를 사용합니다. 완료하지 않은 중간 정지에서는 상단 제목으로 포커스를 강제로 옮기지 않습니다.
prefers-reduced-motion은 사용자가 불필요한 동작을 줄이도록 설정했는지 알려 줍니다. 예제는 매번 링크를 누를 때 이 값을 읽어 duration을 0으로 정하고, 같은 완료 경로에서 포커스를 이동합니다. 이 예제만을 위해 사이트 전체 jQuery 효과를 끄는 전역 설정은 바꾸지 않습니다.
focus({ preventScroll: true })는 애니메이션으로 맞춘 위치를 포커스 이동이 다시 바꾸지 않게 합니다. 도착점을 최상단 0이 아닌 특정 제목 위치로 바꿀 때는 고정 헤더 높이와 실제 읽기 시작점을 별도로 계산해야 합니다. 단순히 id 이름만 바꿔도 모든 위치로 같은 효과가 적용된다고 가정하지 않습니다.
사이트에 적용할 때 확인하기
같은 문서에 id="top"이 이미 있는지 확인하고 링크와 목적지를 일치시킵니다. 기존 링크의 이벤트 처리기나 CSS를 덮어쓰지 않도록 선택자와 네임스페이스를 해당 기능 범위로 한정합니다. 이 예제의 Go to 900px·상태 출력·실습용 긴 높이를 실제 사이트에 그대로 추가할 필요는 없습니다.
CSS scroll-behavior:smooth와 jQuery 애니메이션을 동시에 같은 스크롤에 적용하지 않습니다. 예제는 auto로 두고 jQuery가 시간을 제어합니다. jQuery가 필요 없는 화면이라면 네이티브 window.scrollTo()의 smooth도 선택지이지만, 완료 시점과 포커스 이동은 그 방식에 맞게 설계합니다.
움직이지 않으면 문서에 실제 스크롤 여유가 있는지, 화면이 내부 컨테이너를 스크롤하는 구조인지, jQuery 전체 파일이 정상 로딩됐는지 확인합니다. 링크의 텍스트·대비·모바일 누름 영역과 기존 고정 요소의 겹침도 함께 확인합니다. JavaScript가 실패해도 링크 자체를 쓸 수 있도록 href를 javascript:나 빈 주소로 바꾸지 않습니다.




