Last active
December 23, 2019 02:06
-
-
Save supernovel/8766c3db15132ff73831f0c2c66724da to your computer and use it in GitHub Desktop.
페이지 스크롤 저장 및 복구 (window.history 사용)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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