本文转自:https://uedsky.com/2016-06/mobile-modal-scroll/

问题

众所周知,移动端当有 fixed 遮罩背景和弹出层时,在屏幕上滑动能够滑动背景下面的内容,这就是臭名昭著的滚动穿透问题。

之前搜索了一圈,找到下面两种方案:

css 之 overflow: hidden

1
2
3
4
5
6
.modal-open {
&, body {
overflow: hidden;
height: 100%;
}
}

页面弹出层上将 .modal-open 添加到 html 上,禁用 html 和 body 的滚动条,但是这个方案有两个缺点:

  1. 由于 html 和 body的滚动条都被禁用,弹出层后页面的滚动位置会丢失,需要用 js 来还原。
  2. 页面的背景还是能够有滚的动的效果

js 之 touchmove + preventDefault

1
2
3
modal.addEventListener('touchmove', function(e) {
e.preventDefault();
}, false);

这样用 js 阻止滚动后看起来效果不错了,但是也有一个缺点:弹出层里不能有其它需要滚动的内容(如大段文字需要固定高度,显示滚动条也会被阻止)。

上面两个方案都有缺点,今天用英文关键字 google 了一下,才发现原来还有更好的方案:

解决方案 position: fixed

1
2
3
4
body.modal-open {
position: fixed;
width: 100%;
}

如果只是上面的 css,滚动条的位置同样会丢失。
所以如果需要保持滚动条的位置需要用 js 保存滚动条位置关闭的时候还原滚动位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* ModalHelper helpers resolve the modal scrolling issue on mobile devices
* https://github.com/twbs/bootstrap/issues/15852
* requires document.scrollingElement polyfill https://github.com/yangg/scrolling-element
*/
var ModalHelper = (function(bodyCls) {
var scrollTop;
return {
afterOpen: function() {
scrollTop = document.scrollingElement.scrollTop;
document.body.classList.add(bodyCls);
document.body.style.top = -scrollTop + 'px';
},
beforeClose: function() {
document.body.classList.remove(bodyCls);
// scrollTop lost after set position:fixed, restore it back.
document.scrollingElement.scrollTop = scrollTop;
}
};
})('modal-open');

这样上面3个缺点都解决了,至此滚动穿透就完美解决了

完整的示例

document.scrollingElement

因为浏览器获取和设置 scrollTop 存在兼容性,为了简化上面的示例,我直接使用了 document.scrollingElement 这个新标准 ,对于不支持的浏览器我写了个 polyfill document.scrollingElement.js

参考

代码

css自定义滚动条样式,webkit内核浏览器。

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
/* 滚动条样式 */
::-webkit-scrollbar {
/* 滚动条的宽度 */
width: 10px;
margin-right: 7px;
}
::-webkit-scrollbar-track {
/* 滚动条的滑轨背景颜色 */
background-color: #fff;
}
::-webkit-scrollbar-thumb {
/* 滑块颜色 */
background-color: #d6d6d6;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
/* 鼠标移到滑块上面的变色 */
background-color: #666;
}
::-webkit-scrollbar-button {
/* 滑轨两头的监听按钮颜色 */
background-color: #fff;
}
::-webkit-scrollbar-corner {
/* 横向滚动条和纵向滚动条相交处尖角的颜色 */
background-color: #fff;
}

例子

可以点击此处查看:例子

momentjs是一个用于解析、验证、操作和格式化日期的轻量级JavaScript日期库。

使用方式:

1
2
3
4
<script src="moment.js"></script>
<script>
moment().format();
</script>

当前你也可以引入中文语言包:

1
<script src="zh-cn.js"></script>

日期格式化:

1
2
3
4
5
moment().format();     // 2018-05-13T15:35:08+08:00
moment().format('MMMM Do YYYY, h:mm:ss a'); // May 13th 2018, 3:35:08 pm
moment().format('dddd'); // Sunday
moment().format('MMM Do YY'); // May 13th 18
moment().format('YYYY [escaped] YYYY'); // 2018 escaped 2018

使用中文语言包:

1
2
3
4
5
moment().format();      // 2018-05-13T15:45:11+08:00
moment().format('MMMM Do YYYY, h:mm:ss a'); // 五月 13日 2018, 3:45:11 下午
moment().format('dddd'); // 星期日
moment().format('MMM Do YY'); // 5月 13日 18
moment().format('YYYY [escaped] YYYY'); // 2018 escaped 2018

