在很多APP上,我们经常能见到菜单滚动置顶、点击置顶的场景,说的通俗一点就是:起初页面初始化的时候,菜单处于文档流中,当我们向上滑动页面的时候,菜单脱离文档流,悬浮于页面顶端,且将一直处于顶端,而当我们向下滑动页面的至最初的位置时,菜单又回到文档流当中了。

滚动置顶的思路

要想让菜单固定在顶端,我们想到的当然是position:fixed啦~ 上代码来试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<template>
<div class="mod-page js-body">
<section class="mod-content js-mod-body index">
<!--省略部分结构-->
<div class="mod-filter bc-main com-space" id="menuNav" :style="{'height':menuHeight + 'px'}">
<div class="mod-filter bc-main" :class="{'fixed-menu-fixed': isFixedMenu}" id="topMenu">
<nav class="filter-nav">
<ul class="mod-list mod-flex com-border-bottom">
<li class="mod-list-item mod-flex-item" v-for="item in filterData">
<a href="javascript:;" class="mod-list-info">{{item}}</a>
</li>
</ul>
</nav>
</div>
</div>
<!--省略部分结构-->
</section>
</div>
</template>
<script>
import scrollTo from 'scrollTo.js'
export default {
data () {
return {
filterData: ["区域", "职位类型", "职位排序", "筛选"],
platform: Util.OS(),
isFixedMenu: false, //是否Fixed menu
menuTop: 48, // menu固定top
menuHeight: 44 // menu高度
threshold: 100
}
},
methods: {
/**
* 初始化
* @method initFun
* @public
* @return {Null} void
*/
initFun() {
let self = this;
//注册滚动事件
self.registerScrollEvent();
},
/**
* 注册滚动事件,用以实现menu固定
* @method registerScrollEvent
* @public
* @return {Null} void
*/
registerScrollEvent () {
let self = this;
$(document).on('scroll', function(e) {
let scrolltop = document.body.scrollTop;
if( scrolltop >= threshold) {
self.isFixedMenu = true;
} else if (scrolltop < threshold + menuHeight) {
self.isFixedMenu = false;
}
});
}
},
created() {
let self = this;
self.initFun();
}
}
</script>
<!--省略部分样式-->
<style src="static/css/test.css"></style>
<style type="text/css">
.fixed-menu-fixed {
animation: fade-in .3s;
position: fixed !important;
}
@keyframes fade-in {
0% {
opacity: 0;
}
10% {
opacity: 0.1;
}
20% {
opacity: 0.2;
}
30% {
opacity: 0.3;
}
40% {
opacity: 0.4;
}
50% {
opacity: 0.5;
}
60% {
opacity: 0.6;
}
70% {
opacity: 0.7;
}
80% {
opacity: 0.8;
}
90% {
opacity: 0.9;
}
100% {
opacity: 1;
}
}
</style>

我们通过监听页面的滚动scroll事件,在滚动事件回调中判断菜单滚动位置是否超出阀值threshold,如果超出阀值则设置成固定布局position: fixed(脱离文档流),如果小于阀值+菜单高度,则恢复原有布局(回到文档流),经过试验,安卓手机上是没问题的,需要注意的是:使用position: fixed会使页面中菜单脱离文档流,那么将影响它下面的DOM结构,出现突然抖动上移的情况,对于这种情况,我们需要做占位处理,即给菜单套一层有固定高度(menuHeight)的div。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="mod-page js-body">
<section class="mod-content js-mod-body index">
<!--省略部分结构-->
<div class="mod-filter bc-main com-space" id="menuNav" :style="{'height':menuHeight + 'px'}">
<div class="mod-filter bc-main" :class="{'fixed-menu-fixed': isFixedMenu}" id="topMenu">
<nav class="filter-nav">
<ul class="mod-list mod-flex com-border-bottom">
<li class="mod-list-item mod-flex-item" v-for="item in filterData">
<a href="javascript:;" class="mod-list-info">{{item}}</a>
</li>
</ul>
</nav>
</div>
</div>
<!--省略部分结构-->
</section>
</div>
</template>

但是对于ios来说,只能在滚动结束后才会触发scroll回调,即便监听’touchmove’实时触发滚动回调,对于菜单布局的渲染也是阻塞的,也就是会延迟很多。。。,多么尴尬的岁月~多么尴尬的日子~多么尴尬~好想改变一切~只是没有银子但有方法,哈哈!下面隆重介绍一下粘性布局position: sticky

粘性布局(position: sticky)

网络上关于粘性布局(position: sticky)的博客很多,这里引用一下地址(本人太懒,O(∩_∩)O):
使用 position:sticky 实现粘性布局
position:sticky实现iOS6+下的粘性布局

