I've modified the native smoothscroll function playing with pushstate, popstate and localstorage
the result is that the scroll position is maintained on every browser I tested
d
function smoothScroll(content, viewport, smoothness) {
content = gsap.utils.toArray(content)[0];
smoothness = smoothness || 1.4;
gsap.set(viewport || content.parentNode, {overflow: "hidden", position: "fixed", height: "100%", width: "100%", top: 0, left: 0, right: 0, bottom: 0});
gsap.set(content, {overflow: "visible", width: "100%"});
function pushHistory(e){
e = e || window.event;
var target;
target = e.target || e.srcElement;
history.pushState({scrollTop:document.body.scrollTop},document.title,document.location.pathname);
}
if (document.body.addEventListener)
{
document.body.addEventListener('click',pushHistory,false);
}
else
{
document.body.attachEvent('onclick',pushHistory);
}
window.addEventListener('popstate', (event) => {
var scrolltop = localStorage.getItem("scrolltop");
if (scrolltop != undefined && scrolltop > 0){
gsap.to(window, {duration: 0, scrollTo: {y: scrolltop, autoKill: true}});
}
});
window.onpopstate = (event) => {
var scrolltop = localStorage.getItem("scrolltop");
if (scrolltop != undefined && scrolltop > 0){
gsap.to(window, {duration: 0, scrollTo: {y: scrolltop, autoKill: true}});
}
};
let getProp = gsap.getProperty(content),
setProp = gsap.quickSetter(content, "y", "px"),
setScroll = ScrollTrigger.getScrollFunc(window),
removeScroll = () => content.style.overflow = "visible",
killScrub = trigger => {
let scrub = trigger.getTween ? trigger.getTween() : gsap.getTweensOf(trigger.animation)[0]; // getTween() was added in 3.6.2
scrub && scrub.pause();
trigger.animation.progress(trigger.progress);
},
height, isProxyScrolling;
function refreshHeight() {
height = content.clientHeight;
content.style.overflow = "visible"
document.body.style.height = height + "px";
return height - document.documentElement.clientHeight;
}
ScrollTrigger.addEventListener("refresh", () => {
removeScroll();
requestAnimationFrame(removeScroll);
})
ScrollTrigger.defaults({scroller: content});
ScrollTrigger.prototype.update = p => p; // works around an issue in ScrollTrigger 3.6.1 and earlier (fixed in 3.6.2, so this line could be deleted if you're using 3.6.2 or later)
ScrollTrigger.scrollerProxy(content, {
scrollTop(value) {
if (arguments.length) {
isProxyScrolling = true; // otherwise, if snapping was applied (or anything that attempted to SET the scroll proxy's scroll position), we'd set the scroll here which would then (on the next tick) update the content tween/ScrollTrigger which would try to smoothly animate to that new value, thus the scrub tween would impede the progress. So we use this flag to respond accordingly in the ScrollTrigger's onUpdate and effectively force the scrub to its end immediately.
setProp(-value);
setScroll(value);
return;
}
return -getProp("y");
},
scrollHeight: () => document.body.scrollHeight,
getBoundingClientRect() {
return {top: 0, left: 0, width: window.innerWidth, height: window.innerHeight};
}
});
return ScrollTrigger.create({
animation: gsap.fromTo(content, {y:0}, {
y: () => document.documentElement.clientHeight - height,
ease: "none",
onUpdate: ScrollTrigger.update
}),
scroller: window,
//invalidateOnRefresh: true,
start: 0,
end: refreshHeight,
refreshPriority: -999,
scrub: smoothness,
onUpdate: self => { localStorage.setItem("scrolltop", self.scroll()); //save the value
if (isProxyScrolling) {
killScrub(self);
isProxyScrolling = false;
}
},
onRefresh: killScrub // when the screen resizes, we just want the animation to immediately go to the appropriate spot rather than animating there, so basically kill the scrub.
});
}