position: sticky;css
fixed 吸頂html
頁面滾動結束後頁面才渲染jquery
常常會有這樣的需求,當頁面滾動到某一個位置fixedTopValue時,須要某個元素fixedElement固定在屏幕頂部。基本方法是獲取頁面的scrollTop值作判斷:
若是 scrollTop > fixedTopValue; 則添加position:fixed;top: 0;不然刪除position:fixed;屬性。web
當在pc瀏覽器操做的時候正常。真機測試時總會出現千奇百怪的現象。好比:
一、 當頁面往下滾時,fixedElement須要等頁面滾動中止以後纔會出現。
二、往上滾動時出現到固定的位置時不恢復原樣,而是到達頂部、等頁面中止滾動以後纔會唰的一下恢復原樣
三、滾動到頂部以後,會出現兩個同樣的fixedElement, 過一會才恢復正常。
這樣的用戶體驗真的不好,因此迫切須要解決這個問題。chrome
解決方法主要涉及一下三個方面
一、使用新的定位屬性 position: sticky; (若是支持)
二、若是不支持1,使用window.requestAnimationFrame方法確保改變定位屬性在固定時間內執行一次
三、 給fixedElement開啓硬件加速編程
基本邏輯以下圖:瀏覽器
測試頁面二維碼:微信
對與css的position屬性咱們只知道有static、relative、absolute、fixed這四個值,何時又多出了sticky這個值。看下MDN文檔解釋app
Sticky positioning
Sticky positioning is a hybrid of relative and fixed positioning. The element is treated as relative positioned until it crosses a specified threshold, at which point it is treated as fixed positioned.dom
大概意思是:sticky定位時relative定位與fixed定位的混合體。對於設置了sticky定位的元素,在它的頂部到達一個指定的界限以前會被看成relative定位,超過這個界限字後則被看成fixed定位。這個界限就是 該元素頂部距離窗口頂部的距離等於該元素設置的top值
好比如下demo:
<div> <div class="top">60像素分割線</div> <div class="sticky">當個人頂部距離窗口頂部爲10px(top值)時,我就會像fixed同樣fixed在距離窗口10px(top值處)</div> <div class="content">sticky的co時代發送分ntent</div> </div>
.top{height:60px;background:#f20;width:100%;color: #fff;font-size:16px;text-align:center;line-height:60px;} .sticky{position:sticky;position:-webkit-sticky;top:10px;height:40px;background:#dd5;color:#fff;line-height:20px;text-align:center;} .sticky-t10{top:0px;} .content{height:1000px;width:100%;background:#f8f8f8;text-align:center;padding-top:40px;color:#333;}
效果圖:
當頁面滾動到距離黃色區塊頂部10px時,黃色區塊就會fixed在窗口頂部10px處,頁面再往下滾動距離也不會變。當頁面網上滾動時,頁面頂部距離黃色區塊頂部大於10px時,黃色區塊又會恢復原樣固定在原來的位置。
position:sticky這個屬性並不會出現當頁面滾動中止以後纔會出現的bug,由於它自己就是屬於正常流。並不會像fixed 與static相互切換時引發重排於重繪,而移動端瀏覽器滾動時是禁止重排跟重繪的,因此纔會致使以上出現的問題。下圖是對於position:sticky的支持狀況:
發現支持的瀏覽器通常般,可是通過測試像微信、safari、uc等瀏覽器是支持的,雖然chrome不支持,可是在chrome使用優化後的fixed定位也能夠解決這個問題,基本能知足主流的瀏覽器就夠了,其餘的見鬼去吧。
若是瀏覽器不支持position:sticky,那麼就使用js動態的在節點在fixed定位於static定位中切換,可是須要對切換過程作一些優化。
一、使用函數節流防抖減小dom操做頻繁粗發,可是保證在規定時間內必須執行一次。
二、使用window.requestAnimationFrame 方法在下一幀前觸發瀏覽器的強制同步佈局,是對dom的操做能及時渲染到頁面上。
三、減小對dom的讀寫操做,或者把dom操做把讀、寫操做分開,能夠減小渲染次數。
因爲移動設備的硬件限制,致使移動端的瀏覽器的渲染能比較差。此時對須要定位的元素開啓硬件加速,會把須要渲染的元素放到特定的複合層『Composited Layer』中,當該元素改變時能夠較少重繪或重排的範圍。給元素添加 transform: translateZ(0);屬性就行。
硬件加速:
http://div.io/topic/1348
http://www.cnblogs.com/shyton...
提高頁面性能:
https://developer.mozilla.org...
http://www.ruanyifeng.com/blo...
http://www.jianshu.com/p/a32b...
具體實現請參考如下jquery版本的代碼:
//jquery (function() { function Sticky(){ this.init.apply(this, arguments); } /** * 滾動fixed組件初始化 * @param {object} setting allocate傳進來的參數 * @param {object} setting.stickyNode 須要設置position:sticky的節點,一般是最外層 * @param {object} setting.fixedNode 當滾動必定距離時須要fixed在頂部的節點 * @param {int} setting.top fixed以後距離頂部的top值 * @param {int} setting.zIndex fixed以後的z-index值 * @param {string} setting.fixedClazz fixed時給fixedNode添加的類 * @param {function} setting.runInScrollFn 滾動期間額外執行的函數 * @return {void} */ Sticky.setting = { stickyNode: null, fixedNode: null, top: 0, zIndex: 100, fixedClazz: '', runInScrollFn: null }; var sPro = Sticky.prototype; var g = window; /** * 初始化 * @param {object} options 設置 * @return {void} */ sPro.init = function(options){ this.setting = $.extend({}, Sticky.setting, options, true); if (options.fixedNode) { this.fixedNode = options.fixedNode[0] || options.fixedNode; this.stickyNode = options.stickyNode[0] || options.stickyNode; this.cssStickySupport = this.checkStickySupport(); this.stickyNodeHeight = this.stickyNode.clientHeight; this.fixedClazz = options.fixedClazz; this.top = parseInt(options.top, 10) || 0; this.zIndex = parseInt(options.zIndex) || 1; this.setStickyCss(); this.isfixed = false; // 把改變定位的操做添加到節流函數與window.requestAnimationFrame方法中,確保必定事件內必須執行一次 this.onscrollCb = this.throttle(function() { this.nextFrame(this.sticky.bind(this)); }.bind(this), 50, 100); this.initCss = this.getInitCss(); this.fixedCss = this.getFixedCss(); this.addEvent(); } }; /** * 獲取原始css樣式 * @return {string} 定位的樣式 */ sPro.getInitCss = function() { if (!!this.fixedNode) { return "position:" + this.fixedNode.style.position + ";top:" + this.fixedNode.style.top + "px;z-index:" + this.fixedNode.style.zIndex + ";"; } return ""; }; /** * 生成fixed時的css樣式 * @return {void} */ sPro.getFixedCss = function() { return "position:fixed;top:" + this.top + "px;z-index:" + this.zIndex + ";"; }; /** * 給fixedNode設置fixed定位樣式 * @param {string} style fixed定位的樣式字符串 */ sPro.setFixedCss = function(style) { if(!this.cssStickySupport){ if (!!this.fixedNode){ this.fixedNode.style.cssText = style; } } }; /** * 檢查瀏覽器是否支持positon: sticky定位 * @return {boolean} true 支持 false 不支持 */ sPro.checkStickySupport = function() { var div= null; if(g.CSS && g.CSS.supports){ return g.CSS.supports("(position: sticky) or (position: -webkit-sticky)"); } div = document.createElement("div"); div.style.position = "sticky"; if("sticky" === div.style.position){ return true; } div.style.position = "-webkit-sticky"; if("-webkit-sticky" === div.style.position){ return true; } div = null; return false; }; /** * 給sticyNode設置position: sticky定位 */ sPro.setStickyCss = function() { if(this.cssStickySupport){ this.stickyNode.style.cssText = "position:-webkit-sticky;position:sticky;top:" + this.top + "px;z-index:" + this.zIndex + ";"; } }; /** * 監聽window的滾動事件 */ sPro.addEvent = function() { $(g).on('scroll', this.onscrollCb.bind(this)); }; /** * 讓函數在規定時間內必須執行一次 * @param {Function} fn 定時執行的函數 * @param {int} delay 延遲多少毫秒執行 * @param {[type]} mustRunDelay 多少毫秒內必須執行一次 * @return {[type]} [description] */ sPro.throttle = function(fn, delay, mustRunDelay){ var timer = null; var lastTime; return function(){ var now = +new Date(); var args = arguments; g.clearTimeout(timer); if(!lastTime){ lastTime = now; } if(now - lastTime > mustRunDelay){ fn.apply(this, args); lastTime = now; }else{ g.setTimeout(function(){ fn.apply(this, args); }.bind(this), delay); } }.bind(this); }; /** * window.requestAnimationFrame的兼容性寫法,保證在100/6ms執行一次 * @param {Function} fn 100/16ms須要執行的函數 * @return {void} */ sPro.nextFrame = (function(fn){ var prefix = ["ms", "moz", "webkit", "o"]; var handle = {}; handle.requestAnimationFrame = window.requestAnimationFrame; for(var i = 0; i < prefix.length && !handle.requestAnimationFrame; ++i){ handle.requestAnimationFrame = window[prefix[i] + "RequestAnimationFrame"]; } if(!handle.requestAnimationFrame){ handle.requestAnimationFrame = function(fn) { var raf = window.setTimeout(function() { fn(); }, 16); return raf; }; } return function(fn) { handle.requestAnimationFrame.apply(g, arguments); } })(); /** * 判斷stickyNode的當前位置設置fixed|static|sticky定位 * @return {void} */ sPro.sticky = function() { this.setting.runInScrollFn && this.setting.runInScrollFn(); var stickyNodeBox = this.stickyNode.getBoundingClientRect(); if(stickyNodeBox.top <= this.top && !this.isfixed){ this.setFixedCss(this.fixedCss); this.fixedClazz && $(this.fixedNode).addClass(this.fixedClazz); this.isfixed = true; $(this).trigger('onsticky', true); } else if(stickyNodeBox.top > this.top && this.isfixed) { this.setFixedCss(this.initCss.replace(/position:[^;]*/, "position:static")); g.setTimeout(function() { this.setFixedCss(this.initCss) }.bind(this), 30); this.fixedClazz && $(this.fixedNode).removeClass(this.fixedClazz); this.isfixed = false; $(this).trigger('onsticky', true); } }; $.initSticky = function(options){ return new Sticky(options); }; })();
html 結構
<div class="m-nav"> <div class="nav-fixed fixed" id="j-nav" style="position: fixed; top: 0px; z-index: 100;"> <ul class="f-cb"> <li class="active" anchor-id="j-understand">瞭解兒童編程</li> <li anchor-id="j-join">參與公益直播課</li> <li anchor-id="j-upload">上傳編程做品</li> </ul> </div> </div>
css 結構
.g-page-box .m-nav { height: 1.33333rem; } .g-page-box .m-nav .nav-fixed { height: .86667rem; padding: .22667rem .50667rem; background-color: #1aadbb; position: relative; transform: translate3d(0, 0, 0); -webkit-transform: translate3d(0, 0, 0); transition: height 4s; } .fixed{ position: fixed; top: 0px; z-index: 100; }