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

/* @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 popstate
  if ('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/inforList
  const 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)
    }
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WARN "css.modules" option in vue.config.js is deprecated now, please use "css.requireModuleExtension" instead. INFO Starting development server... 98% after emitting CopyPlugin WARNING Compiled with 17 warnings 09:43:57 warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'computed' was not found in 'vue' warning in ./src/router/index.js "export 'default' (imported as 'VueRouter') was not found in 'vue-router' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'defineComponent' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'getCurrentInstance' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'h' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'inject' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'nextTick' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'onActivated' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'onDeactivated' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'onUnmounted' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'provide' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'reactive' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'ref' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'shallowRef' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'unref' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'watch' was not found in 'vue' warning in ./node_modules/vue-router/dist/vue-router.mjs "export 'watchEffect' was not found in 'vue'这个报错因为什么
06-09

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值