下圖可見,除了IE之外,目前絕大部分瀏覽器都是支持sticky佈局。css
<!--sticky組件--> <template> <!--盒子容器--> <section ref="$box" class="c-sticky-box" :style="boxStyle"> <!--內容容器--> <div ref="$content" class="content" :style="contentStyle"> <slot></slot> </div> </section> </template> <script> export default { props: { top: { type: [String], default: 'unset', }, left: { type: [String], default: 'unset', }, }, data() { return { boxStyle: { position: 'static', top: 0, left: 0, width: 'auto', // 佔位,爲了造成數據綁定 height: 'auto', }, contentStyle: { position: 'static', top: 0, left: 0, width: 'auto', height: 'auto', }, isFixedX: false, // 是否已經設置爲fixed佈局,用於優化性能,防止屢次設置 isFixedY: false, // 是否已經設置爲fixed佈局,用於優化性能,防止屢次設置 isSupport: this.cssSupport('position', 'sticky'), // isSupport: false, } }, mounted() { if (!this.isSupport) { // 不支持sticky this.getContentSize() // 獲取內容寬高 this.scrollHandler() // 主動觸發一次位置設置操做 window.addEventListener('resize', this.onResize) window.addEventListener('scroll', this.scrollHandler, true) } else { this.boxStyle = { position: 'sticky', top: this.top, left: this.left, } } }, beforeDestroy() { if (!this.isSupport) { window.removeEventListener('resize', this.onResize) window.removeEventListener('scroll', this.scrollHandler, true) } }, methods: { // 判斷是否支持某樣式的函數 cssSupport(attr, value) { let element = document.createElement('div') if (attr in element.style) { element.style[attr] = value return element.style[attr] === value } else { return false } }, // 獲取dom數據 getContentSize() { // 獲取內容容器寬高信息 const style = window.getComputedStyle(this.$refs.$content) // 設置盒子容器的寬高,爲了後續佔位 this.boxStyle.width = style.width this.boxStyle.height = style.height }, // 頁面縮放重置大小時,從新計算其位置 onResize() { const { $box } = this.$refs const { contentStyle } = this const boxTop = $box.getBoundingClientRect().top const boxLeft = $box.getBoundingClientRect().left if (contentStyle.position === 'fixed') { contentStyle.top = this.top === 'unset' ? `${boxTop}px` : this.top contentStyle.left = this.left === 'unset' ? `${boxLeft}px` : this.left } }, scrollHandler() { const { $content, $box } = this.$refs const { contentStyle } = this const boxTop = $box.getBoundingClientRect().top const boxLeft = $box.getBoundingClientRect().left const contentTop = $content.getBoundingClientRect().top const contentLeft = $content.getBoundingClientRect().left if (this.top !== 'unset') { if (boxTop > parseInt(this.top) && this.isFixedY) { this.isFixedY = false contentStyle.position = 'static' } else if (boxTop < parseInt(this.top) && !this.isFixedY) { this.isFixedY = true contentStyle.position = 'fixed' this.onResize() } // 當位置距左位置不對時,從新設置fixed對象left的值,防止左右滾動位置不對問題 if (contentLeft !== boxLeft && this.left === 'unset') { this.onResize() } } if (this.left !== 'unset') { if (boxLeft > parseInt(this.left) && this.isFixedX) { this.isFixedX = false contentStyle.position = 'static' } else if (boxLeft < parseInt(this.left) && !this.isFixedX) { this.isFixedX = true contentStyle.position = 'fixed' this.onResize() } // 當位置距左位置不對時,從新設置fixed對象left的值,防止左右滾動位置不對問題 if (contentTop !== boxTop && this.top === 'unset') { this.onResize() } } }, }, } </script>
sticky效果須要解決這麼幾個問題前端
組件有兩層容器vue
<section ref="$box" class="c-sticky-box" :style="boxStyle"> <div ref="$content" class="content" :style="contentStyle"> <slot></slot> </div> </section>
監聽vue的mounted事件git
const style = window.getComputedStyle(this.$refs.$content) this.boxStyle.width = style.width this.boxStyle.height = style.height
監聽scroll事件github
const { $content, $box } = this.$refs const { contentStyle } = this const boxTop = $box.getBoundingClientRect().top const boxLeft = $box.getBoundingClientRect().left const contentTop = $content.getBoundingClientRect().top const contentLeft = $content.getBoundingClientRect().left
if (boxTop > parseInt(this.top) && this.isFixedY) { contentStyle.position = 'static' } else if (boxTop < parseInt(this.topI) && !this.isFixedY) { contentStyle.position = 'fixed' contentStyle.top = this.top contentStyle.left = `${boxLeft}px` }
// 當位置距左位置不對時,從新設置fixed對象left的值,防止左右滾動位置不對問題 if (contentLeft !== boxLeft && this.left === 'unset') { const { $box } = this.$refs const { contentStyle } = this const boxTop = $box.getBoundingClientRect().top const boxLeft = $box.getBoundingClientRect().left if (contentStyle.position === 'fixed') { contentStyle.top = this.top contentStyle.left = `${boxLeft}px` } }
最後,是監聽頁面的resize事件,防止頁面大小變化時,fixed相對頁面的變化。一樣的,從新設置left值chrome
// 當位置距左位置不對時,從新設置fixed對象left的值,防止左右滾動位置不對問題 const { $box } = this.$refs const { contentStyle } = this const boxTop = $box.getBoundingClientRect().top const boxLeft = $box.getBoundingClientRect().left if (contentStyle.position === 'fixed') { contentStyle.top = this.top === 'unset' ? `${boxTop}px` : this.top contentStyle.left = this.left === 'unset' ? `${boxLeft}px` : this.left }
設置內容樣式時須要注意,設置定位相關屬性須要設置在box容器上,例如設置'displCy: inline-block;','verticCl-Clign: top;','margin'瀏覽器
目前本組件僅實現了基本功能,後續還將繼續優化如下功能安全
slot內容中,若是有圖片,若是獲取設置寬高,(監聽全部圖片的load事件,從新設置容器的高寬)dom
slot內容有變化時,設置容器函數
移動端適配
單位適配