Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

移动端布局方案 #86

Closed
PolluxLee opened this issue Aug 16, 2018 · 0 comments
Closed

移动端布局方案 #86

PolluxLee opened this issue Aug 16, 2018 · 0 comments
Labels

Comments

@PolluxLee
Copy link
Owner

PolluxLee commented Aug 16, 2018

# 前置知识点

像素

CSS 像素(CSS Pixel):
又称为虚拟像素、设备独立像素或逻辑像素,也可以理解为直觉像素。CSS 像素是 Web 编程的概念,指的是 CSS 样式代码中使用的逻辑像素。比如 iPhone 6 的 CSS 像素数为 375 x 667px

设备像素(Device Pixels):
又称为物理像素。指设备能控制显示的最小物理单位,意指显示器上一个个的点。从屏幕在工厂生产出的那天起,它上面设备像素点就固定不变了。比如 iPhone 6 的分辨率为 750 x 1334px

设备像素比(DevicePixelRatio):
DPR = 设备像素 / CSS 像素
* 这里的 CSS 像素其实是理想视口

例如,iPhone 6 物理像素为 750 x 1334,理想视口 375 x 667 ,DPR = 2

视网膜 Retina 显示屏

Retina显示屏英文Retina Display)是一种由苹果公司设计和委托制造的显示屏,具备足够高像素密度而使得人体肉眼无法分辨其中单独像素点的液晶屏 ---- 维基百科

比如 iphone 6 设备像素 750 x 1334 ,理想视口 375 x 667,如下图,在宽度或高度方向上,1 个 CSS 像素,普通显示屏用一个设备像素显示,而视网膜显示屏用两个设备像素显示,在一个平面上,1 个 CSS 像素,视网膜显示屏用 4 个设备像素显示,显然颗粒度更加精细

移动设备上的三个 viewport

Layout Viewport 布局视口

为了容纳为桌面浏览器设计的网站,移动设备默认的布局视口宽度远大于屏幕的宽度,设置为 980px 或 1024px(也可能是其它值,这个是由设备自己决定的)

document.documentElement.clientWidth 可获取布局视口宽度

Visual Viewport 视觉视口

浏览器可视区域的大小,缩放程度和视觉视口的大小是逆相关的,放得越大,视觉视口越小

以 iphone5 为例,浏览器布局视口的宽度默认是 1024px,屏幕宽度只有 640 个设备像素,DPR 为 2,所以 CSS 像素是 320px。现在用户从 100% 放大到 200%,CSS 像素被放大,直到屏幕上只有 160 个 CSS 像素。但是,布局视口仍然保持在1024px,所以页面中的元素并没有改变大小,但视觉视口宽度变成了 160 个 CSS 像素

window.innerWidth 可获取视觉视口宽度

Ideal Viewport 理想视口

理想视口是移动端的概念,对于每个设备,能给用户提供最好体验的布局视口的尺寸,显示在理想视口中的网站拥有最理想的浏览和阅读的宽度,用户刚进入页面时不需要缩放

只有主动地往页面里添加 meta 视口标签时理想视口才会生效。如果没有 meta 视口标签声明,那么布局视口将会维持它的默认宽度,理想视口只有当显式地使用它的时候才会产生影响

<!-- 这一行代码告诉浏览器,布局视口的宽度应该与理想视口的宽度一致 -->
<meta name="device" content="width=device-width">

screen.width 可获取理想视口的尺寸

meta name="viewport"

meta 标签有 6 个值

描述
width 设置布局视口的宽度为特定的值
initial-scale 设置页面的初始缩放程度和布局视口的宽度
minimum-scale 设置了最小缩放程度
maximum-scale 设置了最大缩放程度
user-scalable 是否阻止用户进行缩放
height 设置布局视口的高度(未被实现)

width

width 可设置固定值

<meta name="viewport" content="width=400">

也可设置 divice-width ,即布局视口宽度 = 理想视口宽度

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

initial-scale=1 也能达到 布局视口宽度 = 理想视口宽度 的效果,因为指定缩放为 1 时,就等同于不缩放

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

scale

当前缩放值 = 理想视口宽度 / 视觉视口宽度

例如,当理想视口宽度为 320px,初始缩放 initial-scale = 2,放大后的视觉视口宽度为 160px

# flexible.js 方案

flexible.js 是淘宝的 REM 可伸缩布局方案 点击下载

(1) 动态设置 根元素 html 的 font-size,等比缩放元素大小来自适应移动设备
(2) 获取设备 dpr,动态修改 meta,对视口缩放,进行高清显示

使用 rem 布局

首先,REM 是由根元素 html 标签的 font-size 决定的,它们是关系是
1rem = 1 * font-size2rem = 2 * font-size,...

设计师姐姐给的视觉稿一般是 640px 或者是 750px (这里指的是宽度),我们会选定一种尺寸的稿子作为参考去布局

假设我们选择 750px 去布局,第一件事情就是要设置根元素 htmlfont-size,那我们把 750px 分成 10 份,$rem = 750px / 10 = 75px,也就是,1rem = 75px10rem = 750px

