/* @flow */import type Router from '../index'
import { assert } from './warn'
//getStateKey: 用于获取时间戳key;
//setStateKey: 用于设置时间戳key;
import { getStateKey, setStateKey } from './state-key'
//浅拷贝对象的属性。
import { extend } from './misc'//用于保存对应的页面的 scrollposition 位置。
const positionStore = Object.create(null)export function setupScroll() {/*history.scrollRestoration。它提供两个值,auto,作为它的默认值,可以像你所见的大多数情况一样工作,另一个manual,意味着作为一个开发者你拥有了自主掌控任何所需的scroll改变,当用户循环往复于app的history中。如果需要,你可以跟踪scroll的位置轨迹,当你使用history.pushState(),push history的时候。*///用于组织浏览器在 history popstate 事件中自动滚动窗口。// Prevent browser scroll behavior on History popstateif ('scrollRestoration' in window.history) {window.history.scrollRestoration = 'manual'}// Fix for #1585 for Firefox// Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678// Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with// window.location.protocol + '//' + window.location.host// location.host contains the port and location.hostname doesn't//浏览器地址为: http://192.168.10.73:10095/message/inforList//protocolAndPath: "http://192.168.10.73:10095"const protocolAndPath = window.location.protocol + '//' + window.location.host//absolutePath: message/inforListconst absolutePath = window.location.href.replace(protocolAndPath, '')// preserve existing history state as it could be overriden by the user// stateCopy: {//  key: "777.900"// }const stateCopy = extend({}, window.history.state)//替换掉 stateCopy 的 key 的值。stateCopy.key = getStateKey()//repalce 方式跳转到 absolutePath 路径。window.history.replaceState(stateCopy, '', absolutePath)//开始监听 history 的 popstate 事件。window.addEventListener('popstate', handlePopState)return () => {//移除对 history 的 popstate 事件的监听。window.removeEventListener('popstate', handlePopState)}
}/*handleScroll() 函数router: 路由对象。to:     前往的路由from:   上一个路由。isPop:  是否是退出。
*/
export function handleScroll(router: Router,to: Route,from: Route,isPop: boolean
) {//不存在 router 当前指向的 vue 实例。if (!router.app) {return}//获取创建 router 时,配置的 scrollBehavior 函数。const behavior = router.options.scrollBehavior//如果 scrollBehavior 没有配置,则直接返回。if (!behavior) {return}//scrollBehavior 必须是一个函数。否则警告提示。if (process.env.NODE_ENV !== 'production') {assert(typeof behavior === 'function', `scrollBehavior must be a function`)}// wait until re-render finishes before scrolling//在下一个 event loop 周期执行一下函数。router.app.$nextTick(() => {//获取最后一次保存的滚动位置记录。const position = getScrollPosition()//shouldScroll 是对于滚动位置的配置。const shouldScroll = behavior.call(router,to,from,isPop ? position : null)//如果shouldScroll 为 null。只直接返回。if (!shouldScroll) {return}//如果 shouldScroll 是 prommise 对象。if (typeof shouldScroll.then === 'function') {//通过 promise.then() 的方式获取要滚动的坐标。shouldScroll.then((shouldScroll) => {scrollToPosition((shouldScroll: any), position)}).catch((err) => {//如果不是生产环境,且产生了异常,则输出错误信息。if (process.env.NODE_ENV !== 'production') {assert(false, err.toString())}})} else {//滚动到指定的位置。// shouldScroll: 是关于滚动行为的配置。// position:     是上一次窗口滚动的位置。scrollToPosition(shouldScroll, position)}})
}/*** 保存当前滚动的位置。*/
export function saveScrollPosition() {//获取一个根据当前时间的时间戳生成的key。const key = getStateKey()//如果key存在if (key) {//则 positionStore[key] 形式记录滚动的x,y位置。positionStore[key] = {x: window.pageXOffset,y: window.pageYOffset,}}
}/* 用户触发了 popState 事件时,会调用 handlePopState() 方法。1、调用history.pushState()或者history.replaceState()不会触发popstate事件. 2、popstate事件只会在浏览器某些行为下触发, 比如:(1)用户主动触发的:点击后退、前进按钮。(2)代码主动触发:在JavaScript中调用history.back()、history.forward()、history.go()方法。
*/
function handlePopState(e) {//记录当前 window 滚动的位置。saveScrollPosition()//e.state 就是 pushState, replaceState 的第一个参数。if (e.state && e.state.key) {//更新存储的 key。setStateKey(e.state.key)}
}/*获取最后一次保存的滚动记录。
*/
function getScrollPosition(): ?Object {//获取时间戳生成的 key。const key = getStateKey()//如果 key 存在,返回以该 key 存储的滚动位置记录。if (key) {return positionStore[key]}
}/*el: 被 selector 选择器指定的 dom 节点。offset: 偏移量
*/
function getElementPosition(el: Element, offset: Object): Object {const docEl: any = document.documentElement//Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。const docRect = docEl.getBoundingClientRect()//获取被选中元素的元素的大小,以及相对于视口的位置。const elRect = el.getBoundingClientRect()return {//elRect.left - docRect.left 表示相对于网页 x 轴方向的间距。x: elRect.left - docRect.left - offset.x,//elRect.top - doctRect.top 表示相对于网页 y 轴方向的间距。y: elRect.top - docRect.top - offset.y,}
}/*isValidPosition() 判断是不是可用的坐标。只需要 x 或者 y 一个方向有值即可。
*/
function isValidPosition(obj: Object): boolean {//判断 x 或者 y 坐标有值,且不为0;return isNumber(obj.x) || isNumber(obj.y)
}/*归一化坐标位置。
*/
function normalizePosition(obj: Object): Object {//针对只需要 x 或者 y 方向滚动的坐标。不需要滚动的坐标进行数据补齐。return {x: isNumber(obj.x) ? obj.x : window.pageXOffset,y: isNumber(obj.y) ? obj.y : window.pageYOffset,}
}/*归一化偏移位置
*/
function normalizeOffset(obj: Object): Object {return {x: isNumber(obj.x) ? obj.x : 0,y: isNumber(obj.y) ? obj.y : 0,}
}/*判断是不是 number 类型的数据。
*/
function isNumber(v: any): boolean {return typeof v === 'number'
}//用于判断是不是 “#数字” 的形式开头的字符串的 的正则表达式对象。
const hashStartsWithNumberRE = /^#\d//***  position: 为上一次保存下来的滚动坐标。*  shouldScroll: 包含滚动位置,元素等的对象。 {*    selector: "xxx"  //如果指定了该属性,那么就是根据指定的dom元素的位置计算滚动位置。*    offset: { x, y } //指定 dom 元素的偏移量。*    x, //x坐标*    y, //y坐标*    behavior: "auto|smooth" //指定滚动行为。*  }**  会将滚动坐标转化为 window 窗口的滚动坐标。*/
function scrollToPosition(shouldScroll, position) {const isObject = typeof shouldScroll === 'object'//如果 shouldScroll.selector 存在, 且是字符串类型。// 则指定的滚动偏移位置,是针对 selector 指定的 dom 元素的偏移位置。if (isObject && typeof shouldScroll.selector === 'string') {//获取 selector 指定的锚地 dom 节点。const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line? //shouldScroll.selector.slice(1) 用于去掉 # 号document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line: document.querySelector(shouldScroll.selector)//dom元素存在if (el) {//获取 shouldScroll 的 offset,作为要滚动的坐标。let offset =shouldScroll.offset && typeof shouldScroll.offset === 'object'? shouldScroll.offset: {}//归一化偏移位置。offset = normalizeOffset(offset)//需要滚动到的坐标。position = getElementPosition(el, offset)} else if (isValidPosition(shouldScroll)) {//需要滚动的坐标。position = normalizePosition(shouldScroll)}//如果 shouldScroll 是对象类型,且x,y都是数字。} else if (isObject && isValidPosition(shouldScroll)) {position = normalizePosition(shouldScroll)}}/* 如果   scrollBehavior( to, from, savedPosition ){if( savedPosition ){return savedPosition;} else {return { x:0, y:0 }}}返回的是一个对象,则必须带有滚动的坐标。则不再使用 scrollToPosition() 的第二个参数的数据。如果返回的不是对象,则滚动的坐标就使用 scrollToPosition() 的第二个参数的数据。*///将两种滚动方式的 position 归一化为窗口滚动的坐标后if (position) {//判断当前浏览器是否支持 scrollBehavior 的 scroll-behavior 的属性。如果支持,则可以指定 behavior 属性。// scroll-behavior 属性的值有: smooth,auto; 其中 auto 是默认值。// (1) auto:   默认值,表示滚动框立即滚动到指定位置。// (2) smooth: 表示允许滚动时采用平滑过度,而不是直接滚动到相应的位置。if ('scrollBehavior' in document.documentElement.style) {window.scrollTo({left: position.x,top: position.y,behavior: shouldScroll.behavior,})} else {//将窗口滚动到 { x:"xxx", y:"xxx" } 的位置。window.scrollTo(position.x, position.y)}}
}

