From 37d303ed66095e78da424326743d41b4a8d7c397 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 11:27:13 -0500 Subject: [PATCH 1/4] Save scroll position with history Save scroll position when navigating away from a page, and restore it when navigating back to that page. Fixes #30. --- index.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index eb2f853..3895d6b 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,6 @@ var defaultSwitches = require("./lib/switches") var Pjax = function(options) { - this.firstrun = true this.state = { numPendingSwitches: 0, href: null, @@ -35,6 +34,7 @@ var Pjax = function(options) { opt.title = st.state.title opt.history = false opt.requestOptions = {}; + opt.scrollPos = st.state.scrollPos if (st.state.uid < this.lastUid) { opt.backward = true } @@ -160,6 +160,17 @@ Pjax.prototype = { return } + // push scroll position to history + var currentState = window.history.state || {} + window.history.replaceState({ + url: currentState.url || window.location.href, + title: currentState.title || document.title, + uid: currentState.uid || newUid(), + scrollPos: [document.documentElement.scrollLeft || document.body.scrollLeft, + document.documentElement.scrollTop || document.body.scrollTop] + }, + document.title, window.location) + // Clear out any focused controls before inserting new page contents. document.activeElement.blur() @@ -218,13 +229,13 @@ Pjax.prototype = { var state = this.state if (state.options.history) { - if (this.firstrun) { + if (!window.history.state) { this.lastUid = this.maxUid = newUid() - this.firstrun = false window.history.replaceState({ url: window.location.href, title: document.title, - uid: this.maxUid + uid: this.maxUid, + scrollPos: 0 }, document.title) } @@ -235,7 +246,8 @@ Pjax.prototype = { window.history.pushState({ url: state.href, title: state.options.title, - uid: this.maxUid + uid: this.maxUid, + scrollPos: 0 }, state.options.title, state.href) @@ -250,15 +262,20 @@ Pjax.prototype = { state.options.analytics() - // Scroll page to top on new page load - if (state.options.scrollTo !== false) { - if (state.options.scrollTo.length > 1) { - window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]) - } - else { - window.scrollTo(0, state.options.scrollTo) + if (state.options.history) { + // Scroll page to top on new page load + if (state.options.scrollTo !== false) { + if (state.options.scrollTo.length > 1) { + window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]) + } + else { + window.scrollTo(0, state.options.scrollTo) + } } } + else { + window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]) + } this.state = { numPendingSwitches: 0, From bc2432b18c0f0d99944a42e9418f0fe6e8460e48 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Sun, 21 Jan 2018 19:04:50 -0500 Subject: [PATCH 2/4] Scroll to element position when URL contains a hash When the URL contains a hash, try to find the corresponding element, and if found, scroll to its position. Based on darylteo/pjax@4893a2a6574b3490396f83a1863499e8a9ba171e Fixes #22. --- index.js | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 3895d6b..7bd2c4c 100644 --- a/index.js +++ b/index.js @@ -174,6 +174,7 @@ Pjax.prototype = { // Clear out any focused controls before inserting new page contents. document.activeElement.blur() + var oldHref = href if (request.responseURL) { if (href !== request.responseURL) { href = request.responseURL @@ -185,6 +186,17 @@ Pjax.prototype = { else if (request.getResponseHeader("X-XHR-Redirected-To")) { href = request.getResponseHeader("X-XHR-Redirected-To") } + + // Add back the hash if it was removed + var a = document.createElement("a") + a.href = oldHref + var oldHash = a.hash + a.href = href + if (oldHash && !a.hash) { + a.hash = oldHash + href = a.href + } + this.state.href = href this.state.options = clone(options) @@ -263,8 +275,29 @@ Pjax.prototype = { state.options.analytics() if (state.options.history) { - // Scroll page to top on new page load - if (state.options.scrollTo !== false) { + // First parse url and check for hash to override scroll + var a = document.createElement("a") + a.href = this.state.href + if (a.hash) { + var name = a.hash.slice(1) + name = decodeURIComponent(name) + + var curtop = 0 + var target = document.getElementById(name) || document.getElementsByName(name)[0] + if (target) { + // http://stackoverflow.com/questions/8111094/cross-browser-javascript-function-to-find-actual-position-of-an-element-in-page + if (target.offsetParent) { + do { + curtop += target.offsetTop + + target = target.offsetParent + } while (target) + } + } + window.scrollTo(0, curtop); + } + else if (state.options.scrollTo !== false) { + // Scroll page to top on new page load if (state.options.scrollTo.length > 1) { window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]) } From 546b9abba3f9c453de349c68260dbb912983f343 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 17:32:59 -0500 Subject: [PATCH 3/4] Small bug fix --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 7bd2c4c..5655c91 100644 --- a/index.js +++ b/index.js @@ -247,7 +247,7 @@ Pjax.prototype = { url: window.location.href, title: document.title, uid: this.maxUid, - scrollPos: 0 + scrollPos: [0, 0] }, document.title) } @@ -259,7 +259,7 @@ Pjax.prototype = { url: state.href, title: state.options.title, uid: this.maxUid, - scrollPos: 0 + scrollPos: [0, 0] }, state.options.title, state.href) From e7935d9c7412e75adf07e9b41471e937bd17eb43 Mon Sep 17 00:00:00 2001 From: Behind The Math Date: Mon, 22 Jan 2018 18:56:22 -0500 Subject: [PATCH 4/4] Add scrollRestoration option --- README.md | 4 ++++ index.js | 2 +- lib/proto/parse-options.js | 1 + tests/lib/proto/parse-options.js | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18a2c93..ec01cff 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,10 @@ It's called every time a page is switched, even for history buttons. Value (in px) to scrollTo when a page is switched. +##### `scrollRestoration` (Boolean, default true) + +When set to true, attempt to restore the scroll position when navigating backwards or forwards. + ##### `cacheBust` (Boolean, default true) When set to true, diff --git a/index.js b/index.js index 5655c91..5bb7a4e 100644 --- a/index.js +++ b/index.js @@ -306,7 +306,7 @@ Pjax.prototype = { } } } - else { + else if (state.options.scrollRestoration && state.options.scrollPos) { window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]) } diff --git a/lib/proto/parse-options.js b/lib/proto/parse-options.js index fce3ec8..37c16c7 100644 --- a/lib/proto/parse-options.js +++ b/lib/proto/parse-options.js @@ -24,6 +24,7 @@ module.exports = function(options) { this.options.cacheBust = (typeof this.options.cacheBust === "undefined") ? true : this.options.cacheBust this.options.debug = this.options.debug || false this.options.timeout = this.options.timeout || 0 + this.options.scrollRestoration = (typeof this.options.scrollRestoration !== "undefined") ? this.options.scrollRestoration : true // we can’t replace body.outerHTML or head.outerHTML // it create a bug where new body or new head are created in the dom diff --git a/tests/lib/proto/parse-options.js b/tests/lib/proto/parse-options.js index 2b79435..8f57a65 100644 --- a/tests/lib/proto/parse-options.js +++ b/tests/lib/proto/parse-options.js @@ -53,6 +53,7 @@ tape("test parse initalization options function", function(t) { t.deepEqual(body1.options.scrollTo, 0); t.deepEqual(body1.options.cacheBust, true); t.deepEqual(body1.options.debug, false); + t.deepEqual(body1.options.scrollRestoration, true) t.end(); });