Vant2 源码分析之 vant-sticky

这篇具有很好参考价值的文章主要介绍了Vant2 源码分析之 vant-sticky。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

借鉴 vant-sticky 源码,实现业务需求的某个功能时,第一眼看以为看懂了,拿来用的时候,才发现一知半解。看第二遍时,对不起,是我肤浅了。这里侧重分析实现原理,其他部分不拓展。一起研读源码,交流心得吧 ~

会分析这三个的源码实现,因为项目用的 Vue2,故参考 Vant2 的 v2.12.54 版本,

Vant2 源码分析之 vant-sticky
而该版本未实现 Vant3 的吸底距离功能,故不做分析。
Vant2 源码分析之 vant-sticky
如果只关注实现原理,不关注每个部分实现细节的话,可以跳到 onScroll 滚动事件部分。

项目启动和调试

clone 项目:

git clone https://github.com/youzan/vant.git

切换版本:

git checkout v2.12.54

安装和启动项目:

Vant2 源码分析之 vant-sticky

npm run bootstrap
npm run dev

调试过程中,可以打印计算值,帮助理解

源码分析

找到 vant-sticky 目录后,开始我们的源码分析吧

Vant2 源码分析之 vant-sticky

html 部分

render() {
    const { fixed } = this;
    const style = {
      height: fixed ? `${this.height}px` : null,
    };
    return (
      <div style={style}> // 1 注意这里是 style
        // bem({ fixed }) 
        // fixed 为 true 生成的是 'vant-sticky--fixed‘
        // 否则生成的是 'vant-sticky‘
        <div class={bem({ fixed })} style={this.style}> // 2 这里是 this.style
          {this.slots()}
        </div>
      </div>
    );
  }

标记说明:

1 为包裹元素 用于占位,因为内部元素 class=‘vant-sticky–fixed’ 是用 fixed 实现的,会脱离文档流。
2 class 和 style 都是根据 fixed 去决定是否展示。而 style 是计算属性,动态变化的。

这里学习到的是:

  • 元素使用 fixed 时,为了不影响滚动效果,布局错乱,可以包裹一个父元素去保持占位。
  • 由同个变量去控制一个元素的样式变化,而静态的样式放到 class 里,动态的放到 style 里。

css 部分

@import '../style/var';

.van-sticky {
  &--fixed {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    z-index: @sticky-z-index;
  }
}

@import ‘…/style/var’ 定义了 less 变量,@sticky-z-index: 99;

  computed: {
    // 将 px vw vh rem 单位传值转换为 px
    offsetTopPx() {
      return unitToPx(this.offsetTop);
    },
    // style 动态响应 dom 元素
    style() {
      // 由 this.fixed 控制 style 展现与否
      if (!this.fixed) {
        // 返回空,就不设置 style 了
        return;
      }

      const style = {};

      if (isDef(this.zIndex)) {
        // 修改层级,vant 默认在 vant-sticky--fixed 里变量定义为 99,这里通过传参修改
        style.zIndex = this.zIndex; 
      }

      if (this.offsetTopPx && this.fixed) {
        // offsetTopPx 赋值给 top,来设置偏移量
        style.top = `${this.offsetTopPx}px`;
      }

      if (this.transform) {
        style.transform = `translate3d(0, ${this.transform}px, 0)`;
      }

      return style;
    },
  },

props 和 data 部分

简单看下传值和变量定义部分

  props: {
    zIndex: [Number, String], // 吸顶时的 z-index
    container: null, // 容器对应的 HTML 节点,类型 Element
    offsetTop: { // 吸顶时与顶部的距离,支持 px vw vh rem 单位,默认 px
      type: [Number, String],
      default: 0,
    },
  },

  data() {
    return {
      fixed: false,
      height: 0, // 元素本身高度
      transform: 0, // 偏移量,只在有容器,且展示吸底效果时,有用到
    };
  },

初始的生命周期部分

created 生命周期

created() {
    // compatibility: https://caniuse.com/#feat=intersectionobserver
    // vant2 使用 SSR 写的,故有 isServer 是否在服务器运行的判断
    // window.IntersectionObserver ie11 不支持
    if (!isServer && window.IntersectionObserver) {
      this.observer = new IntersectionObserver(
        // entries是一个数组,每个成员都是一个 IntersectionObserverEntry 对象
        // 有几个被观察的成员就有几个对象
        (entries) => {
          // 每次元素进入可视区 或 离开可视区时 触发
          if (entries[0].intersectionRatio > 0) {
            this.onScroll();
          }
        },
        // root 属性指定目标元素所在的容器节点(即根元素)
        { root: document.body }
      );
    }
  },

Window.IntersectionObserver 自动观察元素是否可见(本质是目标元素与视口产生一个交叉区,只有线程空闲下来,才会执行观察器), 详见 阮一峰的 IntersectionObserver API 使用教程