相对时间:

1
2
3
4
5
6
moment('20111031', 'YYYYMMDD').fromNow();    // 7 years ago
moment('20120620', 'YYYYMMDD').fromNow(); // 6 years ago
moment().startOf('day').fromNow(); // 16 hours ago
moment().endOf('day').fromNow(); // in 8 hours
moment().startOf('hour').fromNow(); // 35 minutes ago
moment().endOf('hour').fromNow(); // in 25 minutes

使用中文语言包:

1
2
3
4
5
6
moment('20111031', 'YYYYMMDD').fromNow();     // 7 年前
moment('20120620', 'YYYYMMDD').fromNow(); // 6 年前
moment().startOf('day').fromNow(); // 16 小时前
moment().endOf('day').fromNow(); // 8 小时内
moment().startOf('hour').fromNow(); // 1 小时前
moment().endOf('hour').fromNow(); // 15 分钟内

日历时间:

1
2
3
4
5
6
7
8
moment().subtract(10, 'days').calendar();     // 05/03/2018
moment().subtract(6, 'days').calendar(); // Last Monday at 3:35 PM
moment().subtract(3, 'days').calendar(); // Last Thursday at 3:35 PM
moment().subtract(1, 'days').calendar(); // Yesterday at 3:35 PM
moment().calendar(); // Today at 3:35 PM
moment().add(1, 'days').calendar(); // Tomorrow at 3:35 PM
moment().add(3, 'days').calendar(); // Wednesday at 3:35 PM
moment().add(10, 'days').calendar(); // 05/23/2018

使用中文语言包:

1
2
3
4
5
6
7
8
moment().subtract(10, 'days').calendar();     // 2018/05/03
moment().subtract(6, 'days').calendar(); // 上星期一15:45
moment().subtract(3, 'days').calendar(); // 上星期四15:45
moment().subtract(1, 'days').calendar(); // 昨天15:45
moment().calendar(); // 今天15:45
moment().add(1, 'days').calendar(); // 明天15:45
moment().add(3, 'days').calendar(); // 下星期三15:45
moment().add(10, 'days').calendar(); // 2018/05/23

多语言支持:

1
2
3
4
5
6
7
8
9
10
11
moment.locale();     // en
moment().format('LT'); // 3:35 PM
moment().format('LTS'); // 3:35:08 PM
moment().format('L'); // 05/13/2018
moment().format('l'); // 5/13/2018
moment().format('LL'); // May 13, 2018
moment().format('ll'); // May 13, 2018
moment().format('LLL'); // May 13, 2018 3:35 PM
moment().format('lll'); // May 13, 2018 3:35 PM
moment().format('LLLL'); // Sunday, May 13, 2018 3:35 PM
moment().format('llll'); // Sun, May 13, 2018 3:35 PM

使用中文语言包:

1
2
3
4
5
6
7
8
9
10
11
moment.locale();      // zh-cn
moment().format('LT'); // 15:45
moment().format('LTS'); // 15:45:11
moment().format('L'); // 2018/05/13
moment().format('l'); // 2018/5/13
moment().format('LL'); // 2018年5月13日
moment().format('ll'); // 2018年5月13日
moment().format('LLL'); // 2018年5月13日下午3点45分
moment().format('lll'); // 2018年5月13日 15:45
moment().format('LLLL'); // 2018年5月13日星期日下午3点45分
moment().format('llll'); // 2018年5月13日星期日 15:45

如果对于momentjs你想了解更加详细的信息,可以点此查看:

  1. 我写的一个例子:momentjs例子
  2. momentjs官网:momentjs官网
  3. momentjs中文网:momentjs中文网

前段时间做的一个集助活动,其中有一个倒计时,需要计算两个时间节点之间的差值,然后转化为时分秒,我用的是将两个时间节点传入new Date(),然后getTime()计算差值,代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 仅是两个时间点是同一天的简单换算
function timeFormat(newTimeStr, oldTimeStr){
var newTime = new Date(newTimeStr).getTime();
var oldTime = new Date(oldTimeStr).getTime();
var diffTime = newTime - oldTime;
if(diffTime < 0){
return "请确保参数顺序";
}
var hh = Math.floor(diffTime / 60 / 60 / 1000);
hh = hh < 10 ? "0" + hh : hh;
diffTime -= hh * 60 * 60 * 1000;
var mm = Math.floor(diffTime / 60 / 1000);
mm = mm < 10 ? "0" + mm : mm;
diffTime -= mm * 60 * 1000;
var ss = Math.floor(diffTime / 1000);
return hh + ":" + mm + ":" + ss;
}
timeFormat("2018-05-11 15:09:09", "2018-05-11 12:10:10");

