移动端开发的一些问题总结

基础概念:

1. 视口

viewport标签,是定义的虚拟的布局视口,指的是页面实际布局所占用的区域。通过 document.documentElement.clientWidth 来获取布局视口的宽度。
在这里插入图片描述

视觉视口,用户正在看到的网页的区域。用户可以通过缩放来查看网站的内容。如果用户缩小网站,我们看到的网站区域将变大,此时视觉视口也变大了。不管用户如何缩放,都不会影响到布局视口的宽度。通过访问 window.innerWidth 和 window.innerHeight 两个属性,我们可以获取到视觉视口的宽高。
在这里插入图片描述
理想视口
布局视口的一个理想尺寸,只有当布局视口的尺寸等于设备屏幕的尺寸时,才是理想视口。js获取理想视口:window.screen.width;
要得到ideal viewport就必须把默认的layout viewport的宽度设为移动设备的屏幕宽度。因为meta viewport中的width能控制layout viewport的宽度,所以我们只需要把width设为width-device这个特殊的值就行了。用下面的方法可以使布局视口与理想视口的宽度一致:这是响应式设计的基础

<meta name="viewport" content="width=device-width">

移动端一般设置:

<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>

这句代码的作用是将移动设备的布局视口与理想视口的宽度一致,同时初始缩放值为 1 最大缩放值为1,并且不允许用户进行缩放。
当前缩放值 = ideal viewport宽度 / visual viewport宽度

2. 物理像素(设备像素) & 逻辑像素(CSS像素)

物理像素(屏幕分辨率):物理像素,又称 设备像素,在同一个设备上,他的物理像素是固定的。
逻辑像素(设备独立像素):又称css像素,viewport中的一个小方格。我们在写css代码的时候,用的就是css像素。
设备像素比:物理像素和逻辑像素的比例。在程序中则可以通过 window.devicePixelRatio 来获取,window.devicePixelRatio = 设备的物理像素 / CSS像素。当比例为 1:1 时,表示1个物理像素显示一个css像素;当比例为 2:1 时,表示使用4个物理像素显示1个逻辑像素。

逻辑像素和物理像素差异:(1px逻辑像素 != 1px物理像素)

UI设计师给我们的UI稿上,字号动不动就是36px,甚至是60px;这要真是写到代码中,页面直接爆炸,UI稿上的36px,60px,其实是物理像素,是相对于设备分辨率下的字号,而我们要在css中填写的字号,是css像素,是相对于逻辑像素下的字号,不是同一个概念。

布局问题:

响应式布局解决方案

能够使一张页面适配多种屏幕的布局方案,就是所谓的“响应式布局方案”。
具体来说,响应式布局主要解决的是屏幕大小不确定的问题。

1.媒体查询
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>媒体查询示例</title>
  <style>
    #left,
    #right {
      height: 300px;
      float: left;
    }
    #right {
      width: 150px;
      background-color: red;
    }
    #left {
      width: 200px;
      background-color: yellow;
    }
    @media screen and (max-width: 320px) {
    #right {
    display: none;
    }
    }

    @media screen and (min-width: 768px) {
    #right {
    width: 300px;
    }
    }
  </style>
</head>
<body>
  <div id="container">
    <div id="left">
      我是两列布局的左侧
    </div>
    <div id="right">
      我是两列布局的右侧
    </div>
  </div>
</body>
</html>

两列布局的右侧,在屏幕视口小于等于320px的时候不显示,在大于等于768px的时候为300px,在320——768px中间为150px。
媒体查询:页面根据不同的宽度节点,能够展示出三种不同的元素样式。

2. rem

设置根元素字体大小为视图容器宽度的十分之一,然后使用rem进行布局,因为1rem = 根元素字体大小px,所以可以实现等比缩放。
rem 是一个以根元素 font-size 大小为基准的相对单位。默认1rem = 16px
以 rem 作为布局单位,那么只要根元素大小发生了改变,就有“牵一发而动全身”的效果,整个页面中所有相关元素的大小都会跟着进行相应的放缩。非常经典的轮子——flexible.js。在这个示例中,我们将 rem 固定为视图容器宽度的十分之一。之后不管视图宽度如何变化,1rem 始终都是视图宽度的 1/10。此时使用 rem 来进行布局,就可以实现等比缩放。
在声明了DOCTYPE的浏览器中,可以用以下来获取浏览器显示窗口大小:

document.documentElement.clientWidth
document.documentElement.clientHeight

// 动态为根元素设置字体大小
function init() {
    // 获取视图容器宽度
    const docWidth = document.documentElement.clientWidth
    // 设置根元素字体大小(也是rem的大小)。1rem此时为宽的10等分
    document.documentElement.style.fontSize =docWidth  / 10 + 'px'
}
//首次加载应用,设置一次
init()
// 监听浏览器窗口大小的变化
window.addEventListener('resize', init);
3. vw/vh

vw 和 vh 是一种区别与 rem 和 px 的 css 尺寸单位。它们天生自带等比缩放能力:、

  • vw:1vw = 视觉视口宽度 / 100
  • vh:1vh = 视觉视口高度 / 100

使用vw和vh单位来布局,不管窗口大小如何变化,元素的宽高都维持在网页视口宽高的 1/10 水平。
视觉视口宽度:window.innerWidth

4. 利用UI框架实现响应式布局(栅格系统和flex布局)

其实,现在的主流UI框架都会考虑到响应式布局这个问题,比如elementUI,iview等框架提供了栅格系统,搭配来实现响应式布局。 所以在工作中直接拿来使用就完事了。

移动端适配问题解决方案

flexible.js是解决移动端适配问题的成熟方案:
注意:

  • 将 viewport 宽度设置为理想视口宽度;
  • 在样式中字体使用 px 单位,而其它元素使用 rem 单位;
  • 使-用 sass 中的 function 来设置一个 px 与 rem 之间的转换函数;
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
//750px宽度的屏幕设置除以100
@function pxToRem($num) {
  @return ($num/100) * 1rem;
}

当设计稿中有一个宽高都为 100px 的元素时,我们便可以如下写样式

div{
  width:pxToRem(100);
  height:pxToRem(100);
}

flexible.js:
原理:通过获取设备像素比dpr进行运算,设置页面里name=viewport的meta标签(包括内部的缩放比例),再在页面根元素html上添加data-dpr属性以及值,并且根据当前设备的width给网页中html根节点设置不同的font-size,来进行页面适配。

 ;(function(win, lib) {
    var doc = win.document;   // doc取文档的document对象
    var docEl = doc.documentElement;  //docEl取到了我们html为根的整个dom树
    var metaEl = doc.querySelector('meta[name="viewport"]');  // 获取名为viewport的meta标签
    var flexibleEl = doc.querySelector('meta[name="flexible"]'); // 获取名为flexible的meta标签
    var dpr = 0; // dpr (设备像素比)初始化置为0
    var scale = 0; // scale (缩放比例)
    var tid;
    var flexible = lib.flexible || (lib.flexible = {});
    

//这段代码是判断你的meta标签里面是不是设置了name=viewport属性.
// 如果你设置了viewport并且设置了initial-scale(初始屏幕的大小),
// 我们将取到这个值作为dpr(做了逻辑运算,如果你的页面初始的放大为二,那么我们的dpr会设置成0)
// 同理我们如果动态设置了meta我们直接就取出来然后设置dpr和scale
    if (metaEl) { 
        var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);  
        if (match) {
            scale = parseFloat(match[1]); // 获得了页面的初始缩放比例
            dpr = parseInt(1 / scale); // 得到设备像素比
        }
    } else if (flexibleEl) { // 
        var content = flexibleEl.getAttribute('content');
        if (content) {
            var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
            var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
            if (initialDpr) {
                dpr = parseFloat(initialDpr[1]);
                scale = parseFloat((1 / dpr).toFixed(2));    
            }
            if (maximumDpr) {
                dpr = parseFloat(maximumDpr[1]);
                scale = parseFloat((1 / dpr).toFixed(2));    
            }
        }
    }



 // 之后如果我们动态设置了scale或者设置了meta标签里面的name=flexible的inital-scale,那么我们就根据自己设置的dpr在判断iphone手机的retina屏幕的dpr比值判断不同型号的倍数,最后我们在html上设置了data-dpr自定义属性。
    if (!dpr && !scale) { // 当上面条件都不满足时
        var isAndroid = win.navigator.appVersion.match(/android/gi); // 安卓机
        var isIPhone = win.navigator.appVersion.match(/iphone/gi); // IOS机
        var devicePixelRatio = win.devicePixelRatio; // 获取window对象的 devicePixelRatio属性值,这个属性值就是我们所说的设备像素比,简称dpr
        if (isIPhone) {
            // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
            if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {                
                dpr = 3; // 
            } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
                dpr = 2;
            } else {
                dpr = 1;
            }
        } else {
            // 其他设备下,仍旧使用1倍的方案
            dpr = 1;
        }
        scale = 1 / dpr;
    }
    docEl.setAttribute('data-dpr', dpr); // 给页面根元素设置自定义属性data-dpr,值为前面已经赋值好的dpr
   




 // 之后当我们之前没有设置metaEl标签的话,那么需要我们手动的去创建meta标签,实现移动端的适配
    if (!metaEl) { // 当name=viewport的mate标签不存在时,就给页面添加一个,各元素值为前面计算好的scale,并不允许用户拖动缩放
        metaEl = doc.createElement('meta');
        metaEl.setAttribute('name', 'viewport');
        metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
        if (docEl.firstElementChild) {
            docEl.firstElementChild.appendChild(metaEl);
        } else {
            var wrap = doc.createElement('div');
            wrap.appendChild(metaEl);
            doc.write(wrap.innerHTML);
        }
    }
   

 // 这段代码的目的就是监听window里面的resize和pageshow方法来实现css样式的重绘。