但是我们视觉稿上的单位是 px,我们需要转成 rem,因为前面说了 1rem = 75px1px = 1/75rem,所以用视觉稿上的 px 值除以 75 就可以了,比如,100px = 100/75rem = 1.33333rem

为了方便运算,有些老司机会把 750 分成 7.5 份,即 $rem = 750px / 7.5 = 100px,所以,用视觉稿上的 px 直接除以 100 就好了!!!

也可使用 Sass 混合,就不需要自己去算了

/* 基准 font-size,可设置成其他值 */
$rem: 100;
@mixin px2rem($name, $px){
  #{$name}: $px / $rem * 1rem;
}

/* 使用示例:*/
.container {
  @include px2rem(height, 240);
}

/* Scss 编译后:*/
.container {
  height: 2.4rem;
}

Retina 高清显示

先来看看位图在 Retina 屏是怎样显示的

位图像素: 一个位图像素是栅格图像 (如:png, jpg, gif 等)最小的数据单元。每一个位图像素都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)

对于 dpr=2retina 屏幕而言,1 个位图像素对应于 4 个物理像素,由于单个位图像素不可以再进一步分割,所以只能就近取色,从而导致图片模糊(注意上述的几个颜色值)

为了使这张图高清显示,需要提供(@2x)两倍图,比如,200×300(css pixel) img 标签,就需要提供 400×600 的图片,使得位图像素跟物理像素 1:1 对应,图片就自然清晰了

但是对于普通屏幕,就没有必要加载(@2x)两倍图了,一个是因为会造成资源浪费,另一个是(如上图)会让图片失去了一些锐利度(或色差)

对于图片高清,最好的解决方案是,不同的 dpr,加载不同的图片

对于网页高清,如果我们要使我们的网页在 Retina 屏上高清显示,就要缩放视口,使得布局的像素与屏幕的物理像素 1:1 对应

下面是 flexible.jsdprscale 的设置:

  if (!dpr && !scale) {
    var isAndroid = win.navigator.appVersion.match(/android/gi);
    var isIPhone = win.navigator.appVersion.match(/iphone/gi);
    var devicePixelRatio = win.devicePixelRatio;
    if (isIPhone) {
        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;
  }

在引入 lib-flexible 需要执行的 JS 之前,可以手动设置meta来控制dpr值,如:

<meta name="flexible" content="initial-dpr=2" />

其中 initial-dpr 会把 dpr 强制设置为给定的值。如果手动设置了 dpr 之后,不管设备是多少的 dpr,都会强制认为其 dpr 是你设置的值。在此不建议手动强制设置 dpr,因为在 Flexible 中,只对 iOS 设备进行 dpr 的判断,对于 Android 系列,始终认为其 dpr1

代码中对 iOS 设备的 window.devicePixelRatio 进行判断,设置对应的整倍数 dpr,然后根据 dpr 设置 缩放比 scale,以 iphone 6 为例

布局视口 视觉视口 理想视口 基准 font-size
scale=1 375 375 375 50
scale=0.5 750 750 375 100

理想视口在特定机型和浏览器情况下,是不变的,我们 F12 打开 Chrome 开发者工具,看到的就是理想视口

在这里要先明白缩放的概念,缩放是视觉视口相对于理想视口来缩放的。当设置 initial-scale=1 后,即,不缩放,此时,视觉视口与理想视口重合,又因为 initial-scale=1 实现了 width=device-width 的效果,所以设置了缩放比的网页,布局视口就等于视觉视口(个人推断)

initial-scale = 1 的情况下,布局视口和理想视口重合,虽然 750 物理像素,但是 1 CSS 像素还是用 4 个设备像素表示,没有发挥 Retina 的作用,所以我们要让视觉视口增大一倍,到 750

因为 scale = 理想视口 / 视觉视口,在理想视口不变的情况下,视觉视口增大,则 scale 缩小,对于 dpr=2iphone 6,设置 initial-scale=0.5 的缩放比。所以可以看到源码中,dpr 是多少,意思就是视觉视口该被放大多少倍,就得出了 scale = 1 / dpr

设置基准 font-size

function refreshRem(){
  var width = docEl.getBoundingClientRect().width;
  var rem = width / 7.5;
  docEl.style.fontSize = rem + 'px';
  flexible.rem = win.rem = rem;
}

基准 font-size 是在设置了 dpr缩放 之后,根据 布局视口 等分后得出的,对于 iphone 6 来说,假如没有根据 dpr 做高清配置,也就是没有设置缩放比,font-size = 375 / 7.5 = 50,如果设置了缩放,就是 font-size = 750 / 7.5 = 100

Retina 的 border: 1px 问题

Retina 下的 1px 比普通屏下的 1px 看起来要粗,出现这种情况是因为 Retina 用相对于 CSS 像素两倍的物理像素点去显示 1px

所以,设计师姐姐想要的 retinaborder: 1px,其实就是 1 物理像素宽,对于 CSS 而言,可以认为是border: 0.5px,这是 retina 下 (dpr=2) 下能显示的最小单位

其实,对视口缩小就能解决这个问题。对于 dpr=2iphone 6,将 scale 缩放到 0.5,视觉视口增大到 750,此时 CSS 像素中的 1px 就是物理像素的 1px

文本字号不建议使用 rem

为什么?

因为我们希望文本能够流动,使得在大屏手机能看到更多的文本,而不是文本因为 rem 等比缩放使得大字号显得突兀;同样也不希望在小屏手机字体因为等比缩放显得太小

所以我们根据不同的 dpr 动态设置固定字号的字体

div {
  width: 1rem; 
  height: 0.4rem;
  font-size: 12px; // 默认写上 dpr 为 1 的 fontSize
}
[data-dpr="2"] div {
  font-size: 24px;
}
[data-dpr="3"] div {
  font-size: 36px;
}

为了能更好的利于开发,在实际开发中,我们可以定制一个 font-dpr() 这样的 Sass 混合

@mixin font-dpr($font-size){
  font-size: $font-size;

  [data-dpr="2"] & {
      font-size: $font-size * 2;
  }
  [data-dpr="3"] & {       
    font-size: $font-size * 3;
  }
}

/* 使用 */
@include font-dpr(16px);

最大屏幕控制

如果不设置最大宽度,font-size 将随着布局视口的增大而增大,所有尺寸等比例放大,如果在 PC 端打开网页,巨大的按钮将不堪入目。所以设置一个宽度断点,超过断点将使用同一宽度

function refreshRem(){
  var width = docEl.getBoundingClientRect().width;
  // 最大宽度控制
  if (width / dpr > 540) {
    width = 540 * dpr;
  }
  var rem = width / 7.5;
  docEl.style.fontSize = rem + 'px';
  flexible.rem = win.rem = rem;
}

使用 flexible.js

使用 flexible.js 很简单,只需要在 <head></head> 中引入即可

<!-- ... -->
<script src="./assets/libs/flexible.js"></script>

或者 cdn

<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>

# vw 布局方案

viewport 单位:
vw: 相对于视觉视口(window.innerWidth)宽度,视口宽度被均分为 100 单位的 vw
vh: 相对于视觉视口(window.innerHeight)高度,视口高度被均分为 100 单位的 vh

vw 换算

vw 布局方案中,所有元素采用 vw 布局,对于 750 设计稿,750px = 100vw,即 1px = 100/750vw

使用 Sass 混合

@mixin px2vw($name, $px){
  #{$name}: $px * (100/750) * 1vw;
}

/* 使用示例:*/
.container {
  @include px2vw(height, 750);
}

/* Scss 编译后:*/
.container {
  height: 100vw;
}

解决 border: 1px 问题

使用 transform 缩放元素

.wrapper {
  width: 100px;
  height: 100px;
  background: gold;
  position: relative;
  box-sizing: border-box;
  padding: 10px;
}

.wrapper::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  border-bottom: 20px solid #000;
  transform: scaleY(.5);
  transform-origin: left bottom;
}

这里使用伪元素,是因为 transform: scaleY(.5) 会对整个元素进行缩放,我们只想要 border 缩小一半

不过这种 hack 方法是治标不治本的,圆角的 border 就很麻烦

无法做最大屏幕控制

纯 vw 布局,没有办法控制最大宽度,因为 vw 单位只受视觉视口影响

# vw + rem 方案

vw + rem 方案的关键点是,用 vw 去设置 font-size

vw 计算 font-size

对于 750 设计稿,我们会把宽度分成 7.5 份, 750px / 7.5 = 100px ,每一份就是我们要的 font-size,同样地,我们把视觉视口宽度分成 7.5 份,100vw / 7.5 = 13.33333vw

这样令,font-size=13.33333vw,就不需要用 JS 去设置了

html {
  font-size: 13.33333vw;
}

media 媒体查询设置文本字号

像这样:

@mixin font-media($font-size){
  font-size: $font-size;

  @media (min-width: 375px){
    & {
      font-size: $font-size * 2;
    }
  }
  @media (min-width: 750px){
    & {
      font-size: $font-size * 3;
    }
  }
}

span {
  @include font-media(16px);
}

具体设置多大,就看设计师姐姐了

与 flexible.js 方案的对比

  • 不需要用 JS 去动态设置 font-size
  • 没有对 Retina 屏的视口进行缩放,没有解决 border: 1px 的问题
  • 没有获取 dpr,文本字号需要用媒体查询去设置
  • viewport 单位兼容性没有 rem 单位好

# hotcss 方案

与 flexible.js 类似 https://github.com/imochen/hotcss

# 参考

amfe/article#17

http://www.html-js.com/article/Mobile-terminal-H5-mobile-terminal-HD-multi-screen-adaptation-scheme%203041

https://www.cnblogs.com/xiaohuochai/p/5496995.html

jawil/blog#21

https://www.jianshu.com/p/07669cb3e7c5

https://www.cnblogs.com/libin-1/p/7148377.html

https://my.oschina.net/weijuer/blog/1819301

https://juejin.im/post/5ad4bcdd6fb9a028e33bedab

https://github.com/imochen/hotcss

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant