当前位置: 代码迷 >> 综合 >> vue-router3 源码注释系列 /src/util/scroll.js
  详细解决方案

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

热度:51   发布时间:2023-10-26 11:36:21.0
/* @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)}}
}
  相关解决方案