因為專案需求,最近開始轉到微信公眾號開發,接觸到了Vue框架,這個效果的實現雖說是基於Vue框架下實現的,但是同樣也可以借鑑到其他地方,原理都是一樣的。
進入正題,先看下效果圖:
其實js做這個效果還是挺簡單的,因為在css中我們可以設定一個元素的position: fixed;
,這樣它就可以固定在那裡,這樣不管頁面怎麼滾動,它的位置都不受影響,所以我們的思路就是在合適的時機把要吸頂的頭部元素的position屬性設定為fixed就可以了。但是這個合適的時機是什麼時候呢,這就需要我們計算了,我們需要監聽頁面的滾動狀態,當頁面滾動到要吸頂元素所處的位置的時候就是我們設定它固定的時候,所以就需要我們:
1.監聽頁面的滾動狀態:
在mounted回撥中加入以下程式碼:
mounted() {
// handleScroll為頁面滾動的監聽回撥
window.addEventListener(`scroll`, this.handleScroll);
},
複製程式碼
同時在destroyed回撥中移除監聽:
destroyed(){
window.removeEventListener(`scroll`, this.handleScroll);
},
複製程式碼
2.計算吸頂元素到頁面頂部的距離:
計算出來這個距離之後就可以確定固定吸頂元素的時機了,如果你的吸頂元素上面的元素的高度是固定的話,那就簡單了,直接在handleScroll方法中進行判斷就可以了,可以直接跳到第三步了,如果是動態的,那就需要我們在介面請求完資料,dom元素渲染完之後進行動態計算了,Vue中有一個很好用的方法,可以很方便的監聽dom渲染完成:
// 監聽dom渲染完成
this.$nextTick(function(){
// 這裡fixedHeaderRoot是吸頂元素的ID
let header = document.getElementById("fixedHeaderRoot");
// 這裡要得到top的距離和元素自身的高度
this.offsetTop = header.offsetTop;
this.offsetHeight = header.offsetHeight;
console.log("offsetTop:" + this.offsetTop + "," + this.offsetHeight);
});
複製程式碼
3.判斷頁面滾動距離:
handleScroll(){
// 得到頁面滾動的距離
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
// 判斷頁面滾動的距離是否大於吸頂元素的位置
this.headerFixed = scrollTop > (this.offsetTop - this.offsetHeight * 2);
},
複製程式碼
ps:這裡理論上其實應該是scrollTop > (this.offsetTop – this.offsetHeight),但是不知道為啥我這裡做出來後滾動到吸頂元素位置的時候scrollTop還是比this.offsetTop – this.offsetHeight的值小,所以這裡*2,這樣得出來的值才剛剛好,如果有知道的朋友可以幫忙解惑一下。
上面我們得到了一個headerFixed的boolean屬性值,接下來我們只需要根據它的值來設定吸頂元素的position: fixed;
屬性就可以了。
我們可以寫一個css樣式:
.isFixed{
position: fixed;
top: px2rem(110);
left: px2rem(20);
right: px2rem(20);
}
複製程式碼
然後Vue可以在dom元素裡這樣動態設定class,非常方便:
<div id="fixedHeaderRoot">
<div id="knowPointHeader" class="knowPointHeader" :class="headerFixed?`isFixed`:``">
<div><span>知識模組</span></div>
<div><span>知識點</span></div>
<div><span>能力要求</span></div>
</div>
</div>
複製程式碼
其實到這裡這個效果已經實現完成了,不過我在測試過程中發現,因為ios手機頁面滾動到底部的時候,還可以上拉,有一個橡皮筋效果,這個效果會導致一個我們頁面的一個Bug,因為它的這種橡皮筋效果也會觸發頁面滾動的監聽,當資料很多的時候其實看不出來,只有當資料剛好佔滿螢幕的時候,這個時候你再繼續往上滑動螢幕,就會觸發頁面的滾動監聽,這個時候handleScroll方法中計算出來的值scrollTop是大於吸頂元素top的距離,所以吸頂元素會被設定為固定屬性,大家知道一個元素一旦被設定為position: fixed;
,那麼它就會相對於瀏覽器視窗進行定位,這樣我們下面的內容就會往上頂,這樣的話scrollTop的值又小於了吸頂元素top的距離,這樣headerFixed屬性又為false,position: fixed;
屬性又沒有了,這樣它就又相對與它原本的父元素進行定位,這樣就成了一個迴圈,你會發現頁面會上下跳到,這樣是肯定不行的,所以我下面又針對這個問題進行了一個優化,當然這個方案感覺不是特別完美,不過確實可以解決這個問題。
通過上面的分析我們可以得知造成這個問題的原因是因為我們把設定了元素的position: fixed;
屬性,使得下面的內容往上頂,所以要想解決這個問題,那我們就不固定這個元素,但是這樣的話就達不到吸頂的效果了,所以我們需要再加一個和吸頂元素一模一樣的元素,它一直就是固定狀態:
<div id="fixedHeaderRootReal">
<div class="knowPointHeader isFixed" v-show="headerFixed">
<div><span>知識模組</span></div>
<div><span>知識點</span></div>
<div><span>能力要求</span></div>
</div>
</div>
複製程式碼
這個元素預設是隱藏的,只有當頁面滾動的距離達到了它的位置的時候我們才讓它顯示,由於它是固定狀態,所以它的隱藏顯示並不會對頁面產生影響,這樣下面的內容就不會往上頂了,就可以解決ios手機上拉頁面橡皮筋效果的Bug了,當然這種方式有些取巧,但是暫時沒有更好的解決方案了,如果大家有更好的解決方案,歡迎在下面評論。最後給大家看一下我的頁面佈局:
<div v-show="kpointListShow" class="knowPointList">
<div id="fixedHeaderRoot">
<div id="knowPointHeader" class="knowPointHeader">
<div><span>知識模組</span></div>
<div><span>知識點</span></div>
<div><span>能力要求</span></div>
</div>
</div>
<div id="fixedHeaderRootReal">
<div class="knowPointHeader isFixed" v-show="headerFixed">
<div><span>知識模組</span></div>
<div><span>知識點</span></div>
<div><span>能力要求</span></div>
</div>
</div>
<div class="knowPointItem" v-for="(kpointItem,index) in rows.kpointList" :key="index">
<div><span>{{kpointItem.knowModule}}</span></div>
<div><span>{{kpointItem.knowPoint}}</span></div>
<div><span>{{kpointItem.abilityRequire}}</span></div>
</div>
</div>
複製程式碼