移动端适配方案(下) #18

Open
riskers opened this Issue Jan 24, 2016 · 48 comments

Comments

Owner

riskers commented Jan 24, 2016

上一篇介绍了像素和视口这些基本概念,现在接着移动端的适配方案。

推荐一篇文章:MobileWeb适配总结,里面说到的三种布局方法已经说的很详细,还分别做了demo,我就不做了,这里说说三种方案的原理以及我使用中的感受,希望各为互补,大家理解是最重要的。

之前做过PC页面的人聊的最多的就是『兼容』,这是因为浏览器之间的差异引起的,不再多说。而移动端是基本没有『兼容』的问题的,全是CSS3,简直不要太开心。可是『适配』问题随之而来。

什么是『适配』?做PC页面的时候,我们按照设计图的尺寸来就好,这个侧边栏200px,那个按钮50px的。可是,当我们开始做移动端页面的时候,设计师给了一份宽度为640px的设计图。那么,我们把这份设计图实现在各个手机上的过程就是『适配』。

那么,我们怎么开始呢?目前有三种方法:

  • 固定高度,宽度自适应
  • 固定宽度,viewport缩放
  • rem做宽度,viewport缩放

这三种方法的核心都是视口的确定,现在以实现这个设计图为例说明。

app-demo

固定高度,宽度自适应

demo

这也是目前使用最多的方法,垂直方向用定值,水平方向用百分比、定值、flex都行。腾讯京东百度天猫亚马逊的首页都是使用的这种方法。

随着屏幕宽度变化,页面也会跟着变化,效果就和PC页面的流体布局差不多,在哪个宽度需要调整的时候使用_响应式布局_调调就行(比如网易新闻),这样就实现了『适配』。

原理

这种方法使用了完美视口:

<meta name="viewport" content="width=device-width,initial-scale=1">

这样设置之后,我们就可以不用管手机屏幕的尺寸进行开发了。

固定宽度,viewport缩放

demo

设计图、页面宽度、viewport width使用一个宽度,浏览器帮我们完成缩放。单位使用px即可。

目前已知荔枝FM网易新闻在使用这种方法。有兴趣的同学可以看看是怎么做的。

原理

这种方法需要根据屏幕宽度来动态生成viewport,生成的 viewport 基本是这样:

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

640 是我们根据设计图定下的,0.5 是根据屏幕宽度动态生成的。

生成的viewport告诉浏览器网页的布局视口使用 640px,然后把页面缩放成50%,这是绝对的等比例缩放。图片、文字等等所有元素都被缩放在手机屏幕中。

这个gif图说明了一切:

1

rem做宽度,viewport缩放

demo

这也是淘宝使用的方案,根据屏幕宽度设定 rem 值,需要适配的元素都使用 rem 为单位,不需要适配的元素还是使用 px 为单位。

具体使用方法见使用Flexible实现手淘H5页面的终端适配

上文提供了sass和postcss的px2rem转换方法,我这里给出less的px2rem。因为less不支持函数,所以需要安装插件 less-plugin-functions ,然后就简单了:

.function{
    .px2rem(@px,@base:72px){
        return: @px / @base * 1rem;
    }    
}

这样使用:

div{
    width: px2rem(100px);
}

使用这个方法的库:

原理

实际上做了这几件事情:

  1. 动态生成 viewport
  2. 屏幕宽度设置 rem的大小,即给<html>设置font-size
  3. 根据设备像素比(window.devicePixelRatio)给<html>设置data-dpr

rem

这么设置的好处是可以让不同设备的rempx都显示一样的长度。

设置rem

设置rem的意义在于得到一个与屏幕宽度相关的单位,本来vw是最合适的,但是因为兼容性的问题,只能使用rem来做。这样,让不同设备的rem显示一样的长度

vw是CSS3引入的单位,1vw = 1%窗口宽度

rem

上面的布局我们可以这样:

html{
    font-size: 屏幕宽度 / 10; 
}
.btn{
    width:8.75rem;
    height:1.25rem;
}

这样,无论屏幕宽度是多少,.btn都是相对于屏幕的这么宽,做到了适配。

设置 viewport 缩放 和 data-dpr

