From 23060c9afde94d9c0810da386e50ba4aacbeed7a Mon Sep 17 00:00:00 2001 From: Mate Solymosi Date: Mon, 14 Jan 2019 16:00:37 +0100 Subject: [PATCH] feat(scroll): smarter scroll behavior --- src/core/event/index.js | 21 ++++++++++++++++++--- src/core/fetch/index.js | 7 +++++-- src/core/render/index.js | 6 ++---- src/core/router/history/hash.js | 20 +++++++++++++++++++- src/core/router/history/html5.js | 6 ++++-- src/core/router/index.js | 7 ++++--- 6 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/core/event/index.js b/src/core/event/index.js index 596d3debb..4a312421e 100644 --- a/src/core/event/index.js +++ b/src/core/event/index.js @@ -1,11 +1,26 @@ import { isMobile } from '../util/env'; import { body, on } from '../util/dom'; import * as sidebar from './sidebar'; -import { scrollIntoView } from './scroll'; +import { scrollIntoView, scroll2Top } from './scroll'; export function eventMixin(proto) { - proto.$resetEvents = function() { - scrollIntoView(this.route.path, this.route.query.id); + proto.$resetEvents = function(source) { + const { auto2top } = this.config; + + (() => { + // Rely on the browser's scroll auto-restoration when going back or forward + if (source === 'history') { + return; + } + // Scroll to ID if specified + if (this.route.query.id) { + scrollIntoView(this.route.path, this.route.query.id); + } + // Scroll to top if a link was clicked and auto2top is enabled + if (source === 'navigate') { + auto2top && scroll2Top(auto2top); + } + })(); if (this.config.loadNavbar) { sidebar.getAndActive(this.router, 'nav'); diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index 14c7fc888..28fb84434 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -148,7 +148,10 @@ export function fetchMixin(proto) { } }; - proto.$fetch = function(cb = noop) { + proto.$fetch = function( + cb = noop, + $resetEvents = this.$resetEvents.bind(this) + ) { const done = () => { callHook(this, 'doneEach'); cb(); @@ -160,7 +163,7 @@ export function fetchMixin(proto) { done(); } else { this._fetch(() => { - this.$resetEvents(); + $resetEvents(); done(); }); } diff --git a/src/core/render/index.js b/src/core/render/index.js index ca8e8f56b..9b50dc708 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -6,7 +6,7 @@ import { getAndActive, sticky } from '../event/sidebar'; import { getPath, isAbsolutePath } from '../router/util'; import { isMobile, inBrowser } from '../util/env'; import { isPrimitive } from '../util/core'; -import { scrollActiveSidebar, scroll2Top } from '../event/scroll'; +import { scrollActiveSidebar } from '../event/scroll'; import { Compiler } from './compiler'; import * as tpl from './tpl'; import { prerenderEmbed } from './embed'; @@ -123,7 +123,7 @@ export function renderMixin(proto) { }; proto._bindEventOnRendered = function(activeEl) { - const { autoHeader, auto2top } = this.config; + const { autoHeader } = this.config; scrollActiveSidebar(this.router); @@ -136,8 +136,6 @@ export function renderMixin(proto) { dom.before(main, wrapper.children[0]); } } - - auto2top && scroll2Top(auto2top); }; proto._renderNav = function(text) { diff --git a/src/core/router/history/hash.js b/src/core/router/history/hash.js index 1e099eacf..166af9ab5 100644 --- a/src/core/router/history/hash.js +++ b/src/core/router/history/hash.js @@ -30,7 +30,25 @@ export class HashHistory extends History { } onchange(cb = noop) { - on('hashchange', cb); + // The hashchange event does not tell us if it originated from + // a clicked link or by moving back/forward in the history; + // therefore we set a `navigating` flag when a link is clicked + // to be able to tell these two scenarios apart + let navigating = false; + + on('click', e => { + const el = e.target.tagName === 'A' ? e.target : e.target.parentNode; + + if (el.tagName === 'A' && !/_blank/.test(el.target)) { + navigating = true; + } + }); + + on('hashchange', e => { + const source = navigating ? 'navigate' : 'history'; + navigating = false; + cb({ event: e, source }); + }); } normalize() { diff --git a/src/core/router/history/html5.js b/src/core/router/history/html5.js index 4ae3b8d83..d0b2a9a31 100644 --- a/src/core/router/history/html5.js +++ b/src/core/router/history/html5.js @@ -28,11 +28,13 @@ export class HTML5History extends History { e.preventDefault(); const url = el.href; window.history.pushState({ key: url }, '', url); - cb(); + cb({ event: e, source: 'navigate' }); } }); - on('popstate', cb); + on('popstate', e => { + cb({ event: e, source: 'history' }); + }); } /** diff --git a/src/core/router/index.js b/src/core/router/index.js index 6967b20b8..1cb2f67d8 100644 --- a/src/core/router/index.js +++ b/src/core/router/index.js @@ -2,6 +2,7 @@ import { supportsPushState } from '../util/env'; import * as dom from '../util/dom'; import { HashHistory } from './history/hash'; import { HTML5History } from './history/html5'; +import { noop } from '../util/core'; export function routerMixin(proto) { proto.route = {}; @@ -31,16 +32,16 @@ export function initRouter(vm) { lastRoute = vm.route; // eslint-disable-next-line no-unused-vars - router.onchange(_ => { + router.onchange(params => { updateRender(vm); vm._updateRender(); if (lastRoute.path === vm.route.path) { - vm.$resetEvents(); + vm.$resetEvents(params.source); return; } - vm.$fetch(); + vm.$fetch(noop, vm.$resetEvents.bind(vm, params.source)); lastRoute = vm.route; }); }