移动端问题锦集

移动端 1px 问题

产生原因

简单点说就是因为 Retine 屏的分辨率始终是普通屏幕的 2 倍,1px 的边框在devicePixelRatio=2的 retina 屏下会显示成 2px,所以在高清屏下 1px 就显得和 2px 差不多,具体细节可以参考这篇文章

解决方案

  • 使用媒体查询
.border_1px {
  border: 1px solid #f1f1f1;
}

@media (-webkit-min-device-pixel-ratio: 2) {
  .border_1px {
    border: 0.5px solid #f1f1f1;
  }
}

缺点

兼容性差,有的 retina 屏会将 0.5 识别成 0px

  • 伪类配合 transform
border-1px($color = #ccc, $radius = 2px, $style = solid)
  position relative

  &:after
    content ''
    pointer-events none // 解决iphone上的点击无效Bug
    display block
    position absolute
    left 0
    top 0
    transform-origin 0 0
    border 1px $style $color
    border-radius $radius
    box-sizing border-box
    width 100%
    height 100%

    @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2)
      width 200%
      height 200%
      border-radius $radius * 2
      transform scale(0.5) translateZ(0)

    @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3)
      width 300%
      height 300%
      border-radius $radius * 3
      transform scale(0.33) translateZ(0)

缺点

如果碰上其他伪类并存,则需要多层嵌套

  • 使用 box-shadow 模拟
.border_1px {
  box-shadow: inset 0px -1px 1px -1px #f1f1f1;
}

缺点

边框有阴影,颜色略浅

  • 使用 view-port

对于devicePixelRatio=2的:

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

对于devicePixelRatio=3的:

<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">

可以使用js判断后动态插入:

let dpr = 1;
let ratio = window.devicePixelRatio;
let doc = document;
let head = doc.querySelector("head");
if (ratio === 2) {
  dpr = 2;
} else if (ratio === 3) {
  dpr = 3;
}
let scale = 1 / dpr;
let meta = doc.createElement("meta");
meta.setAttribute("name", "viewport");
meta.setAttribute(
  "content",
  `initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`
);
head.appendChild(meta);

rempx 的取舍

如果项目只适配手机端,pxrem可以根据个人习惯自主选择。

如果项目适配多种设备,比如手机和 pad 设备,分辨率差的比较大的情况下,使用rem最优

移动端点击背景图/链接有底色

/* 点击的时候底色透明即可 */
-webkit-tap-highlight-color: transparent;

禁止选择文字/图片

-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;

禁止复制/保存图片

img {
  -webkit-touch-callout: none;
}

页面动画容易出现闪烁白屏

使用translate3d优化

-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);

使用float排列布局容易出现断层

出现原因

在使用float布局列表的时候,容易出现某一行只出现第一个位置或者最后一个位置的内容,出现这个的原因是,上一行有某个内容的高度和其他高度不同的原因,导致将底下那一行的内容给挤了下去。

解决

给每个item设置一个最小高度/固定高度即可:

.item {
  min-height: 200px;
}

斜边布局

/* 斜边部分 原理和小三角一样 */
position: absolute;
width: 0;
height: 0;
border-width: 100px 0 0 414px; /* 底边的宽度要等于设备宽度 */
border-style: solid;
border-color: transparent transparent gray gray;
bottom: 0;
z-index: 99;

实现效果:

transform 和 fixed

在父元素使用transform下使用fixed,将会使fixed失效。

解决方案是:

  • 在使用了transform的容器中不使用fixed,换absolute,并需要通过 js 计算固定位置
  • 依然使用fixed,不过使用fixed的容器放在使用transform的容器之外。

遮罩层滚动点透问题

在出现遮罩层的时候,我们可以使用@touchmove.prevent来防止点透的问题出现,也就是遮罩层背后的内容还能滚动的问题。但是如果这样的话,如果遮罩层内有需要滚动的容器,那将使容器的滚动也失效。

通过自身的了解和网上的收集,主要解决方案有以下几种:

  • body设置 css{overflow:hidden}

    问题

    IOS 上无效

  • body,html都设置 css{overflow:hidden}

    问题

    a - 滚动位置会丢失,需要通过 js 设置 scrollTop

    b - 没效果

  • 目前比较完美的解决方案

    (function() {
      var scrollTop = 0;
    
      // 显示弹出层
      open.onclick = function() {
        // 在弹出层显示之前,记录当前的滚动位置
        scrollTop = getScrollTop();
    
        // 使body脱离文档流
        document.body.classList.add("dialog-open");
    
        // 把脱离文档流的body拉上去!否则页面会回到顶部!
        document.body.style.top = -scrollTop + "px";
    
        mask.style.display = "block";
      };
    
      // 隐藏弹出层
      close.onclick = function() {
        mask.style.display = "none";
    
        // body又回到了文档流中(我胡汉三又回来啦!)
        document.body.classList.remove("dialog-open");
    
        // 滚回到老地方
        to(scrollTop);
      };
    
      function to(scrollTop) {
        document.body.scrollTop = document.documentElement.scrollTop = scrollTop;
      }
      function getScrollTop() {
        return document.body.scrollTop || document.documentElement.scrollTop;
      }
    })();
    
    function fixedBody(){
      var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
      document.body.style.cssText += 'position:fixed;top:-'+scrollTop+'px;';
    }
    
    function looseBody() {
      var body = document.body;
      body.style.position = '';
      var top = body.style.top;
      document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top);
      body.style.top = '';
    }
    

不过这样依旧会有点问题,就是如果在遮罩层中滚动的时候,做点击操作,会容易出现卡死的现象,就是滚动后需要进行两次点击,再能进行对应的点击操作。对此的解决方案是,改click事件为touchstart事件。

使用webkit-overflow-scrolling:touch的坑

今天遇到的了一个问题就是内容滚动有时候会出现卡住的情况,尤其是当滚动条位于最顶端或者最底下的时候,继续下拉或者上拉会导致页面卡住,这样的情况颇为频繁,找了很久都没发现问题所在,所以在尝试的情况下注释掉了webkit-overflow-scrolling后,发现居然好了!!!居然好了!!!

然后就针对这个问题谷歌上找了下,发现其实也很很多:

  • 取消webkit-overflow-scrolling:touch,但是这样会导致滚动时手指离开就马上停住滚动,没有了惯性滚动

  • 让子内容的高度+1px,比如父容器使用了webkit-overflow-scrolling:touch,那么子内容的容器设置高度height:calc(100% + 1px)既可以解决。

SPA路由使用history模式下的微信分享bug

会出现的一个情况是,我已进入页面是可以分享的,当时如果这时候路由变化,比如replace或者push操作之后,那么分享就会出现问题,并不会如同配置那样分享出来。因为报了invaild signature的错误。

这主要还区分安卓和苹果,两者的情况不同。

出现这个的原因是:

history模式下,vue-router操作的是浏览器的历史记录,从而改变URL的变化。

而官方JSSDK文档中也有提到:

同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复

但是坑爹的是,android中出现的问题不多,因为每次URL变化后,微信记录的都是当前那个URL,而IOS则不然,IOS是微信只记录页面第一次加载进来的URL地址。

举个栗子:

一进入系统的地址是https://a.com,然后先后进入ab,也就是https://a.com/ahttps://a.com/b,然后停在b页面,在android中,微信记录的是https://a.com/b这个地址,而IOS中记录的却是https://a.com,也就是第一次进来的路由地址。

这个时候我们刷新页面,那么https://a.com/b就是IOS第一次进来的地址了,所以IOS微信中记录的地址也就变成了https://a.com/b了。

解决方式:

注册一个全局变量,比如可以在window下挂在一个wxSignURL的变量,当一进入页面的时候保存第一次记录的地址:window.wxSignURL = location.href

这段代码可以写在main.js里,也可以写在router.js的钩子函数中,不过要区别对待,如果是android,则使用每次跳转后的路由,如果是IOS则使用记录的wxSignURL

然后获取微信签名的时候,把这个URL传过去即可。

IOSandroid的判断使用浏览器的ua判断即可。根据资料,微信会在window下挂载一个__wxjs_is_wkwebview属性,如果是在IOS中,该值为true,也可以通过这个属性去判断。

上次更新: 12/19/2018, 5:10:43 PM