这两个设置其实是干的一件事,就是适配高密度屏幕手机的px单位。

.a{
  font-size:12px;
}
[data-dpr="2"] .a{
  font-size: 24px;
}
[data-dpr="3"] .a{
  font-size: 36px;
}

而缩放是动态的,这样,不同设备下的px显示一样的长度

之前说过CSS像素和物理像素与缩放、dpr都有关系,这里说明:

在普通手机上,.a字体设置为12px;

在dpr是2的手机上,[data-dpr="2"] .a字体为24px,又因为页面缩放50%,字体为还是12px

总结

坦白说,我不觉得第一种方案能就做『适配』方案,因为太笨了,只能做一些列表等简单排列的样式。

demo

但是一些复杂的活动页的布局,用它可能就力不从心了:

PK活动

这是我曾经做过的一个页面,『PK』要和左右两张图平行,而且下面的『不怒自威』、『义薄云天』和下面的战斗力位置都要固定,不能有差。如果用第一种方案,可能各个元素就要绝对定位,然后各种百分比来定位了。且不说计算麻烦,而且辛苦一番最后的结果尺寸是和设计图有出入的。

但是,第二种和第三种方案就绝对不会有这种情况,不会和设计图有差。再说第二种和第三种方案的区别,目前我唯一知道的区别就是第三种方案更加灵活,有两种单位可以使用,想让元素适配的时候就用rem,想让文字不缩放的时候就用px

如果你没有明白我以上讲的,可能是我太啰嗦了,这是我在团队内分享时做的PPT,应该没那么啰嗦了。当然你也可以看看下面的那些文章,不过最好的理解方式就是用这些方案做几个页面,到时候就明白了。

其他文章

这两篇文章看着简单,尤其是上一篇讲视口,花费了将近一周的时间去翻阅资料。本来以为很简单的东西,才发现有那么多名堂。

20170821 update

vw方案: https://www.w3cplus.com/css/vw-for-layout.html

demo: http://huodong.m.taobao.com/act/layouttestvw.html


向我捐助 | 关于我 | 工作机会


@yutingzhao1991 yutingzhao1991 referenced this issue in yutingzhao1991/github-blogs-daily Jan 25, 2016

Open

文章更新 [ 2016-01-24 ] #16

写的很好,很有用,谢谢~

发现一处似乎有问题的句子。

设置rem.. 但是应该兼容性的问题,只能..

Owner

riskers commented Jan 26, 2016

@think2011 已修改,谢谢

kisnows commented Jan 27, 2016

发现第二个方法好方便,只要计算一下viewpoint就可以了,我以后就用这个方法了

banama commented Jan 30, 2016

写的很不错,以前常用第一第三种,今天第一次看到第二种,耳目一新啊,学习了。

@yutingzhao1991 yutingzhao1991 referenced this issue in yutingzhao1991/github-blogs-weekly Jan 31, 2016

Open

文章更新 [ 2016-01-24 - 2016-01-30 ] #24

kisnows commented Feb 1, 2016

发现第二个方法还是有坑啊,比如你很难实现一个整页的 fullpage 应用。
因为当页面大小不同时,scale 是按照屏幕宽度计算的,这样在只要宽高比不同的设备下不借助 js 还真是难以实现。

Owner

riskers commented Feb 1, 2016

@kisnows 你说的整屏应用我其实一直在思考,似乎没什么好的办法,因为一旦整屏,屏幕中的元素为了保持设计图的比例就要像 background-sizecover / contain 一样,必然在屏幕中有『黑边』。

上面的三种方法都是在与屏幕宽度有关系,整屏页面是与宽高比有关系,其实挺复杂的。而且这种页面也不适合复杂设计,最好就是每个元素绝对定位来做比较好。

可以参考 #15

SASUKE40 commented Feb 1, 2016

@kisnows fullpage應用不建議用這種方式
@riskers 其實荔枝FM的很多頁面都是使用第二種,因為這樣跟設計師非常好交流。另外推薦PPT很贊,末尾還有切圖工具推薦。其實那幾個切圖工具我現在基本不用了,推薦使用Photoshop自帶的【文件-生成-图像资源】切圖,再配合Renamy就非常幸福了~