vue-router3 源码注释系列 /src/util/scroll.js相关推荐

  1. vue-router3 源码注释系列 /src/util/query.js

    /* @flow */import { warn } from './warn'//判断字符为 !'()* 的正则表达式. const encodeReserveRE = /[!'()*]/g /* ...

  2. vue-router3 源码注释系列 /src/util/push-state.js

    /* @flow *///用于判断是否是浏览器环境 import { inBrowser } from './dom' //保存滚动的位置(x,y). import { saveScrollPosit ...

  3. vue-router3 源码注释系列 /src/util/path.js

    /* @flow *//*** resolvePath(): 解析路径* 第一个参数: 相对路径,要跳转路径的 pathname.* 第二个参数: 基准路径.* 第三个参数: 是否需要拼接基准地址.* ...

  4. vue源码注释系列 /src/module/module.js

    import { forEachValue } from "../util";// Base data struct for store's module, package wit ...

  5. vue-router3源码注视系列 /src/index.js

    /* @flow *///install: vue.use( VueRouter ) 时调用的 install() 方法. import { install } from './install' // ...

  6. three.js 源码注释(一)./Three.js

    商域无疆 (http://blog.csdn.net/omni360/) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:商域无疆 -  本博客专注于 敏捷开发 ...

  7. vue 计算属性_lt;Vue 源码笔记系列6gt;计算属性 computed 的实现

    1. 前言 原文发布在语雀: <Vue 源码笔记系列6>计算属性 computed 的实现 · 语雀​www.yuque.com 上一章我们已经学习过 watch,这一章就来看一下计算属性 ...

  8. vue如何让一句代码只执行一次_lt;Vue 源码笔记系列4gt;异步更新队列与$nextTick...

    1. 前言 原文发布在语雀: <Vue 源码笔记系列4>异步更新队列与$nextTick · 语雀​www.yuque.com 上一章我们讲到了修改数据是如何触发渲染函数的观察者,最终调用 ...

  9. vue源码分析系列二:$mount()和new Watcher()的执行过程

    续vue源码分析系列一:new Vue的初始化过程 在initMixin()里面调用了$mount() if (vm.$options.el) {vm.$mount(vm.$options.el);/ ...

最新文章

  1. 如何才能成为java高级程序员?
  2. MySQL(五)汇总和分组数据
  3. vue 分享微信传参_vue实现微信分享链接添加动态参数的方法
  4. extundelete 简单使用
  5. BZOJ 4285 使者 (CDQ分治+dfs序)
  6. 这本Python算法书有点火~
  7. jdbc executebatch 非事务_jdbc技术
  8. RouterOS安装以及搭建DHCP PPPoE PPTP L2TP服务
  9. matlab拉格朗日曲线_数学中高耸的金字塔——拉格朗日
  10. Pandas模块,我觉得掌握这些就够用了!
  11. 用AngularJS开发下一代Web应用pdf
  12. maven安装配置之后mvn命令仍然无效的解决办法
  13. kettle-连接mysql数据库
  14. 用HTML创建幻灯片
  15. Java 一步一步教你在控制台打印出菱形
  16. LVGL开发 | lv_lib_100ask之lvgl数字对拼图小游戏(lv_100ask_memory_game)-接口简单、使用便捷
  17. (五)作业Job和实例Instance | 普罗米修斯(Prometheus)
  18. vue项目添加百度统计及设置埋点
  19. CPU/显卡GPU/CUDA/内存/缓存/SDK/API/DLL【转载整理】
  20. 给Rstudio修改背景和字体等设置教程

热门文章

  1. Java练习题一【数据类型,运算符】
  2. 恭喜发财! -- 手把手教你仿造一个qq下拉抢红包 Android自定义view
  3. Swagger 注解~用于Controller
  4. Android仿微信朋友圈发图片和文字
  5. Oracle创建视图时显示没有权限
  6. 利用代码一键完成京东全民养红包所有任务 金币升级脚本
  7. mysql的bigint类型_MySQL中的类型:BigInt(20)与Int(20)
  8. 华为P30系列开卖:10秒破2亿!
  9. Photoshop CS2 视频教程-PS移动选区(转)
  10. 一加七语音唤醒_一加语音助手怎么唤醒_在哪里设置