好了,了解了粘性布局(position: sticky)之后,我们对代码进行了修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<template>
<div class="mod-page js-body">
<section class="mod-content js-mod-body index">
<!--省略部分结构-->
<div class="mod-filter bc-main com-space" id="menuNav" :style="{'height':menuHeight + 'px'}" v-if="platform == 'android'">
<div class="mod-filter bc-main" :class="{'fixed-menu-fixed': isFixedMenu}" id="topMenu">
<nav class="filter-nav">
<ul class="mod-list mod-flex com-border-bottom">
<li class="mod-list-item mod-flex-item" v-for="item in filterData">
<a href="javascript:;" class="mod-list-info">{{item}}</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="mod-filter bc-main com-space" :class="{'fixed-menu-sticky': isStickyMenu, 'active': clickedMenuItem != -1}" id="menuNav" v-if="platform == 'ios'">
<nav class="filter-nav">
<ul class="mod-list mod-flex com-border-bottom">
<li class="mod-list-item mod-flex-item" v-for="(item, index) in filterData" @click="filterMenuClicked()">
<a href="javascript:;" class="mod-list-info">{{item}}</a>
</li>
</ul>
</nav>
</div>
<!--省略部分结构-->
</section>
</div>
</template>
<script>
import scrollTo from 'scrollTo.js'
export default {
data () {
return {
filterData: ["区域", "职位类型", "职位排序", "筛选"],
platform: Util.OS(),
isFixedMenu: false, //是否Fixed menu
isStickyMenu: false, //是否Sticky menu
menuTop: 48, // menu固定top
menuHeight: 44 // menu高度
threshold: 100
}
},
methods: {
/**
* 初始化
* @method initFun
* @public
* @return {Null} void
*/
initFun() {
let self = this;
//注册滚动事件
self.registerScrollEvent();
},
/**
* 注册滚动事件,用以实现menu固定
* @method registerScrollEvent
* @public
* @return {Null} void
*/
registerScrollEvent () {
let self = this;
/*##############主要看这里###################*/
let menuId = 'topMenu';
if(Util.OS() == 'ios') {
menuId = 'menuNav';
}
// 设置菜单的top位置
$(`#${menuId}`).css('top', self.menuTop + 'px');
if(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)){
self.isStickyMenu = true;
} else if (/android/i.test(navigator.userAgent)) {
$(document).on('scroll', function(e) {
let scrolltop = document.body.scrollTop;
if(/android/i.test(navigator.userAgent)){
if( scrolltop >= threshold) {
self.isFixedMenu = true;
} else if (scrolltop < threshold + menuHeight) {
self.isFixedMenu = false;
}
}
});
}
},
filterMenuClicked () {
let self = this;
$('body').scrollTo({toT: self.menuTop, durTime : 200});
}
},
created() {
let self = this;
self.initFun();
}
}
</script>
<!--省略部分样式-->
<style src="static/css/test.css"></style>
<style type="text/css">
.fixed-menu-fixed {
animation: fade-in .3s;
position: fixed !important;
}
/*##############主要看这里###################*/
.fixed-menu-sticky {
position: -webkit-sticky !important;
position: -moz-sticky !important;
position: -ms-sticky !important;
position: -o-sticky !important;
position: sticky !important;
}
@keyframes fade-in {
0% {
opacity: 0;
}
10% {
opacity: 0.1;
}
20% {
opacity: 0.2;
}
30% {
opacity: 0.3;
}
40% {
opacity: 0.4;
}
50% {
opacity: 0.5;
}
60% {
opacity: 0.6;
}
70% {
opacity: 0.7;
}
80% {
opacity: 0.8;
}
90% {
opacity: 0.9;
}
100% {
opacity: 1;
}
}
</style>

因为安卓手机需要一个占位,而ios不需要,所以我们分开处理。在registerScrollEvent方法中,我们首先判断是ios设备还是安卓设备,如果是ios设备,我们直接使用position: sticky;来将菜单设置成粘性布局,并给它一个top值,这个值表示当元素距离页面视口(Viewport,也就是fixed定位的参照)顶部距离大于 0px 时,元素以 relative 定位表现,而当元素距离页面视口小于 0px 时,元素表现为 fixed 定位,也就会固定在顶部。

实现点击滚动置顶

思路很简单,就是点击菜单项,将页面滚动至我们设定的位置。我们前端采用的是zepto这个轻库进行开发的,因此想采用animate({scrollTop:”100px”})函数进行页面的动画滚动,结果就是毫无效果。。。,发现zepto的animate()源码采用css3的方式进行,而scrollTop属性不在css3的动画属性中,所以没有生效。接下来的方法就是自己写一个滚动条上下滚动的方法。那么,我们来写一写:。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* scrollTo.js */
$.fn.scrollTo =function(options){
var defaults = {
toT : 0, //滚动目标位置
durTime : 500, //过渡动画时间
delay : 30, //定时器时间
callback:null //回调函数
};
var opts = $.extend(defaults,options),
timer = null,
_this = this,
curTop = _this.scrollTop(),//滚动条当前的位置
subTop = opts.toT - curTop, //滚动条目标位置和当前位置的差值
index = 0,
dur = Math.round(opts.durTime / opts.delay),
smoothScroll = function(t){
index++;
var per = Math.round(subTop/dur);
if(index >= dur){
_this.scrollTop(t);
window.clearInterval(timer);
if(opts.callback && typeof opts.callback == 'function'){
opts.callback();
}
return;
}else{
_this.scrollTop(curTop + index*per);
}
};
timer = window.setInterval(function(){
smoothScroll(opts.toT);
}, opts.delay);
return _this;
};

采用原型函数的方式,制做一个scrollTo方,效果还不错~~哈!然后,我们给菜单项绑定点击事件filterMenuClicked,在filterMenuClicked中实现点击滚动置顶。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<template>
<div class="mod-page js-body">
<section class="mod-content js-mod-body index">
<!--省略部分结构-->
<div class="mod-filter bc-main com-space" id="menuNav" :style="{'height':menuHeight + 'px'}" v-if="platform == 'android'">
<div class="mod-filter bc-main" :class="{'fixed-menu-fixed': isFixedMenu}" id="topMenu">
<nav class="filter-nav">
<ul class="mod-list mod-flex com-border-bottom">
<li class="mod-list-item mod-flex-item" v-for="item in filterData">
<a href="javascript:;" class="mod-list-info">{{item}}</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="mod-filter bc-main com-space" :class="{'fixed-menu-sticky': isStickyMenu, 'active': clickedMenuItem != -1}" id="menuNav" v-if="platform == 'ios'">
<nav class="filter-nav">
<ul class="mod-list mod-flex com-border-bottom">
<li class="mod-list-item mod-flex-item" v-for="(item, index) in filterData" @click="filterMenuClicked()">
<a href="javascript:;" class="mod-list-info">{{item}}</a>
</li>
</ul>
</nav>
</div>
<!--省略部分结构-->
</section>
</div>
</template>
<script>
import scrollTo from 'scrollTo.js'
export default {
data () {
return {
filterData: ["区域", "职位类型", "职位排序", "筛选"],
platform: Util.OS(),
isFixedMenu: false, //是否Fixed menu
isStickyMenu: false, //是否Sticky menu
menuTop: 48, // menu固定top
menuHeight: 44 // menu高度
threshold: 100
}
},
methods: {
/**
* 初始化
* @method initFun
* @public
* @return {Null} void
*/
initFun() {
let self = this;
//注册滚动事件
self.registerScrollEvent();
},
/**
* 注册滚动事件,用以实现menu固定
* @method registerScrollEvent
* @public
* @return {Null} void
*/
registerScrollEvent () {
let self = this;
if(/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)){
self.isStickyMenu = true;
} else if (/android/i.test(navigator.userAgent)) {
$(document).on('scroll', function(e) {
let scrolltop = document.body.scrollTop;
if(/android/i.test(navigator.userAgent)){
if( scrolltop >= threshold) {
self.isFixedMenu = true;
} else if (scrolltop < threshold + menuHeight) {
self.isFixedMenu = false;
}
}
});
}
},
filterMenuClicked () {
let self = this;
/*##############主要看这里###################*/
$('body').scrollTo({toT: self.menuTop, durTime : 200});
}
},
created() {
let self = this;
self.initFun();
}
}
</script>
<!--省略部分样式-->
<style src="static/css/test.css"></style>
<style type="text/css">
.fixed-menu-fixed {
animation: fade-in .3s;
position: fixed !important;
}
.fixed-menu-sticky {
position: -webkit-sticky !important;
position: -moz-sticky !important;
position: -ms-sticky !important;
position: -o-sticky !important;
position: sticky !important;
}
@keyframes fade-in {
0% {
opacity: 0;
}
10% {
opacity: 0.1;
}
20% {
opacity: 0.2;
}
30% {
opacity: 0.3;
}
40% {
opacity: 0.4;
}
50% {
opacity: 0.5;
}
60% {
opacity: 0.6;
}
70% {
opacity: 0.7;
}
80% {
opacity: 0.8;
}
90% {
opacity: 0.9;
}
100% {
opacity: 1;
}
}
</style>

好啦,先记录到这儿吧