Owner

riskers commented Feb 1, 2016

@SASUKE40
那个应该是PS CC 的功能吧,我现在还是CS6,因为懒并不想升级。

荔枝FM的办法的确不错,不过之前使用你们的兼容办法在有些手机的webview中有问题。还没有研究是什么造成的,你们的办法在安卓4.3的手机默认浏览器中都正常么?之前测试拿来一个魅蓝的手机在默认浏览器中就有问题。

leozdgao commented Feb 1, 2016

@SASUKE40 @riskers

看了下荔枝FM的首页,发现它并没有做 viewport 缩放,只是固定了布局视口的大小,和第二种方式的描述似乎不太一样。在 chrome 的模拟器中还出现了 font boosting 的现象。

<meta name="viewport" content="width=640, user-scalable=no, target-densitydpi=device-dpi">

我的理解是:

  • 缩放 viewport 的目的主要是解决1像素问题,淘宝的 lib-flexible 中是直接把安卓设备全部当作 dpr=1 来处理(也就是 initial-scale=1.0),因为用了根元素字体是根据布局视口大小动态设置的,使用了 rem 作单位的元素总能适配。
  • 单独使用第二种方法的话,只用 px 感觉没有用 rem 的方法灵活。

另外最近遇到单页面适配的问题,感觉 #15 讲的很有帮助😄

Owner

riskers commented Feb 1, 2016

@leozdgao

荔枝FM是第二种方法,不过它做了兼容(你的模拟器应该是用的iOS吧):

为什么这么兼容我不太清楚(无非也就是安卓的各个版本、iOS对viewport的支持不同吧),具体要问问 @SASUKE40 了。你可以看看网易那个页面,也做了兼容,不过方法复杂的多。

第三种方法相当于有两种单位让你使用,是比第二种灵活。

leozdgao commented Feb 1, 2016

@riskers

额... 🤔 #15 在我的场景,用 contain 模式还是比较适合的。

另外在模拟器上有 Font Boosting,在真机上打开又是正常的,很奇怪。

Owner

riskers commented Feb 1, 2016

@leozdgao 是么,我在 #15 的那个案例也是这样,contain比较合适。

Font Boosting的问题,你用 max-height:100% 试试?

leozdgao commented Feb 1, 2016

@riskers

Font Boosting 是在看荔枝FM主页的时候发现的,在模拟器上看有(用 max-height 验证的),真机上打开就没有了... 这个先不管了...

从网易压缩了的代码里挖出来了 viewport 的逻辑:

var b = window.innerWidth || a,
  c = window.outerHeight || b,  // 这句可能是它们写错了
  d = window.screen.width || b,
  e = window.screen.availWidth || b,
  g = window.innerHeight || a,
  h = window.outerHeight || g,
  i = window.screen.height || g,
  j = window.screen.availHeight || g,
  k = Math.min(b, c, d, e, g, h, i, j),
  l = k / a,
  m = window.devicePixelRatio,
  l = Math.min(l, m),
  n = navigator.userAgent.match(/android/gi),
  o = navigator.userAgent.match(/iphone|ipod|ipad/gi);

感觉是在取视觉视口的大小,然后有高有宽应该是为了应对横竖屏,不过只在安卓和 IOS 7 以下有效,这个不清楚是为什么(高版本就忽略横屏的适配了)。

自己现在在学习了 lib-flexible 后,主要使用第三种方法。

Owner

riskers commented Feb 1, 2016

@leozdgao
恩,我现在也在使用第三种方法,感觉很方便。

MaskWu commented Feb 18, 2016

关于对 设置 viewport 缩放 和 data-dpr的解释中,以下部分不是十分的理解,看了几遍,都没有明白

在普通手机上,.a字体设置为12px;

在dpr是2的手机上,[data-dpr="2"] .a字体为24px,又因为页面缩放50%,字体为还是12px

既然最后字体还是12px为什么要进行先将字体设置为24px再然后进行缩放的操作呢,还望博主解答

Owner

riskers commented Feb 18, 2016

