因公司後臺管理系統不少功能技術老舊,最近在用vue重構公司的後臺管理系統,在作商品管理添加商品這一塊,借鑑淘寶的添加商品的交互,須要實現一個簡單的吸頂、錨點和滾動高亮按鈕的效果。javascript
關於元素吸頂效果,經過查閱相關資料和相關測試,有三種方式(還有一種是jquery的方法,這裏就不介紹了)html
粘性定位元素至關於position:relative和position:sticky的結合體,受限於父級元素,在不一樣的條件下呈現出不一樣的頁面效果vue
sticky元素效果徹底受限於父級元素,使用條件:
1.sticky元素的父元素的overflow只能設置爲visible,不然會致使沒有粘滯效果
2.sticky元素的父元素不能設置固定的高度,不然會致使沒有粘滯效果
3.sticky知足條件變成fixed定位時,與標準fixed元素不同,不會脫離文檔流
4.sticky 定位的元素不能添加一個只包含自身的父元素,會致使沒有粘滯效果
5.同一個父級元素中的sticky元素,若是定位值相等,則會重疊,若是屬於不一樣父級元素中,則會擠掉以前的元素,造成依次佔位的效果 具體實現效果以下:java
.sticky-box{
position: sticky;
position: -webkit-sticky;
top: 60px; //可經過js動態設置
}
複製代碼
經過查看can i use 能夠看到相關的兼容性: jquery
能夠看出這個屬性的兼容性不是那麼好,若是項目須要兼容到ie11等的話,就不是那麼適用了HTMLElement.offsetTop 爲只讀屬性,它返回當前元素相對於其 offsetParent 元素的頂部內邊距的距離。所以咱們須要注意的是,在監聽頁面滾動的過程當中,須要將定位父級元素的偏移量也計算在內,能夠以下寫法:web
//獲取當前元素的offsetTop
getOffsetTop(obj) {
let offsetTop = 0;
while (obj != window.document.body && obj != null) {
offsetTop += obj.offsetTop;
obj = obj.offsetParent;
}
return offsetTop;
}
複製代碼
經過在vue的mounted生命週期函數中添加監聽事件滾動的事件:element-ui
mounted() {
/**經過給變成固定定位的元素添加一個同等高度的父元素,防止該元素變成固定定位時,脫離文檔流致使的頁面抖動 */
this.tabsHeight = this.$refs.elTabs.offsetHeight;
window.addEventListener("scroll", this.handleScroll);
},
destroyed() {
//離開該頁面須要移除這個監聽的事件
window.removeEventListener("scroll", this.handleScroll);
},
methods: {
/**滾動事件 */
handleScroll() {
//獲取頁面滾動條的高度
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
let offsetTop = this.getOffsetTop(this.$refs.elTabs);
this.isFixed = scrollTop > offsetTop;
}
}
複製代碼
同時若是這種吸頂方式在項目中會屢次用到,就能夠封裝成組件的形式瀏覽器
還有一種更爲直接的方式,能夠實現吸頂效果,就是使用getBoundingClientRect().top來獲取元素相對於視口(瀏覽器窗口)的位置,相對於offsetTop,該方法不用考慮到吸頂元素的父級元素和頁面滾動條的高度,直接對該元素進行處理便可,實現以下: /*滾動事件 / handleScroll() { / * getBoundingClientRect().top 獲取某元素距離瀏覽器頂部的高度,不包含滾動的距離 */ let tabOffsetTop = this.$refs.stickyBox.getBoundingClientRect().top; this.isFixed = tabOffsetTop < this.offsetTop }性能優化
/**滾動事件 */
handleScroll() {
/** * getBoundingClientRect().top 獲取某元素距離瀏覽器頂部的高度,不包含滾動的距離 this.offsetTop 表示的是吸頂元素距離頂部的條件值(通常項目需求是0) */
let tabOffsetTop = this.$refs.stickyBox.getBoundingClientRect().top;
this.isFixed = tabOffsetTop < this.offsetTop
}
複製代碼
點擊相應的按鈕,頁面滾動到相應的位置,目前我知道實現該功能的方式有兩種:
1. 使用a標籤訂位
2. 使用js模擬錨點定位bash
這是一種常見的定位方式,它有兩種實現方式:
1. 經過href屬性連接到指定元素的id
2.另外一種是添加一個 a 標籤,再將 href 屬性連接到這個 a 標籤的 name 屬性
<a href="#view1">按鈕1</a>
<a href="#view2">按鈕1</a>
<div id="view1">視圖1</div>
<div><a name="view2">視圖2</a></div>
複製代碼
這種定位方式很簡單,支持任意標籤的定位,可是a標籤的定位會改變路由的hash,若是有相關路由會進行路由跳轉
經過js獲取元素的scrollTop值,使其滾動到指定的位置,就能實現錨點定位效果,這裏的tab切換選項,用到是的element-ui的el-tabs組件,具體實現以下:
<!-- html -->
<el-tabs v-model="activeName" type="card" @tab-click="tabClick">
<el-tab-pane :label="item.name" :name="item.key" v-for="item in tabList" :key="item.key"></el-tab-pane>
</el-tabs>
<!-- js -->
methods:{
//獲取當前元素的offsetTop
getOffsetTop(obj) {
let offsetTop = 0;
while (obj != window.document.body && obj != null) {
offsetTop += obj.offsetTop;
obj = obj.offsetParent;
}
return offsetTop;
},
<!--錨點點擊事件-->
<!--fixedHeight 滾動的位置上方固定的高度-->
tabClick(e) {
let _this = this;
//獲取當前選中的index以便後面滾動高亮
this.index = parseInt(e.index);
//給定一個標識,錨點事件不觸發滾動
this.isScroll = false;
this.isChange = false;
//獲取當前選中元素的top值(給元素綁定對應的ref值)
let offsetTop = this.getOffsetTop(this.$refs[this.activeName]);
let scrollTop = offsetTop - this.fixedHeight;
window.scrollTo({
top: scrollTop
});
}
複製代碼
不得不提的一個方法就是scrollIntoView,Element.scrollIntoView() 方法讓當前的元素滾動到瀏覽器窗口的可視區域內,同時還支持動態效果,可是不支持配置滾動到距離頂部的距離,會出現遮罩現象,可是很適合作會到頂部的功能
當用戶滾動內容區時,高亮距離按鈕組件最近的那個元素所對應的按鈕。 經過監聽滾動事件,獲取當前選中的tab的offsetTop值和當前頁面的scrollTop值,判斷向上或者向下滾動,作出不一樣的處理,具體以下:
//頁面滾動要作的事情
handleScroll() {
let scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop;
this.scrollTop = scrollTop;
<!--isScroll 用於避免錨點事件觸發頁面滾動--> if (!this.isScroll) return; /** * scrollTop 頁面的滾動條的高度 * offsetTop 當前選中的tab元素的offsetTop * offsetHeight 當前選中元素的高度 */ let offsetTop = this.getOffsetTop(this.$refs[this.activeName]); let offsetHeight = this.$refs[this.activeName].offsetHeight; let actuaTop = scrollTop + this.fixedHeight; let length = this.tabList.length; /** * 頁面滾動中根據相應位置變換選中tab */ if (actuaTop < offsetTop && this.index > 0) { this.index = this.index - 1; this.activeName = this.tabList[this.index].key; } else if (this.index < length && actuaTop > offsetTop + offsetHeight) { this.index = this.index + 1; this.activeName = this.tabList[this.index].key; } } 複製代碼
頁面中讀取屬性會致使頁面reflow(下次會對致使頁面reflow和repaint 的操做作一個總結),過分的reflow會致使頁面性能降低,因此咱們應該儘可能減小reflow的次數,以便給用戶更好的體驗。 若是產品能夠接受效果有延遲,就可使用節流函數控制在必定時間內只執行一次函數(節流函數可使用lodash.js 封裝好的 throttle 方法)
寫到這裏,需求中的三個功能都已經實現,也許還存在更好的方案,可是經過此次實現這三個需求,若是你們有其餘更好的方法,歡迎留言補充,但我也從中學習到了一些東西 1.position:sticky的用法和使用條件 2.scrollTop、offsetTop等元素的相關屬性、getBoundingClientRect()用法和 scrollTo、scrollIntoView的用法 3.錨點時間和滾動高亮事件致使的衝突處理等