// 函数里面就是实现取到当前设备的width之后根据width计算出rem的具体值,rem代表html的font-size,这里的rem代表的是一个自定义的rem,而不是rem属性!
    function refreshRem(){
        var width = docEl.getBoundingClientRect().width;
        if (width / dpr > 540) { // 对于逻辑像素大于540的设备,其宽度就设置为设备像素比乘以540
            width = 540 * dpr;
        }
        var rem = width / 10; // 将屏幕宽度分成10份,每一份为1rem 所以整个屏幕的完整宽度为10rem
        docEl.style.fontSize = rem + 'px'; // 设置根元素字体大小为计算所得的值
        flexible.rem = win.rem = rem;
    }
    win.addEventListener('resize', function() {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
    }, false);
    win.addEventListener('pageshow', function(e) {
        if (e.persisted) {
            clearTimeout(tid);
            tid = setTimeout(refreshRem, 300);
        }
    }, false);
  



// 之后我们判断document对象是否处于complete状态,如果完成状态我们给body一个font-size=12*dpr的值,否则我们判断dom加载方法来实现body中的font-size的设置。这个设置是为了页面中字体的大小,而html中的font-size是为了设置页面的height,width等属性。 
    if (doc.readyState === 'complete') {
        doc.body.style.fontSize = 12 * dpr + 'px';
    } else {
        doc.addEventListener('DOMContentLoaded', function(e) {
            doc.body.style.fontSize = 12 * dpr + 'px';
        }, false);
    }
    refreshRem();



  // 后面这段代码是将rem单位值转换成px的和将px单位的值换算成rem单位的值
    flexible.dpr = win.dpr = dpr;
    flexible.refreshRem = refreshRem;
    flexible.rem2px = function(d) {
        var val = parseFloat(d) * this.rem;
        if (typeof d === 'string' && d.match(/rem$/)) {
            val += 'px';
        }
        return val;
    }
    flexible.px2rem = function(d) {
        var val = parseFloat(d) / this.rem;
        if (typeof d === 'string' && d.match(/px$/)) {
            val += 'rem';
        }
        return val;
    }

})(window, window['lib'] || (window['lib'] = {}));

1. 移动端的1px边框问题

在一些Retina屏幕的机型上,CSS里面写了1px,但是页面上实际看起来比1px粗?
  因为移动设备上的1px和css中的1px不能划等号。移动端页面上的1px是物理像素,css中设置的1px是逻辑像素,它们之间的比例关系有一个专门的属性来描述:window.devicePixelRatio (设备像素比)= 设备的物理像素 / CSS像素。比如说iPhone的devicePixelRatio==2,css里写的1px长度映射到物理像素上就有2px那么粗。

解决办法
1.直接写0.5px

直接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简单的一种方法。这种方法的缺陷在于兼容性不行,IOS 系统需要8及以上的版本,安卓系统则直接不兼容。

#container[data-device="2"] {
  border:0.5px solid #333
}

<div id="container" data-device={{window.devicePixelRatio}}></div>
2. 伪元素+transform

原理:把原先元素的border去掉,然后利用::before或者::after重做1px
border,将原先的元素相对定位,新做的border绝对定位, 并transform的scaleY缩小设备像素比分之一。

<ul class="hairlines">
	<li>1</li>
	<li>2</li>
</ul>

*  {
	margin: 0;
	padding: 0;
}
ul, li {
	list-style: none;
}
.hairlines {
	width: 300px;
	margin: 100px auto;
}
.hairlines li {
	position: relative;
	border: none;
	margin-top: 10px;
}
.hairlines li::after {
	content: '';
	position: absolute;
	left: 0;
	bottom: 0;
	background: #cccccc;
	width: 100%;
	heigth: 1px;
}
@media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio:1.5)
 .hairlines 
 li::after
   -webkit-transform:scaleY(0.7)
   transform:scaleY(0.7);  
   transform-origin: 0 0;
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio:2)
 .hairlines 
 li::after
   -webkit-transform:scaleY(0.5)
   transform:scaleY(0.5); 
   transform-origin: 0 0;
3. 通过viewport缩放,同时设置rem基准值。

在运行的时候拿到设备的devicePixelRatio,动态改变viewport的initial-scale为 1/devicePixelRatio,这样就能保证1px的宽度就是真正的1物理像素宽。然后根据视图容器宽度设置rem基准值,其他元素的宽度高度适配使用rem方案(因为使用px的话都会被缩小)
这个解决方案是利用viewport+rem+js 实现的。这种方法就是flexble.js的解决方法。

<html>
  <head>
      <title>1px question</title>
      <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
      <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">        
      <style>
          html {
              font-size: 1px;
          }            
          * {
              padding: 0;
              margin: 0;
          }
          .top_b {
              border-bottom: 1px solid #E5E5E5;
          }
 
          .a,.b {
              box-sizing: border-box;
              margin-top: 1rem;
              padding: 1rem;                
              font-size: 1.4rem;
          }
 
          .a {
              width: 100%;
          }
 
          .b {
              background: #f5f5f5;
              width: 100%;
          }
      </style>
      <script>
          var viewport = document.querySelector("meta[name=viewport]");
          //下面是根据设备像素设置viewport
          if (window.devicePixelRatio == 1) {
              viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
          }
          if (window.devicePixelRatio == 2) {
           // 这里针对像素比为2的页面,把整个页面缩放为了原来的1/2大小。如此一来,本来占用2个物理像素的 1px 样式,现在占用的就是标准的一个物理像素。1px正常了,但是整个页面缩小了
              viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
          }
          if (window.devicePixelRatio == 3) {
              viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
          }
          var docEl = document.documentElement;
          docEl.style.fontSize =  docEl.clientWidth / 10  + 'px';
      </script>
  </head>
  <body>
      <div class="top_b a">下面的底边宽度是虚拟1像素的</div>
      <div class="b">上面的边框宽度是虚拟1像素的</div>
  </body>
</html>

2. 移动端300ms点击延迟的问题

在某些机型,某些浏览器上,click事件会延迟300ms再执行。那么为什么会有这个300ms的点击延迟呢?
移动端的双击缩放是会有上述 300 毫秒延迟的主要原因。双击缩放,即用手指在屏幕上快速点击两次,移动端浏览器会将网页缩放至原始比例。
由于用户可以进行双击缩放或者单击跳转的操作,当用户一次点击屏幕之后,浏览器并不能立刻判断用户是确实要打开这个链接,还是想要进行双击操作。因此,网页就等待 300 毫秒,以判断用户是否再次点击了屏幕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值