@MaskWu
因为如果统一设置为 12px 的话 ,在高分屏上实际只有6px 。可以看一下上篇中关于css像素和物理像素的概念。这里的px是css像素

好文,终于找到完成的解决方案了

Owner

riskers commented Mar 7, 2016

@fengchuantao 谢谢支持

killsos commented Mar 17, 2016

恩 这又是一个不错面试题

2014年毕业就这么厉害了

Owner

riskers commented Apr 7, 2016

@kevinkaili 过奖,还在学习阶段

好文,支持下。

第二种方案是对腾讯的QQ浏览器没有用的。
我一开始就想到动态改变viewport的方法来自适应页面,结果怎么调都发现QQ浏览器没有用。
直接发现这篇文章,看到荔枝FM,在QQ浏览器上打开一看,果然乱了。
看来QQ浏览器真是不支持viewport啊。

Owner

riskers commented Apr 8, 2016

@sharkrice QQ浏览器就是移动端的IE

SASUKE40 commented Apr 9, 2016

@sharkrice 刚刚试了一下,没发现哪里乱了,你能截个图给我吗

imruxin commented Apr 26, 2016

额,随着使用后发现,有部分安卓机默认对 viewport 缩放眼瞎呀,比如公司的测试机HTC One X,好崩溃的~ 各位有什么更好的解决方案么,谢谢~

@SASUKE40 最近,我发现只有我的手机上的QQ浏览器才乱了,结果查出原因是因为我开了那个省流量加速的功能。。。。无言了。

kisnows commented Apr 26, 2016

@leozdgao 网易这个方法还是可以用的,可以支持 IOS 所有的版本,而不是 IOS 7 一下。里面判断出来是 IOS 以后没有去改 viewport 这点我还太明白,但是确实是可以正常工作的。
下面是我挖出来稍微改造一下的代码。

(function (window) {
    var viewport = document.documentElement.querySelector('meta[name="viewport"]');
    // 这里的 initWidth 由自己设置,网易是固定的 640 。
    var initWidth = viewport.content.match(/width=([\d]{3})/);
    var a = +initWidth[1];
    var b = window.innerWidth || a
      , c = window.outerHeight || b
      , d = window.screen.width || b
      , e = window.screen.availWidth || b
      , g = window.innerHeight || a
      , h = window.outerHeight || g
      , i = window.screen.height || g
      , j = window.screen.availHeight || g
      , k = Math.min(b, c, d, e, g, h, i, j)
      , L = k / a
      , m = window.devicePixelRatio
      , L = Math.min(L, m);
    var device = window.navigator.userAgent;
    if (device.match(/Android/gi)) {
      window.device = 'android';
      document.body.className += ' android';
      viewport.setAttribute('content',
        'width=' + a + ' initial-scale=' + L + ' maximum-scale=' + L + ' user-scalable=no'
      )
    } else if (device.match(/iPhone|iPad/gi)) {
      window.device = 'ios';
      viewport.setAttribute('content',
        'width=' + a + ' user-scalable=no'
      ) 
    }
    // 判断微信
    if (device.match(/MicroMessenger/gi)){
      document.body.className += ' weixin';
    }
  })(window)

imruxin commented Apr 26, 2016

@kisnows 你的这段代码还会有个错误提示:Uncaught TypeError: Cannot read property '1' of null form var a = +initWidth[1];

Owner

riskers commented Apr 26, 2016

@imruxin 淘宝的做法是只管iOS,安卓一律把dpr设为1,官方的说法是安卓的厂商会自己修改dpr,导致安卓上的 window. devicePixelRatio 是假的。

weaming commented Apr 30, 2016

