Skip to content

Instantly share code, notes, and snippets.

@supernovel
Last active December 23, 2019 02:06
Show Gist options
  • Save supernovel/8766c3db15132ff73831f0c2c66724da to your computer and use it in GitHub Desktop.
Save supernovel/8766c3db15132ff73831f0c2c66724da to your computer and use it in GitHub Desktop.
페이지 스크롤 저장 및 복구 (window.history 사용)
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.ScrollRestoration = factory());
}(this, (function () { 'use strict';
const SCROLL_RESTORATION_TIMEOUT_MS = 3000;
const TRY_TO_SCROLL_INTERVAL_MS = 50;
const disabled = window.history == null;
const originalPushState = window.history.pushState;
const originalReplaceState = window.history.replaceState;
let timeoutHandle = null;
let scrollBarWidth = null;
let main = {
example() {
if (initialize()) {
// 현재는 페이지 전환이 일어나는 부분에만 이벤트 작성
// spa에서는 아래 이벤트로 불가
// 페이지 로드 시
window.addEventListener('DOMContentLoaded', () => {
tryRestore();
});
// 페이지 나갈 때 스크롤 위치 저장
window.addEventListener('beforeunload', () => {
saveCurrentScrollState();
});
}
},
tryRestore,
saveCurrentScrollState,
};
function initialize() {
if (disabled) {
return false;
}
if (window.history.scrollRestoration != null) {
window.history.scrollRestoration = 'manual';
}
// 현재 스크롤 위치 저장 후 상태 푸시
window.history.pushState = function() {
saveCurrentScrollState();
originalPushState.apply(window.history, arguments);
};
window.history.replaceState = function(state, ...otherArgs) {
const newState = Object.assign(
{},
state,
state.scrollLeft || state.scrollTop
? {
isReplaceState: true,
}
: null
);
originalReplaceState.apply(window.history, [newState].concat(otherArgs));
};
return true;
}
function saveCurrentScrollState() {
const state = window.history.state;
if (state != null && state.isReplaceState) return;
const newStateOfCurrentPage = Object.assign({}, state, {
scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
scrollTop: window.pageYOffset || document.documentElement.scrollTop,
});
originalReplaceState.call(window.history, newStateOfCurrentPage, '');
}
function deleteScrollState() {
const state = window.history.state;
delete state.scrollLeft;
delete state.scrollTop;
delete state.isReplaceState;
originalReplaceState.call(window.history, state, '');
}
// 스크롤바 두께 계산
function getScrollbarWidth() {
let outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.width = '100px';
outer.style.msOverflowStyle = 'scrollbar';
document.body.appendChild(outer);
let widthNoScroll = outer.offsetWidth;
// force scrollbars
outer.style.overflow = 'scroll';
// add innerdiv
let inner = document.createElement('div');
inner.style.width = '100%';
outer.appendChild(inner);
let widthWithScroll = inner.offsetWidth;
// remove divs
outer.parentNode.removeChild(outer);
return widthNoScroll - widthWithScroll;
}
function goToPosition(x, y) {
if (window.scrollTo != null) {
window.scrollTo(x, y);
} else {
window.pageXOffset = x;
window.pageYOffset = y;
}
}
// 중간에 사용자 스크롤이 존재하는 경우 상태 제거
function userScrollListener() {
window.removeEventListener('wheel', userScrollListener);
window.removeEventListener('touchmove', userScrollListener);
deleteScrollState();
clearTimeout(timeoutHandle);
}
// scrollTarget 까지 스크롤이 되도록 시도
function tryToScrollTo(scrollTarget) {
// Stop any previous calls to "tryToScrollTo".
window.removeEventListener('wheel', userScrollListener);
window.removeEventListener('touchmove', userScrollListener);
clearTimeout(timeoutHandle);
const body = document.body;
const html = document.documentElement;
if (!scrollBarWidth) {
scrollBarWidth = getScrollbarWidth();
}
// From http://stackoverflow.com/a/1147768
const documentWidth = Math.max(
body.scrollWidth,
body.offsetWidth,
html.clientWidth,
html.scrollWidth,
html.offsetWidth
);
const documentHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
if (
(documentWidth + scrollBarWidth - window.innerWidth >= scrollTarget.x &&
documentHeight + scrollBarWidth - window.innerHeight >= scrollTarget.y) ||
Date.now() > scrollTarget.latestTimeToTry
) {
goToPosition(scrollTarget.x, scrollTarget.y);
} else {
goToPosition(scrollTarget.x, scrollTarget.y);
window.addEventListener('wheel', userScrollListener);
window.addEventListener('touchmove', userScrollListener);
timeoutHandle = setTimeout(() => tryToScrollTo(scrollTarget), TRY_TO_SCROLL_INTERVAL_MS);
}
}
// 이전 스크롤 위치로 스크롤 시도
function tryRestore() {
const state = window.history.state;
if (state && isFinite(state.scrollLeft) && isFinite(state.scrollTop)) {
tryToScrollTo({
x: state.scrollLeft,
y: state.scrollTop,
latestTimeToTry: Date.now() + SCROLL_RESTORATION_TIMEOUT_MS,
});
if (state.isReplaceState) {
deleteScrollState();
}
}
}
return main;
})));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment