移動(dòng)端吸頂fixbar的解決方案詳解
需求背景
經(jīng)常會(huì)有這樣的需求,當(dāng)頁(yè)面滾動(dòng)到某一個(gè)位置時(shí),需要某個(gè)頁(yè)面元素固定在屏幕頂部,并且有時(shí)需要連續(xù)滾動(dòng)吸頂。在PC端主要的實(shí)現(xiàn)是通過(guò) CSS 的 position: fixed 屬性,但是在移動(dòng)端,尤其是在安卓端,存在諸多的兼容性問(wèn)題。
問(wèn)題
position:fixed給移動(dòng)端帶來(lái)的問(wèn)題:
- IOS8在頁(yè)面滾動(dòng)時(shí),吸頂不連續(xù);頁(yè)面滑動(dòng)時(shí),不見(jiàn)吸頂,頁(yè)面滾動(dòng)停止后,吸頂緩慢出現(xiàn)
- 滾動(dòng)到頂部之后,會(huì)出現(xiàn)兩個(gè)一樣的吸頂, 過(guò)一會(huì)才恢復(fù)正常。
- footer底部輸入框 focus 狀態(tài),footer 底部輸入框被居中,而不是吸附在軟鍵盤(pán)上部。
- iPhone 4s&5 / iOS 6&7 / Safari 下,頁(yè)面底部footer輸入框失去焦點(diǎn)時(shí),header定位出錯(cuò)。當(dāng)頁(yè)面有滾動(dòng)動(dòng)作時(shí),header定位恢復(fù)正常。
- iPhone 4 / iOS 5 / Safari下,當(dāng)頁(yè)面發(fā)生跳轉(zhuǎn),再退回時(shí),fixed區(qū)域消失,當(dāng)內(nèi)容獲得焦點(diǎn)時(shí),fixed區(qū)域才顯示。
- 安卓低版本/自帶瀏覽器,不支持fixed屬性,iOS4 也是不支持 fixed 的。
- 三星i9100(S2) / 自帶瀏覽器,在滾屏過(guò)程中,fixed定位異常,touchend之后恢復(fù)正常。
- 部分低版本Android對(duì)支持不好,video poster屬性設(shè)置的封面圖會(huì)遮擋fixed元素。
- QQ、UC瀏覽器滾動(dòng)頁(yè)面時(shí)footer定位錯(cuò)誤,會(huì)往上偏移,是由于地址欄收起的緣故。
- *remind:不要在 fixed 元素中使用 input / textarea 元素。
解決方案
分別處理各個(gè)問(wèn)題:
IOS
在IOS端,使用 position: sticky 這個(gè)屬性,使用類似于 position: relative 和 position: absolute 的結(jié)合體。在目標(biāo)區(qū)域在屏幕中可見(jiàn)時(shí),它的行為就像position:relative; 而當(dāng)頁(yè)面滾動(dòng)超出目標(biāo)區(qū)域時(shí),它的表現(xiàn)就像position:fixed,它會(huì)固定在目標(biāo)位置。
使用時(shí),需要加上私有前綴
position: -webkit-sticky; position: -moz-sticky; position: -ms-sticky; position: sticky;
對(duì)于 position:sticky 的使用,需要注意很多的細(xì)節(jié),sticky滿足以下條件才能生效:
1、具有sticky屬性的元素,其父級(jí)高度必須大于sticky元素的高度。
2、sticky元素的底部,不能和父級(jí)底部重疊。(這條不好表述,文后詳細(xì)說(shuō)明)
3、sticky元素的父級(jí)不能含有overflow:hidden 和 overflow:auto 屬性
4、必須具有top,或 bottom 屬性。
同時(shí)要注意,sticky元素僅在他父級(jí)容器內(nèi)有效,超出容器范圍則不再生效了。
安卓
滾動(dòng)距離超過(guò)某位置時(shí),js動(dòng)態(tài)設(shè)置樣式;為了防止慣性滾動(dòng)引起的fix不及時(shí)的情況,在 touchstart、 touchmove 、 touchend 事件都進(jìn)行監(jiān)聽(tīng)。
// 注意處理遮罩層的位置
var scrollHandler = function () {
if (topLength < me.getScrollTop()) {
target.css('position', 'fixed');
me.replaceEle.show();
}
else {
target.css('position', 'relative');
me.replaceEle.hide();
}
};
// 安卓情況下,防止慣性滾動(dòng)引起的fix不及時(shí)的情況
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動(dòng)態(tài)的在節(jié)點(diǎn)在fixed定位于static定位中切換,但是需要對(duì)切換過(guò)程做一些優(yōu)化。
1、使用函數(shù)節(jié)流防抖減少dom操作頻繁粗發(fā),但是保證在規(guī)定時(shí)間內(nèi)必須執(zhí)行一次。
2、使用window.requestAnimationFrame 方法在下一幀前觸發(fā)瀏覽器的強(qiáng)制同步布局,是對(duì)dom的操作能及時(shí)渲染到頁(yè)面上。
3、減少對(duì)dom的讀寫(xiě)操作,或者把dom操作把讀、寫(xiě)操作分開(kāi),可以減少渲染次數(shù)。
原文代碼
(function() {
function Sticky(){
this.init.apply(this, arguments);
}
/**
* 滾動(dòng)fixed組件初始化
* @param {object} setting allocate傳進(jìn)來(lái)的參數(shù)
* @param {object} setting.stickyNode 需要設(shè)置position:sticky的節(jié)點(diǎn),通常是最外層
* @param {object} setting.fixedNode 當(dāng)滾動(dòng)一定距離時(shí)需要fixed在頂部的節(jié)點(diǎn)
* @param {int} setting.top fixed之后距離頂部的top值
* @param {int} setting.zIndex fixed之后的z-index值
* @param {string} setting.fixedClazz fixed時(shí)給fixedNode添加的類
* @param {function} setting.runInScrollFn 滾動(dòng)期間額外執(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時(shí)的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)聽(tīng)window的滾動(dòng)事件
*/
sPro.addEvent = function() {
$(g).on('scroll', this.onscrollCb.bind(this));
};
/**
* 讓函數(shù)在規(guī)定時(shí)間內(nèi)必須執(zhí)行一次
* @param {Function} fn 定時(shí)執(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的兼容性寫(xiě)法,保證在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;
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
部分網(wǎng)站允許空白referer的防盜鏈圖片的js破解代碼
主要是有些網(wǎng)站的圖片調(diào)用是防盜鏈的但一般只是判斷referer是不是自己網(wǎng)站,如果referer為空也會(huì)顯示圖片,所以有了下面的代碼。2011-05-05
JS中不應(yīng)該使用箭頭函數(shù)的四種情況詳解
箭頭函數(shù)給我們的工作帶來(lái)了極大的方便,但是它們有什么缺點(diǎn)呢?我們應(yīng)該一直使用箭頭函數(shù)嗎?我們應(yīng)該在哪些場(chǎng)景中停止使用箭頭函數(shù)?本文就來(lái)為大家詳細(xì)講講2022-07-07
JS彈出層遮罩,隱藏背景頁(yè)面滾動(dòng)條細(xì)節(jié)優(yōu)化分析
下面小編就為大家?guī)?lái)一篇JS彈出層遮罩,隱藏背景頁(yè)面滾動(dòng)條細(xì)節(jié)優(yōu)化分析。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考2016-04-04
如何根據(jù)url?批量下載二維碼實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了如何根據(jù)url批量下載二維碼實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
JS 實(shí)現(xiàn)獲取打開(kāi)一個(gè)界面中輸入的值
JS 實(shí)現(xiàn)獲取打開(kāi)一個(gè)界面中輸入的值,需要的朋友可以參考一下2013-03-03
簡(jiǎn)單聊聊JavaScript中作用域與自執(zhí)行函數(shù)的使用
作用域指的是一個(gè)變量的作用范圍,自執(zhí)行函數(shù)是指定義后立即執(zhí)行的函數(shù),它可以被用來(lái)創(chuàng)建一個(gè)私有作用域,本文主要來(lái)和大家聊聊二者的具體定義與使用,感興趣的可以了解下2024-03-03