上面代码在Google浏览器正常,在安卓手机里面也正常,但是在iphone内一直出问题,我在网上查了查,大致意思是Safari不支持”2018-05-11 15:10:10”这种时间格式的new Date()参数,所以我写了下面这个例子来试试:
可点击此处查看,或者是直接用手机扫描下面的二维码:二维码

例子代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ios端关于Date()格式的兼容</title>
</head>

<body>
<script>
alert("new Date('2018-05-11 12:00:00').getMonth() :" + new Date("2018-05-11 12:00:00").getMonth());
alert("new Date('2018/05/11 12:00:00').getMonth() :" + new Date("2018/05/11 12:00:00").getMonth());
var time = '2018-05-11 12:00:00';
var arr = time.split(/[- : \/]/);
var d = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
alert("new Date(" + arr[0] + "," + (arr[1] - 1) + "," + arr[2] + "," + arr[3] + "," + arr[4] + "," + arr[5] + ") : " + d.getMonth());
</script>
</body>

</html>

例子是为了试一下浏览器的new Date()对’2018-05-11 12:00:00’和’2018/05/11 12:00:00’时间格式的兼容性。

如果你用的是Safari,那么你应该会看到第一个的提示是:

new Date(‘2018-05-11 12:00:00’).getMonth() :NAN

说明其不兼容’2018-05-11 12:00:00’这种时间格式,但是我们为了兼容,所以我将上面的第一段代码更改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 仅是两个时间点是同一天的简单换算
function timeFormat(newTimeStr, oldTimeStr){
newTimeStr = newTimeStr.replace(/-/g, "/");
oldTimeStr = oldTimeStr.replace(/-/g, "/");
var newTime = new Date(newTimeStr).getTime();
var oldTime = new Date(oldTimeStr).getTime();
var diffTime = newTime - oldTime;
if(diffTime < 0){
return "请确保参数顺序";
}
var hh = Math.floor(diffTime / 60 / 60 / 1000);
hh = hh < 10 ? "0" + hh : hh;
diffTime -= hh * 60 * 60 * 1000;
var mm = Math.floor(diffTime / 60 / 1000);
mm = mm < 10 ? "0" + mm : mm;
diffTime -= mm * 60 * 1000;
var ss = Math.floor(diffTime / 1000);
ss = ss < 10 ? "0" + ss : ss;
return hh + ":" + mm + ":" + ss;
}
timeFormat("2018-05-11 15:09:09", "2018-05-11 12:10:10");

如此就兼容所有的浏览器了。关于Date的详细信息可查看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date

在做移动端的时候,我们会遇到横竖屏样式该如何使用,我们可以定义两个最顶层的父类class,然后根据css的后代选择器来定义相应的样式,但我们今天要说的是利用媒体查询来做。

关于媒体查询的具体文档可查看https://developer.mozilla.org/zh-CN/docs/Web/CSS/@media ,这里就不做过多描述,咱们这次主要说的媒体特性是orientation

orientation

orientation特性可用于基于视口的方向应用样式。我们恰恰使用此特性来实现。

使用方法

基本用法上如下:

1
2
3
4
5
6
7
@media screen and (orientation: landscape) {
/* 横屏 */
}

@media screen and (orientation: portrait) {
/* 竖屏 */
}

可以直接点击此处查看例子,下面贴出例子代码:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>orientation</title>
<style>
@media screen and (orientation: portrait) {
/*竖屏*/
body {
color: green;
}
}
@media screen and (orientation: landscape) {
/*横屏*/
body {
color: red;
}
}
</style>
</head>
<body>
<h2>查看手机横竖屏:</h2>
<ol>
<li>竖屏时字体颜色为绿色</li>
<li>横屏时字体颜色为红色</li>
</ol>
</body>
</html>