移动端1px/100vh/滚动穿透问题
1px的实现
上周五,从vant
库中拷出了hairline
的源码,发现有一个他的实现是比较好的,不需要进行额外的media-query
那么他是如何做到的呢?代码再贴一遍:
/* scss */
@mixin border-surround-1px($color, $raduis) {
&::after {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
top: -50%;
right: -50%;
bottom: -50%;
left: -50%;
border: 0 solid $color;
transform: scale(0.5);
border-radius: $raduis;
}
&,
&--top,
&--left,
&--right,
&--bottom,
&--surround {
position: relative;
}
&--top::after {
border-top-width: 1px;
}
&--left::after {
border-left-width: 1px;
}
&--right::after {
border-right-width: 1px;
}
&--bottom::after {
border-bottom-width: 1px;
}
&--surround::after {
border-width: 1px;
}
}
大致的1px的整体结构都是用伪元素+绝对定位实现的,主要的不同在于具体实现1px的方式
@mixin border-1px($color, $opacity) {
position: relative;
&:after {
display: block;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
border-top: 1px solid $color;
opacity: $opacity;
content: ' ';
}
}
/* 进行缩放 */
@media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5) {
.border-1px {
&:after {
-webkit-transform: scaleY(0.7);
transform: scaleY(0.7);
}
}
}
@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {
.border-1px {
&:after {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
}
@media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
.border-1px {
&:after {
-webkit-transform: scaleY(0.3);
transform: scaleY(0.3);
}
}
}
采用的是把伪元素after变成一条线,然后定位到元素底部,再进行缩放
主要是利用了伪元素加缩放
的这两个功能,在需要1px边框的元素上定义一个伪元素,然后伪元素相对于这个元素是绝对定位,通过伪元素去画一个1px的边框,然后我们把它定位这个元素的下面,然后我们在去应用一个class去对这个伪元素进行缩放(纵轴Y轴进行缩放)
而vant不需要media-query,他采用的是一种比较讨巧的方法:
/* 不设置宽高,然后利用绝对布局+各个方向的-50%实现了 */
xxx {
position: absolute;
top: -50%;
right: -50%;
bottom: -50%;
left: -50%;
}
已知top/left/right/bottom,设置百分比是根据父元素的宽高来定的,于是不设置宽高,而采用四个方向的-50%,会相当于把自身相比于父元素扩大了一倍
但是border-width: 1px;
,却始终是1px
,最后再利用transform: scale(0.5);
就实现了缩小一倍,也就是实现了vant文档里面的0.5px
(细边框),这也算讨巧的解决了移动端1px的问题吧...
ps:其中的pointer-events: none;
可以直接MDN得知,主要是为了避免伪元素变成了鼠标点击事件的target
:
pointer-events
CSS 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target。
100vh在chrome/qq浏览器中的问题
主要参考:在移动前端上避免使用100vh单位
核心问题是移动浏览器,其中地址栏有时可见,有时隐藏,从而改变了视口的可见大小。 这些浏览器没有将
100vh
高度调整为视口高度变化时屏幕的可见部分,而是将100vh
设置为浏览器的高度,并隐藏了地址栏。 结果是,当地址栏可见时,屏幕的底部将被切除更糟糕的是,当用户首次访问移动设备上的网站时,地址栏将在顶部可见
于是就会出现在chrome/qq浏览器中,当设置100vh的body的时候,页面会出现滚动
文中提出的解决方案是:
页面加载时,将高度设置为window.innerHeight
可以将高度正确设置为窗口的可见部分
而我自己使用的height:100%
应该也算是误打误撞了...
滚动穿透的处理
记得之前自己也是直接搜的解决,直接参考链接吧...
记录一下,相当于做个拷贝(转载需带上参考链接):
分为固定弹窗和滚动弹窗的滚动穿透
固定弹窗的解决办法:
- 弹窗时给body设置overflow:hidden;(缺点:ios没用)
- 弹窗时给body设置position:fixed;(缺点:滚动位置会丢失,ios没用)
- 最佳:给弹窗加上
@touchmove.stop.prevent
,即可阻止touchmove
事件传递到body
,也就解决了滚动穿透。
滚动弹窗的解决方法:
在弹窗打开的时候给body的全局滚动设置position:fixed
属性,并设置top值;由于设置了fixed属性,那在弹窗的时候body就没有滚动条了。此时如果这么设置会发现body虽然没有了滚动穿透,但是原来的位置丢失了。所以再给body设置fixed属性的时候,要把当前的滚动位置赋值给css的top属性,那在视觉上就没有任何变化了
fixedBody () {
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop
document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;'
}
弹窗关闭的时候则要清除fixed固定定位和top值;并设置其滚动位置位置top值,则又恢复了滚动功能,而且视觉上没有任何变化,是目前最完美的解决方案!
looseBody () {
let body = document.body
body.style.position = ''
let top = body.style.top
document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top)
body.style.top = ''
}
而评论区的redbuck也给出了不使用better-scroll,而是用AlloyTouch的方法:
better-scroller太重了.
可以试试AlloyTouch,只有300多行.你可以只用它监视dom上的滑动.然后拿到值后自己去做滚动.
<div ref=wrap>
<div :style="{transfrom: `translateY(${offsetY}px)`}">
<div>
<div>
new AlloyTouch({
target: this.$refs.wrap,
change: v => {
this.offsetY = v
}
})