移動端吸頂fixbar的解決方案詳解
需求背景
經(jīng)常會有這樣的需求,當(dāng)頁面滾動到某一個位置時,需要某個頁面元素固定在屏幕頂部,并且有時需要連續(xù)滾動吸頂。在PC端主要的實現(xiàn)是通過 CSS 的 position: fixed 屬性,但是在移動端,尤其是在安卓端,存在諸多的兼容性問題。
問題
position:fixed給移動端帶來的問題:
- IOS8在頁面滾動時,吸頂不連續(xù);頁面滑動時,不見吸頂,頁面滾動停止后,吸頂緩慢出現(xiàn)
- 滾動到頂部之后,會出現(xiàn)兩個一樣的吸頂, 過一會才恢復(fù)正常。
- footer底部輸入框 focus 狀態(tài),footer 底部輸入框被居中,而不是吸附在軟鍵盤上部。
- iPhone 4s&5 / iOS 6&7 / Safari 下,頁面底部footer輸入框失去焦點時,header定位出錯。當(dāng)頁面有滾動動作時,header定位恢復(fù)正常。
- iPhone 4 / iOS 5 / Safari下,當(dāng)頁面發(fā)生跳轉(zhuǎn),再退回時,fixed區(qū)域消失,當(dāng)內(nèi)容獲得焦點時,fixed區(qū)域才顯示。
- 安卓低版本/自帶瀏覽器,不支持fixed屬性,iOS4 也是不支持 fixed 的。
- 三星i9100(S2) / 自帶瀏覽器,在滾屏過程中,fixed定位異常,touchend之后恢復(fù)正常。
- 部分低版本Android對支持不好,video poster屬性設(shè)置的封面圖會遮擋fixed元素。
- QQ、UC瀏覽器滾動頁面時footer定位錯誤,會往上偏移,是由于地址欄收起的緣故。
- *remind:不要在 fixed 元素中使用 input / textarea 元素。
解決方案
分別處理各個問題:
IOS
在IOS端,使用 position: sticky 這個屬性,使用類似于 position: relative 和 position: absolute 的結(jié)合體。在目標(biāo)區(qū)域在屏幕中可見時,它的行為就像position:relative; 而當(dāng)頁面滾動超出目標(biāo)區(qū)域時,它的表現(xiàn)就像position:fixed,它會固定在目標(biāo)位置。
使用時,需要加上私有前綴
position: -webkit-sticky; position: -moz-sticky; position: -ms-sticky; position: sticky;
對于 position:sticky 的使用,需要注意很多的細節(jié),sticky滿足以下條件才能生效:
1、具有sticky屬性的元素,其父級高度必須大于sticky元素的高度。
2、sticky元素的底部,不能和父級底部重疊。(這條不好表述,文后詳細說明)
3、sticky元素的父級不能含有overflow:hidden 和 overflow:auto 屬性
4、必須具有top,或 bottom 屬性。
同時要注意,sticky元素僅在他父級容器內(nèi)有效,超出容器范圍則不再生效了。
安卓
滾動距離超過某位置時,js動態(tài)設(shè)置樣式;為了防止慣性滾動引起的fix不及時的情況,在 touchstart、 touchmove 、 touchend 事件都進行監(jiān)聽。
// 注意處理遮罩層的位置
var scrollHandler = function () {
if (topLength < me.getScrollTop()) {
target.css('position', 'fixed');
me.replaceEle.show();
}
else {
target.css('position', 'relative');
me.replaceEle.hide();
}
};
// 安卓情況下,防止慣性滾動引起的fix不及時的情況
if (/(Android)/i.test(navigator.userAgent)) {
$(window).on('scroll', scrollHandler);
$(document.body).on('touchstart', scrollHandler);
$(document.body).on('touchmove', scrollHandler);
$(document.body).on('touchend', function () {
scrollHandler();
setTimeout(scrollHandler, 1000);
});
}
不支持sticky
如果瀏覽器不支持position:sticky,那么就使用js動態(tài)的在節(jié)點在fixed定位于static定位中切換,但是需要對切換過程做一些優(yōu)化。
1、使用函數(shù)節(jié)流防抖減少dom操作頻繁粗發(fā),但是保證在規(guī)定時間內(nèi)必須執(zhí)行一次。
2、使用window.requestAnimationFrame 方法在下一幀前觸發(fā)瀏覽器的強制同步布局,是對dom的操作能及時渲染到頁面上。
3、減少對dom的讀寫操作,或者把dom操作把讀、寫操作分開,可以減少渲染次數(shù)。
原文代碼
(function() {
function Sticky(){
this.init.apply(this, arguments);
}
/**
* 滾動fixed組件初始化
* @param {object} setting allocate傳進來的參數(shù)
* @param {object} setting.stickyNode 需要設(shè)置position:sticky的節(jié)點,通常是最外層
* @param {object} setting.fixedNode 當(dāng)滾動一定距離時需要fixed在頂部的節(jié)點
* @param {int} setting.top fixed之后距離頂部的top值
* @param {int} setting.zIndex fixed之后的z-index值
* @param {string} setting.fixedClazz fixed時給fixedNode添加的類
* @param {function} setting.runInScrollFn 滾動期間額外執(zhí)行的函數(shù)
* @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 設(shè)置
* @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;
// 把改變定位的操作添加到節(jié)流函數(shù)與window.requestAnimationFrame方法中,確保一定事件內(nèi)必須執(zhí)行一次
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設(shè)置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設(shè)置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 + ";";
}
};
/**
* 監(jiān)聽window的滾動事件
*/
sPro.addEvent = function() {
$(g).on('scroll', this.onscrollCb.bind(this));
};
/**
* 讓函數(shù)在規(guī)定時間內(nèi)必須執(zhí)行一次
* @param {Function} fn 定時執(zhí)行的函數(shù)
* @param {int} delay 延遲多少毫秒執(zhí)行
* @param {[type]} mustRunDelay 多少毫秒內(nèi)必須執(zhí)行一次
* @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執(zhí)行一次
* @param {Function} fn 100/16ms需要執(zhí)行的函數(shù)
* @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的當(dāng)前位置設(shè)置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 結(jié)構(gòu):
<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 結(jié)構(gòu):
.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;
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript數(shù)組常用方法實例講解總結(jié)
這篇文章主要介紹了JavaScript數(shù)組及常見方法,結(jié)合實例形式總結(jié)分析了JavaScript數(shù)組的基本獲取、添加、刪除、排序、翻轉(zhuǎn)等相關(guān)操作技巧,需要的朋友可以參考下2021-09-09
javascript創(chuàng)建cookie、讀取cookie
這篇文章主要介紹了javascript創(chuàng)建cookie、讀取cookie的操作方法,內(nèi)容簡單易學(xué),感興趣的小伙伴們可以參考一下2016-03-03
javascript數(shù)組中的concat方法和splice方法
這篇文章主要介紹了javascript數(shù)組中的concat方法和splice方法,concat方法作用合并數(shù)組,可以合并一個或多個數(shù)組,會返回合并數(shù)組之后的數(shù)據(jù),不會改變原來的數(shù)組,更多相關(guān)內(nèi)容需要的小伙伴可以參考下面文章內(nèi)容2022-03-03
JavaScript實現(xiàn)跨瀏覽器的添加及刪除事件綁定函數(shù)實例
這篇文章主要介紹了JavaScript實現(xiàn)跨瀏覽器的添加及刪除事件綁定函數(shù),采用純javascript實現(xiàn)jquery的bind及unbind添加與刪除事件綁定的技巧,具有很好的瀏覽器兼容性,需要的朋友可以參考下2015-08-08
如何用JS/HTML將時間戳轉(zhuǎn)換為“xx天前”的形式
如果我們有一份過去時間戳,如何使用JS/HTML將時間戳轉(zhuǎn)換為“xx天前”的形式呢?很多朋友都覺得解決辦法有點困難,其實很簡單的,下面小編給大家分享完整的實現(xiàn)代碼,一起看看吧2017-02-02

