日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Vant2 源码分析之 vant-sticky

發(fā)布時間:2024/3/13 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vant2 源码分析之 vant-sticky 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

原打算借鑒 vant-sticky 源碼,實現(xiàn)業(yè)務(wù)需求的某個功能,第一眼看以為看懂了,拿來用的時候,才發(fā)現(xiàn)一知半解。看第二遍時,對不起,是我膚淺了。這里側(cè)重分析實現(xiàn)原理,其他部分不拓展開來,否則像滾雪球越滾越多了。一邊讀源碼,一邊學習使用技巧吧,這里記錄下心得感悟,和大家共勉。

接下來會分析這三個的源碼實現(xiàn),因為項目用的 Vue2,故參考 Vant2 的 v2.12.54 版本,


而該版本未實現(xiàn) Vant3 的吸底距離功能,故不做分析,同學們交給你們啦。

如果只關(guān)注實現(xiàn)原理,不關(guān)注每個部分實現(xiàn)細節(jié)的話,可以跳到 onScroll 滾動事件部分。

項目啟動和調(diào)試

clone 項目:

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

切換版本:

git checkout v2.12.54

安裝和啟動項目:

npm run bootstrap npm run dev

調(diào)試過程中,可以打印些計算值,幫助理解

源碼分析

找到 vant-sticky 目錄后,開始我們的源碼分析吧

html 部分

render() {const { fixed } = this;const style = {height: fixed ? `${this.height}px` : null,};return (<div style={style}> // 1// bem({ fixed }) 生成 'vant-sticky--fixed'<div class={bem({ fixed })} style={this.style}> // 2{this.slots()}</div></div>);}

1 為包裹元素 用于占位,因為內(nèi)部元素 class=‘vant-sticky–fixed’ 是用 fixed 實現(xiàn)的,會脫離文檔流。
2 class 和 style 都是根據(jù) fixed 去決定是否展示。如下可見 class=‘vant-sticky–fixed’ 內(nèi)容是固定的,而 style 是計算屬性,動態(tài)變化的。

因此,這里學習到的兩個 技巧 是,

  • 元素使用 fixed 時,為了不影響滾動效果,布局錯亂,可以包裹一個父元素去保持占位。
  • 由同個變量去控制一個元素的樣式變化,而靜態(tài)的樣式放到 class 里,動態(tài)的放到 style 里。

css 部分

@import '../style/var';.van-sticky {&--fixed {position: fixed;top: 0;right: 0;left: 0;z-index: @sticky-z-index; // @sticky-z-index: 99;} }

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

computed: {style() {// 意味著 fixed 改變的同時, style 也改變了if (!this.fixed) {// 也就不設(shè)置 style 了,因為是動態(tài)響應(yīng) dom 元素的return;}const style = {};if (isDef(this.zIndex)) {// 修改層級,vant 默認在 vant-sticky--fixed 里變量定義為 99,這里通過傳參修改style.zIndex = this.zIndex; }if (this.offsetTopPx && this.fixed) {style.top = `${this.offsetTopPx}px`; // 通過設(shè)置 top,來設(shè)置偏移量}if (this.transform) {style.transform = `translate3d(0, ${this.transform}px, 0)`;}return style;},},

初始的生命周期部分

created 生命周期

created() {// compatibility: https://caniuse.com/#feat=intersectionobserver// vant2 使用 SSR 寫的,故有 isServer 是否在服務(wù)器運行的判斷// window.IntersectionObserver ie11 不支持if (!isServer && window.IntersectionObserver) {this.observer = new IntersectionObserver(// entries是一個數(shù)組,每個成員都是一個 IntersectionObserverEntry 對象// 有幾個被觀察的成員就有幾個對象(entries) => {// 每次元素進入可視區(qū) 或 離開可視區(qū)時 觸發(fā)if (entries[0].intersectionRatio > 0) {this.onScroll();}},// root 屬性指定目標元素所在的容器節(jié)點(即根元素){ root: document.body });}},

window.IntersectionObserver 自動觀察元素是否可見(本質(zhì)是目標元素與視口產(chǎn)生一個交叉區(qū),只有線程空閑下來,才會執(zhí)行觀察器), 詳見 阮一峰的 IntersectionObserver API 使用教程

后續(xù)會用到,雖然把 IntersectionObserver 相關(guān)部分全都注釋掉,也不影響使用。

// 用法 this.observer = new IntersectionObserver(callback, option)// 開始觀察 this.observer.observe(this.$el);// 停止觀察 this.observer.unobserve(this.$el);// 關(guān)閉觀察器 this.observer.disconnect();

通過 mixins,混入生命周期函數(shù) mounted、activated、deactivated、beforeDestroy 以綁定和取消監(jiān)聽事件

mixins: [BindEventMixin(function (bind, isBind) { // 1 BindEventMixin 建議先看下面的說明部分,再往下看if (!this.scroller) {this.scroller = getScroller(this.$el); // getScroller 從當前元素一直向上找到帶有滾動屬性的元素}// IntersectionObserver 的對象if (this.observer) {// 當綁定時,isBind 為 true,開始觀察// 當取消監(jiān)聽時,isBind 為 false,停止觀察const method = isBind ? 'observe' : 'unobserve'; this.observer[method](this.$el);}// bind 即為 on( addEventListener)bind(this.scroller, 'scroll', this.onScroll, true);this.onScroll();}),],

1 簡單分析下 BindEventMixin 實現(xiàn)如下

import { on, off } from '../utils/dom/event';let uid = 0; // 入?yún)?handler 是個函數(shù) export function BindEventMixin(handler) {const key = `binded_${uid++}`; // 記錄綁定function bind() {if (!this[key]) { // 沒有綁定handler.call(this, on, true); // 把 on(即 addEventListener)傳給 handler,第三個參數(shù)是告知 handler 當前狀態(tài)是否綁定this[key] = true; // 標記綁定}}function unbind() {if (this[key]) { // 綁定了,則取消監(jiān)聽事件handler.call(this, off, false); // 把 off (即 removeEventListener )傳給 handlerthis[key] = false; // 標記w未綁定}}// 通過 mixins,混入生命周期函數(shù),以綁定和取消監(jiān)聽事件return {mounted: bind, activated: bind,deactivated: unbind,beforeDestroy: unbind,}; }

因此這里學習到的 技巧 是,我們也可以通過 mixins 的方式去自動的綁定和取消監(jiān)聽事件。前提是,符合這些生命周期,需要一開始載入便監(jiān)聽的,但 watch 某個數(shù)據(jù)變化,去手動的監(jiān)聽和取消監(jiān)聽就不太適用了。當然,也可以依據(jù)情況改造下函數(shù)。

props 和 data 部分

簡單看下傳值和變量定義部分

props: {zIndex: [Number, String], // 吸頂時的 z-indexcontainer: null, // 容器對應(yīng)的 HTML 節(jié)點,類型 ElementoffsetTop: { // 吸頂時與頂部的距離,支持 px vw vh rem 單位,默認 pxtype: [Number, String],default: 0,},},data() {return {fixed: false,height: 0, // 元素本身高度transform: 0, // 偏移量,只在有容器,且展示吸底效果時,有用到};},

onScroll 滾動事件部分

先搞清楚幾個概念:
scrollTop 為 滾動的距離
window.scrollTop:

getBoundingClientRect():其提供了元素的大小及其相對于視口的位置
el.getBoundingClientRect().top:

可以發(fā)現(xiàn),在向上滾動的過程中,window.scrollTop 不斷增加,el.getBoundingClientRect().top 不斷減少。而增加的部分剛好等于減少的部分。

如果元素的頂部超出視口,那么 el.getBoundingClientRect().top 為負值,window.scrollTop 還是不斷增加。

可以得出,在滾動的過程中, el.getBoundingClientRect().top + window.scrollTop 的值始終是不變的,也就是,元素初始的位置到視口頂部的距離,此時 window.scrollTop 為 0。

接下來是重中之重的 onScroll 滾動事件部分,先從 1、2 開始講起

offsetHeight:一個元素本身的高度 + padding+border+滾動條,不包括偽元素

因此在上面的基礎(chǔ)上,加上 el.offsetHeight,也就是元素的初始位置的底部到視口頂部的距離
el.getBoundingClientRect().top + window.scrollTop + el.offsetHeight

實現(xiàn)原理
scrollTop + offsetTopPx > topToPageTop
當頁面滾動距離 + 偏移量 大于 目標元素一開始距離頂部的距離時,目標元素設(shè)置 fixed 屬性,吸頂。至于偏移量,通過設(shè)置 top 屬性去偏移。
當頁面滾動距離 + 偏移量 小于 目標元素一開始距離定都的距離時,意味著滾回去了,那么移除 fixed 屬性

methods: {onScroll() {// 判斷當前元素,及祖先元素是否隱藏了,隱藏了就不需要滾動了if (isHidden(this.$el)) {return;}this.height = this.$el.offsetHeight; // 當前元素的高度,可用于占位,一直不變的// offsetTopPx() 方法將 px vw vh rem 單位傳值轉(zhuǎn)換為 pxconst { container, offsetTopPx } = this;// window 滾動的距離 window.scrollTopconst scrollTop = getScrollTop(window);// getElementTop() 返回 el.getBoundingClientRect().top + window.scrollTop// 上面分析過,保持不變,也就是 元素一開始與頂部的距離const topToPageTop = getElementTop(this.$el);const emitScrollEvent = () => {this.$emit('scroll', {scrollTop,isFixed: this.fixed,});};// 先注釋掉該部分后面講解,目前的部分足夠?qū)崿F(xiàn) 1 2 效果// if (container) {// ... // }// 當滾動距離達到指定上限:頁面滾動的距離+偏移 > 元素一開始與頂部的距離 // offsetTopPx 偏移,會用設(shè)置 top 來解決if (scrollTop + offsetTopPx > topToPageTop) {this.fixed = true; // 設(shè)置 fixed 屬性,目標元素視口吸頂this.transform = 0; // 重置因吸底容器效果而產(chǎn)生的偏移 transform,后面會提到。} else {// 當滾回頂部時,取消 fixedthis.fixed = false;}emitScrollEvent();},}

接下來,分析 3 指定容器的情況。

有點特殊的是,目標元素到達視口頂部時,需要吸頂。而視口頂部到容器底部的距離,小于目標元素時,應(yīng)該吸底容器,如下圖。
而在該特殊情況出現(xiàn)之前,頁面滾動+偏移距離超出元素一開始到視口頂部距離時,吸頂(這部分和容器沒有關(guān)系)。代碼實現(xiàn)和 1 2 部分相同

如果在容器和元素之間再放個元素,是否也有吸底效果呢

<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>


看樣子,這一版并不支持上述情況。因此,默認目標元素一開始的位置是在容器邊緣。下面的源碼分析,也就排除這一情況了。

實現(xiàn)原理
scrollTop + offsetTopPx + this.height > bottomToPageTop
當頁面滾動距離 + 偏移 + 目標元素高度,超出了容器一開始的底部到視口頂部的距離

如果超出部分小于元素高度,則展示吸底效果。設(shè)置 fixed 吸頂,在通過 transfom 向上移動超出的距離,以達到吸底容器的效果。

如果完全超出元素高度,則消除所有靜態(tài)、動態(tài)樣式,回到原樣。

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

if (container) {// 借鑒上面的分析,排除不支持的情況后// el.getBoundingClientRect().top + window.scrollTop 一開始目標元素到視口頂部的距離// 加上 container.offsetHeight 容器自身的高度,為容器一開始從底部到視口頂部的距離const bottomToPageTop = topToPageTop + container.offsetHeight;// 頁面滾動的距離+偏移+目標元素的高度 > 容器一開始從底部到頂部的距離// 意味著,如果保持 fixed 的狀態(tài),目標元素會超出容器底部,這時候應(yīng)該讓它吸底if (scrollTop + offsetTopPx + this.height > bottomToPageTop) {// 目標元素超出底部的距離 = 目標元素高度 + 頁面滾動距離 - 容器一開始的底部到頂部的距離// 為什么不考慮偏移呢?因為此時視覺上已經(jīng)超出容器底部了,不需要管偏移,而是要吸附容器底部了const distanceToBottom = this.height + scrollTop - bottomToPageTop;// 超出距離 < 元素高度// 沒有全部超出,元素吸底展示if (distanceToBottom < this.height) {// 給個 fixed 吸頂,通過調(diào)整 transform 往上移動使得 視覺上元素到了容器的底部this.fixed = true;// 需往上移動的距離為,超出的距離 + top 值的大小(抵消掉 top 值,因為原先的 top 值還在)this.transform = -(distanceToBottom + offsetTopPx);} else {// 完全超出,解除 fixed// 意味著 class='van-sticky--fixed' 刪除,動態(tài)的 style 返回 {} this.fixed = false;}emitScrollEvent();return;}

在理解了上述原理后,為我們的業(yè)務(wù)增效吧。動手之前多思考,生搬硬套不可取。

總結(jié)

以上是生活随笔為你收集整理的Vant2 源码分析之 vant-sticky的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。