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 eb2f853..5bb7a4e 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,9 +160,21 @@ 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() + var oldHref = href if (request.responseURL) { if (href !== request.responseURL) { href = request.responseURL @@ -174,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) @@ -218,13 +241,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, 0] }, document.title) } @@ -235,7 +258,8 @@ Pjax.prototype = { window.history.pushState({ url: state.href, title: state.options.title, - uid: this.maxUid + uid: this.maxUid, + scrollPos: [0, 0] }, state.options.title, state.href) @@ -250,15 +274,41 @@ 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]) + if (state.options.history) { + // 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 { - window.scrollTo(0, state.options.scrollTo) + 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]) + } + else { + window.scrollTo(0, state.options.scrollTo) + } } } + else if (state.options.scrollRestoration && state.options.scrollPos) { + window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]) + } this.state = { numPendingSwitches: 0, 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(); });