句子上有的少个把字眼,有些必要的推理条件没有给出。还是没看懂(逃

moodpo commented May 6, 2016

mark

mingelz commented Jun 21, 2016

赞这个总结

KeyX-y commented Jul 1, 2016

第二种方法 用js动态控制缩放的比例 感觉棒棒的
var scale;
var docEl = window.screen.width;;
var metaEl = document.querySelector('meta');
var dobe=docEl/1080;
dobe=dobe.toFixed(2);
scale = 1.0 ;
metaEl.setAttribute('name','viewport');
metaEl.setAttribute('content', 'width=device-width ,initial-scale=' + dobe + ',maximum-scale=' + scale + ', minimum-scale=0 ,user-scalable=no');

看完这两篇,再看看mobile_util里面的代码,瞬间就通了,非常非常的牛B

huboshu0819 commented Aug 3, 2016

有一个疑问,关于第三个方案的字体问题,如果我现在要设置字体像素为在dpr为1时14px,就得这样

.f14{font-size: 14px}
[data-dpr="2"] .f14{font-size: 28px}
[data-dpr="3"] .f14{font-size: 42px} 

但是如果页面有多种字体大小,是不是就得写多个类似样式才能解决,还有其他比较好的方法吗?

Owner

riskers commented Aug 3, 2016

@huboshu0819 这个没办法

使用 less 或者 sass 可以简化你的操作

.font(@font-size){
    font-size: @font-size;
    [data-dpr="2"] & {
        font-size: @font-size;
    }
    [data-dpr="3"] & {
        font-size: @font-size * 3;
    }
}

然后可以这样使用 :

.xxx{
    .font(14px);
}
.yyy{
    .font(14px)
}

我现在这样可以避免生成冗余css:

.f14{.font(14px)}
.f16{.font(16px)}
.f18(.font(18px))
<div class="f14">f14</div>
<div class="f16">f16</div>
<div class="f18">f18</div>

至于为什么上面那样会生成冗余css,你生成css看看就知道了 😄
这就是大概的思路……

@riskers 知道了, 谢啦

This is good!

非常谢谢前辈的文章!谢谢!

@547377507 547377507 referenced this issue in 547377507/blog Oct 3, 2016

Open

搬运-文章集合 #1

非常感谢分享,有问题的地方也通过检查和比对解决了,终于完全理解了楼主分享的三种方法了,O(∩_∩)O

huoruji commented Mar 24, 2017

用flexble大家有没有遇到过多列带边框的div,边框设置的都是1px,在安卓手机上显示粗细不一样的情况

@BUPT-HJM BUPT-HJM referenced this issue in BUPT-HJM/vue-blog Jun 29, 2017

Closed

楼主请教你PC和移动端自适应问题! #4

HPJR commented Jul 5, 2017

第二种最近遇到点问题的,当你的网页被dns广告劫持,广告会把你的网站用一个iframe包裹,hede部分重写,使用<meta name="viewport" content="width=device-width,initial-scale=1">,网站是按640写的,这时候被设置成width=device-width,整个页面就错乱了。请问有什么办法解决吗?

nicklao commented Jul 5, 2017

@HPJR 被劫持的解决根本解决就是 https。解决劫持问题,你的这个问题就不是问题了!

HPJR commented Jul 5, 2017

@nicklao 一开始是准备用https解决的,不过看了百度的https一样被劫持,所以最后重写了。不知道https为什么也能被劫持(广告是wifi广告),挺诡异的

nicklao commented Jul 5, 2017

@HPJR 你说看了百度的https一样被劫持很大可能是因为机器上安装了其他不知名的根证书导致的。

frehaiku commented Jul 8, 2017

第一种方案与第三种方案的区别,看的不太懂,麻烦指点一下:

  1. 第一种单纯设置html的font-size来使用rem单位做适配,看起来更方便,换算一下font-size可以做出设计稿量出来100px对应1rem的长度。在不用做适配的地方也可以用px
  2. 第三种用dpr控制viewport的scale,结合rem单位。一样也是要换算。而且增大了换算的难度,实在不懂跟第一种的适配有什么区别
  3. 那个PK的设计稿的例子,

如果用第一种方案,可能各个元素就要绝对定位,然后各种百分比来定位了。且不说计算麻烦,而且辛苦一番最后的结果尺寸是和设计图有出入的。但是,第二种和第三种方案就绝对不会有这种情况,不会和设计图有差。

我觉得第三种做这个布局也是要绝对定位的呀?也是要量设计稿的长度来做,做法一致的吧?

@numberfan numberfan referenced this issue in numberfan/readNotes Sep 6, 2017

Open

webapp+pc #3

less-plugin-functions 这个插件如何在vue-cli构建的项目中引入呢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment