移动端的总体布局通常来讲能够分为上中下三个部分,分别为 header、main、footer,其中header、footer 是固定高度,分别固定在页面顶部和页面底部,而 main 是占据页面其他位置,而且能够滚动。html
页面布局以下:web
<body>
<div class="header"></div>
<div class="main"></div>
<div class="footer"></div>
</body>
根据页面滚动的位置分为两种布局,一种是滚动 body,另外一种是固定 body 的高度为100%,在 main 中滚动。浏览器
第一种布局有个优势,就是页面的地址栏会随着 body 的滚动隐藏起来,而且 Android 设备中,滚动 body 会更加的流畅,若是项目中有相似需求能够考虑。工具
实现布局的方式以下:布局
body { overflow: auto; } .header, .footer { position: fixed; left: 0; right: 0; height: 44px; } .header { top: 0; } .footer { bottom: 0; } .main { height: 100%; padding: 44px 0; }
第一种状况比较适合长列表页面,整个页面除了 header 和 footer 以外都须要滚动,但不少时候,咱们只但愿页面的某个元素滚动,这个时候,就采起第二种布局方式。flex
这种页面布局有三种相对简单的实现方式:flexbox
最容易想到的实现方式是 fixed 定位,实现方式以下:spa
html, body { height: 100%; overflow: hidden; } .header, .footer { position: fixed; left: 0; right: 0; height: 44px; } .header { top: 0; } .footer { bottom: 0; } .main { height: 100%; padding: 44px 0; box-sizing: border-box; }
fixed 定位实现起来简单,在大多数浏览器中也能正常显示,可是 fixed 定位在移动端会有兼容性问题,后面会提到,因此不建议这种实现方式。code
absolute 定位和 fixed 定位相似,只要把 header 的 footer 的 position 改成 absolute 就能够了。orm
细心的小伙伴可能发现了,这里的 main 没有设置 overflow ,由于这里有一个坑,不论是absolute 定位仍是 fixed 定位都同样,为了方便描述,如下只说 fixed 定位(在 absolute 定位也同样成立)。在PC端没有问题,可是在移动端,若是 main 设置了 overflow 为 true,header 会被 main 遮住,对,没有错,虽然是 fixed 定位,可是在移动端,若是 fixed 定位节点后面紧接跟着的兄弟节点是可滚动的(也就是设置了 overflow 为 true ),那么 fixed 节点会被其后的兄弟节点遮住。
这个问题解决方式有不少,既然是 fixed 定位后面紧接着可滚动的兄弟节点才会有这个坑,只要让他的条件有一个不成立就行了,有如下解决方案:
第一种方方案有如下可选方法:
1. 把全部 fixed 节点放在 scroll 元素后面,即把 header 节点放在 main 节点后面
<body>
<div class="main"></div>
<div class="header"></div>
<div class="footer"></div>
</body>
但这样显然不太符合通常人的思惟习惯,代码可读性下降。
2. 使 main 不可滚动,给 main 嵌套一层可滚动的子节点
<body> <div class="header"></div> <div class="main"> <div class="scroll-container"></div> </div> <div class="footer"></div> </body> <style> .main { overflow: hidden; } .scroll-container { height: 100%; overflow: auto; } </style>
第二种方案有如下可选方法:
1. 让 scroll 节点不与 fixed 节点有重合
body { padding: 44px 0; } .main { padding: 0; }
2. 给 fixed 节点设置 z-index
.header, .footer { z-index: 8888; }
简单的方式--->>>第三种实现方式,flex 布局。flex 定位在移动端兼容到了 iOS 7.1+,Android 4.4+,若是使用 autoprefixer 等工具还能够降级为旧版本的 flexbox ,能够兼容到 iOS 3.2 和 Android 2.1。并且用 flex 实现起来相对简单,在各个浏览器里表现也相对一致。实现以下:
body { display: flex; flex-direction: column; } .main { flex: 1; overflow: auto; -webkit-overflow-scrolling: touch; } .header { height: 44px; } .footer { height: 44px; }
要在有 input 标签的页面使用 fixed 定位,由于这二者在一块儿的时候,老是会有奇奇怪怪的问题。
在 iOS 上,当点击 input 标签获取焦点唤起软键盘的时候,fixed 定位会暂时失效,或者能够理解为变成了 absolute 定位,在含有滚动的页面,fixed 定位的节点和其余节点一块儿滚动。
其实这个问题也很好解决,只要保证 fixed 定位的节点的父节点不可滚动,那么即便 fixed 定位失效,也不会和其余滚动节点一块儿滚动,影响界面。
可是除此以外,还有不少坑比较难以解决,例如 Android 软键盘唤起后遮挡住 input 标签,用户无法看到本身输入的字符串,iOS 则须要在输入至少一个字符以后,才能将对应的 input 标签滚动到合适的位置,因此为了避开这些难以解决的坑,在有表单输入的页面,尽可能用absolute 或者 flex 替换 fixed。
在 Web 开发中,常常要对表单元素的输入进行限制,好比说不容许输入特殊字符,标点。一般咱们会监听 input 事件:
inputElement.addEventListener('input', function(event) { let regex = /[^1-9a-zA-Z]/g; event.target.value = event.target.value.replace(regex, ''); event.returnValue = false });
这段代码在 Android 上是没有问题的,可是在 iOS 中,input 事件会截断非直接输入,什么是非直接输入呢,在咱们输入汉字的时候,好比说「喜茶」,中间过程当中会输入拼音,每次输入一个字母都会触发 input 事件,然而在没有点选候选字或者点击「选定」按钮前,都属于非直接输入。
因此输入「喜茶」两个字,会触发6次 input 事件,若是把每次 input 的 value 打印出来,结果以下:
这显然不是咱们想要的结果,咱们但愿在直接输入以后才触发 input 事件,这就须要引出我要说的两个事件—— compositionstart 和 compositionend。
compositionstart 事件在用户开始进行非直接输入的时候触发,而在非直接输入结束,也即用户点选候选词或者点击「选定」按钮以后,会触发 compositionend 事件。
var inputLock = false; function do(inputElement) { var regex = /[^1-9a-zA-Z]/g; inputElement.value = inputElement.value.replace(regex, ''); } inputElement.addEventListener('compositionstart', function() { inputLock = true; }); inputElement.addEventListener('compositionend', function(event) { inputLock = false; do(event.target); }) inputElement.addEventListener('input', function(event) { if (!inputLock) { do(event.target); event.returnValue = false; } });
添加一个 inputLock 变量,当用户未完成直接输入前,inputLock 为 true,不触发 input 事件中的逻辑,当用户完成有效输入以后,inputLock 设置为 false,触发 input 事件的逻辑。这里须要注意的一点是,compositionend 事件是在 input 事件后触发的,因此在 compositionend事件触发时,也要调用 input 事件处理逻辑。
iOS设备上,因为retina屏的缘由,1px 的 border 会显示成两个物理像素,因此看起来会感受很粗,这是一个移动端开发常见的问题。解决方案有不少,但都有本身的优缺点。
0.5px border
从iOS 8开始,iOS 浏览器支持 0.5px 的 border,可是在 Android 上是不支持的,0.5px 会被认为是 0px,因此这种方法,兼容性是不好的。
背景渐变
CSS3 有了渐变背景,能够经过渐变背景实现 1px 的 border,实现原理是设置 1px 的渐变背景,50% 有颜色,50% 是透明的。
@mixin commonStyle() { background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%; background-repeat: no-repeat; background-position: top, right top, bottom, left top; } @mixin border($border-color) { @include commonStyle(); background-image:linear-gradient(180deg, $border-color, $border-color 50%, transparent 50%), linear-gradient(270deg, $border-color, $border-color 50%, transparent 50%), linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%), linear-gradient(90deg, $border-color, $border-color 50%, transparent 50%); }
这种方法虽然可行,可是没有办法实现圆角。
伪类 + transform
这类方法的实现原理是用伪元素的 box-shadow 或 border 实现 border,而后用 transform缩小到原来的一半。即便有圆角的需求也能很好的实现。
@mixin hairline-common($border-radius) { position: relative; z-index: 0; &:before { position: absolute; content: ''; border-radius: $border-radius; box-sizing: border-box; transform-origin: 0 0; } } @mixin hairline($direct: 'all', $border-color: #ccc, $border-radius: 0) { @include hairline-common($border-radius); &:before { transform: scale(.5); @if $direct == 'all' { top: 0; left: 0; width: 200%; height: 200%; box-shadow: 0 0 0 1px $border-color; z-index: -1; } @else if $direct == 'left' or $direct == 'right' { #{$direct}: 0; top: 0; width: 0; height: 200%; border-#{$direct}: 1px solid $border-color; } @else { #{$direct}: 0; left: 0; width: 200%; height: 0; border-#{$direct}: 1px solid $border-color; } } }