后续会用到,虽然即便把 IntersectionObserver 相关部分全都注释掉,也不影响使用。

IntersectionObserver 食用说明:

// 用法
this.observer = new IntersectionObserver(callback, option)

// 开始观察
this.observer.observe(this.$el);

// 停止观察
this.observer.unobserve(this.$el);

// 关闭观察器
this.observer.disconnect();

接下来先了解下 BindEventMixin 函数,用于混入生命周期函数 mounted、activated、deactivated、beforeDestroy 以绑定和取消监听传入的 handler 事件

import { on, off } from '../utils/dom/event';

let uid = 0;
// 入参 handler 是个函数
export function BindEventMixin(handler) {
  const key = `binded_${uid++}`; // 记录绑定
  
  function bind() {
    if (!this[key]) { // 没有绑定
      handler.call(this, on, true); // 把 on(即 addEventListener)传给 handler,第三个参数是告知 handler 当前状态是否绑定
      this[key] = true; // 标记绑定
    }
  }

  function unbind() {
    if (this[key]) { // 绑定了,则取消监听事件
      handler.call(this, off, false); // 把 off (即 removeEventListener )传给 handler
      this[key] = false; // 标记w未绑定
    }
  }
  // 通过 mixins,混入生命周期函数,以绑定和取消监听事件
  return {
    mounted: bind, 
    activated: bind,
    deactivated: unbind,
    beforeDestroy: unbind,
  };
}

mixins :用于混入生命周期钩子

mixins: [
    BindEventMixin(function (bind, isBind) { 
      if (!this.scroller) {
        this.scroller = getScroller(this.$el); // getScroller 从当前元素一直向上找到带有滚动属性的元素
      }
      // this.observer 是 IntersectionObserver 的实例
      if (this.observer) {
        // 当 mounted|activated 时,isBind 为 true,开始观察
        // 当取消监听时,isBind 为 false,停止观察
        const method = isBind ? 'observe' : 'unobserve'; 
        this.observer[method](this.$el);
      }
      // bind 即为 on( addEventListener)
      bind(this.scroller, 'scroll', this.onScroll, true);
      this.onScroll();
    }),
  ],

这里能借鉴的是,我们也可以通过 mixins 的方式去自动的绑定和取消监听事件。前提是,需要一开始载入便监听的且符合这些生命周期。同时 watch 某个数据变化,去手动的监听和取消监听就不太适用了。当然,也可以依据情况改造下函数。

onScroll 滚动事件部分

概念

我们先弄明白后续计算会用到的几个概念
scrollTop 是滚动的距离,红箭头是 window.scrollTop 滚动的距离
Vant2 源码分析之 vant-sticky

getBoundingClientRect() 提供了元素的大小及其相对于视口的位置
红箭头是 el.getBoundingClientRect().top 的距离
Vant2 源码分析之 vant-sticky
可以发现,在向上滚动的过程中,window.scrollTop 不断增加,el.getBoundingClientRect().top 不断减少。而增加的部分刚好等于减少的部分。

如果元素的顶部超出视口,那么 el.getBoundingClientRect().top 为负值,window.scrollTop 还是不断增加。

可以得出,在滚动的过程中, el.getBoundingClientRect().top + window.scrollTop 的值始终是不变的。 而一开始 window.scrollTop 为 0,因此两者相加的值其实始终为一开始 el.getBoundingClientRect().top 的值,也就是元素顶部到视口顶部的距离。

offsetHeight 是一个元素本身的高度 + padding+border+滚动条,不包括伪元素

Vant2 源码分析之 vant-sticky
因此 el.getBoundingClientRect().top + window.scrollTop + el.offsetHeight 的含义是元素的初始位置的底部到视口顶部的距离

有了上面的理论基础后,接下来是重中之重的 onScroll 滚动事件部分,先从 1、2 情况讲起

Vant2 源码分析之 vant-sticky

实现原理
关键代码:scrollTop + offsetTopPx > topToPageTop

当页面滚动距离 + 偏移量 > 目标元素一开始距离顶部的距离,目标元素设置 fixed 属性,吸顶。其中偏移量,是通过设置 props 的 offsetTop 属性去偏移。

反过来,意味着滚回去了,那么移除 fixed 属性

  methods: {
    onScroll() {
      // 判断当前元素,及祖先元素是否隐藏了,隐藏了就不需要滚动了
      if (isHidden(this.$el)) {
        return;
      }
      
	  // 当前元素的高度,可用于占位,一直不变的
      this.height = this.$el.offsetHeight;
      
      const { container, offsetTopPx } = this;
      // window 滚动的距离 window.scrollTop
      const scrollTop = getScrollTop(window);

      // getElementTop() 返回 el.getBoundingClientRect().top + window.scrollTop
      // 上面分析过,保持不变,是一开始元素的顶部与视口顶部的距离
      const topToPageTop = getElementTop(this.$el);

      const emitScrollEvent = () => {
        this.$emit('scroll', {
          scrollTop,
          isFixed: this.fixed,
        });
      };

      // 先注释掉该部分后面讲解,目前的部分足够实现 1 2 效果
      // if (container) {
      //   ... 
      // }
      
      // 当滚动距离达到指定上限:页面滚动的距离 + 偏移 > 一开始元素的顶部与视口顶部的距离(这里面是有包括 offsetTop 的高度的) 
      // 这里也可以反过来想, 一开始元素的顶部与视口顶部的距离 - 偏移 为最多可以滚动的距离
      // offsetTopPx 偏移,来自设置的 props 的 offsetTop
      
      if (scrollTop + offsetTopPx > topToPageTop) {
        this.fixed = true; // 设置 fixed 属性,目标元素视口吸顶
        this.transform = 0; // 重置因吸底容器效果而产生的偏移 transform,后面会提到。
      } else {
        // 当滚回顶部时,取消 fixed
        this.fixed = false;
      }

      emitScrollEvent();
    },
  }

接下来,分析 3 指定容器的情况

Vant2 源码分析之 vant-sticky
和 1 2 情况不同的是,当不断向下滚动,元素会被容器带走,在这之前视口顶部到容器底部的距离,小于目标元素高度 + 偏移量时,会吸底容器,如下图。

而其他部分逻辑的代码实现和 1 2 情况相同。

Vant2 源码分析之 vant-sticky

有个问题是,如果在容器和元素之间再放个元素,是否也有吸底效果呢?

Vant2 源码分析之 vant-sticky

代码如下

<div ref="container" style="height: 150px; background-color: #fff">
  <van-button type="warning">假容器</van-button>
  <van-sticky :container="container" :offset-top="20">
    <van-button type="warning" style="margin-left: 215px">指定容器</van-button>
  </van-sticky>

类似这种

Vant2 源码分析之 vant-sticky

超出容器边界了,不是我们想要的效果。看起来,这一版并不支持上述情况。因此,我们接下来的源码分析,默认目标元素一开始的位置是在容器边缘

Vant2 源码分析之 vant-sticky

实现原理
scrollTop + offsetTopPx + this.height > bottomToPageTop
当页面滚动距离 + 偏移 + 目标元素高度,超出了容器一开始的底部到视口顶部的距离

如果超出部分小于元素高度,则展示吸底效果。设置 fixed 吸顶,在通过 transfom 向上移动超出的距离,以达到吸底容器的效果。

如果完全超出元素高度,则消除所有静态、动态样式,回到原样。

下面部分代码,便是上述特殊吸底情况的分析。

  if (container) {
    // 借鉴上面的分析,排除不支持的情况后
    // topToPageTop 为一开始目标元素顶部到视口顶部的距离(涵盖偏移量)
    // container.offsetHeight 容器自身的高度
    // bottomToPageTop 为容器一开始从底部到视口顶部的距离
    const bottomToPageTop = topToPageTop + container.offsetHeight;
    
    // 页面滚动的距离 + 偏移 + 目标元素的高度 > 容器一开始从底部到顶部的距离
    // 其中 this.height = this.$el.offsetHeight;
    // 意味着,如果保持 fixed 的状态,目标元素会超出容器底部,这时候应该让它吸底
    if (scrollTop + offsetTopPx + this.height > bottomToPageTop) {
      // 目标元素超出底部的距离 = 目标元素高度 + 页面滚动距离 - 容器一开始的底部到顶部的距离
      // distanceToBottom 为什么不减去偏移呢?因为此时视觉上已经超出容器底部了,不需要管偏移,而是要吸附容器底部了
      const distanceToBottom = this.height + scrollTop - bottomToPageTop;
      // 超出距离 < 元素高度
      // 没有全部超出,元素吸底展示
      if (distanceToBottom < this.height) {
        // 给个 fixed 吸顶,通过调整 transform 往上移动使得视觉上元素到了容器的底部
        this.fixed = true;
        // 需往上移动的距离为,超出的距离 + offsetTop 值的大小(抵消掉 top 值,因为原先的 style.top 值还在)
        this.transform = -(distanceToBottom + offsetTopPx);
      } else {
        // 完全超出,解除 fixed
        // 意味着 class='van-sticky--fixed' 删除,动态的 style 返回 {} 
        this.fixed = false;
      }

      emitScrollEvent();
      return;
    }

在理解了上述原理后,为我们的业务增效吧。动手之前多思考,生搬硬套不可取。好用的方法是,先把逻辑写下来,理清楚,再一步步去攻克和实现。文章来源地址https://www.toymoban.com/news/detail-494338.html

到了这里,关于Vant2 源码分析之 vant-sticky的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序使用scroll-view导致吸顶无效或vant-sticky吸顶无效

    我们先清楚为什么要使用scroll-view? 在做回到顶部时,需要拿到实时的滚动距离,这时候就得使用上scroll-view了 记录一次在做 微信小程序开发时,在没有使用scroll-view的时候,我们使用vant-sticky做吸顶效果的时候可以正常使用。 但是当我们使用scroll-view时,突然发现吸顶失效

    2024年02月09日
    浏览(55)
  • vue2+vant2+rem+axios+钉钉自动登录 h5模板

    请轻轻的点一下这里~ Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。你可以使用 nvm 或 nvm-windows 在同一台电脑中管理多个 Node 版本。 本示例 Node.js 14.17.0 对应配置 .env.staging config/env.staging.js 对应配置 .env.production config/env.production.js package.json 里的 scripts 配置 serve stage build ,通

    2024年02月03日
    浏览(34)
  • springboot+mybatis-plus+vue+element+vant2实现短视频网站,模拟西瓜视频移动端

    目录 一、前言 二、管理后台 1.登录 2.登录成功,进入欢迎页 ​编辑  3.视频分类管理 4. 视频标签管理 5.视频管理  6.评论管理 ​编辑 7.用户管理 8.字典管理 (类似于后端的枚举)  9.参数管理(富文本录入)  10.管理员管理  三、移动端  1.首页  2.视频详情 3.视频评论

    2024年02月15日
    浏览(43)
  • 关于vant2 组件van-dropdown-item,在IOS手机上,特定条件下无法点击问题的探讨

    先贴有问题的代码 样式 van-dropdown-menu .van-dropdown-menu__bar 这一行是对组件内的样式进行了修改 上个图直观一些 右上角人名可以切换,用到的就是van-dropdown-menu,这个在web,在android,都没有问题,但是在IOS机型上,有时候点击没反应。刚开始以为是不兼容,但是在某些情况下又

    2024年02月12日
    浏览(38)
  • 分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊

    2022年11月23日首发于掘金,现在同步到公众号。 11. 前言 大家好,我是若川。推荐点右上方蓝字若川视野把我的公众号 设为星标 。我倾力持续组织了一年多源码共读,感兴趣的可以加我微信 lxchuan12 参与。另外,想学源码,极力推荐关注我写的专栏《学习源码整体架构系列》

    2024年02月05日
    浏览(101)
  • vue2+vant 简易实现京东app商城(附源码)

    利用 vue2 + vant 模仿京东app商城,实现 首页 、 商品分类页面 、 购物车 、 简易商品详情页 、 登录页 。 ①、创建vue2项目 mobile ②、安装路由模块,vant组件 ①、在 src 目录下创建 views 文件夹,存放页面组件 views 结构如图 ②、在 src 目录下创建 router 文件夹,新建 index.js 文件

    2024年02月11日
    浏览(34)
  • mpVue 微信小程序基于vant-weapp 组件的二次封装TForm 表单组件(修改源码插槽使用)

    1、mpVue微信小程序不支持动态组件(component ) 2、mpVue微信小程序不支持动态属性及事件穿透( $attrs 和 $listeners ) 3、mpVue微信小程序不支持 render 函数 TForm 表单组件 代码示例: 参数 说明 类型 默认值 className 自定义类名 String - listTypeInfo 下拉选择数据源(type:\\\'date/datetime/ra

    2024年02月16日
    浏览(71)
  • Vue项目实战——【基于 Vue3.x + Vant UI】实现一个多功能记账本(项目演示、涉及知识点、源码分享)

    1、前言 如果你对 vue3.x 的基础知识还很陌生,推荐先去学习一下 vue 基础 内容 参考链接 Vue2.x全家桶 Vue2.x全家桶参考链接 Vue3.x知识一览 Vue3.x重点知识参考链接 如果你 刚学完 vue3 , 想检查一下自己的学习成果 如果你 已学完 vue3 , 想快速回顾复习所学知识 如果你 已精通

    2024年01月18日
    浏览(53)
  • 【vant】打开vant表单的正确形式(基于vant表单的二次封装)

    最近在用vant做关于移动端的项目,由于表单字段太多,不想写直接写到template中,这样太繁琐了,所以我们以把表单弄成schema配置形式: form组件使用: 就是通过数据驱动生成表单(效果如下)。 也可以戳链接亲自体验:vant_twice_form 这样不仅表单字段配置起来方便,更能解决一些业务

    2024年02月13日
    浏览(47)
  • Vant简介及创建Vue项目并使用Vant

                                      🔥 文档网站(国内) 🌈 文档网站(GitHub) Vant 是一个轻量、可定制的移动端组件库,于 2017 年开源。 目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本,并由社区团队维护React 版本和支付宝小程序版本。 🚀 性

    2024年02月04日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包