CSS--移动端适配

概要

基于移动端H5的业务场景下,移动端设备的适配问题显得尤为重要,规划好移动适配方案是项目可以完美落地的基石。所以了解rem、em、css、px、device px等概念以及相互之间的差别与联系是设计出好的适配方案的基础。

device px(设备像素)和 css px(css像素)

通常在PC端,我们并不需要考虑设备像素和css像素之间的差别,1个设备像素通常等于1个css像素,可以使用screen.width/height来获取屏幕的宽高对应的设备像素。

// 以1920X1080的屏幕为例
screen.width // 1920
screen.height // 1080

如果一个元素el的宽度为width: 192px; 那么屏幕上(假设屏幕宽度1920px)一行可以显示10个该元素。原理则是通常PC中1个设备像素等于1个css像素。

当用户放大或者缩小屏幕时(按住ctrl+滚动鼠标轮,也就是改变zoom值),则有所不同。此时,我们的设备像素仍然没有改变``,还是1920*1080,css像素的数量也没有改变`,但是css像素大小变了。 假设放大到200%, 那么1个css像素就等于两个设备像素, 以此类推。

以下是引用ppk大神的三张图片, 下面深蓝色为设备像素, 上面浅蓝色为css像素:

正常情况下:

缩小情况下:

放大情况下:

screen.width/height 和 window.innerWidth/innerHeight

  • screen.width/heihgt取的是屏幕的宽高,单位是是css像素。
  • window.innerWidth/innerHeight取的是网页区域的宽高, 单位是css像素。

注意: 当你改变zoom值时, screen不会改变, innerWidth/height会改变。

移动端中的css像素和设备像素

在移动端中, 1个css像素并不总等于1个设备像素, 而是取决于设备像素比(物理像素(设备像素)/独立像素(css像素)),像Iphone的Retina屏幕, 就有2倍屏(ip6s)、3倍屏(ip6 plus), 也就是设备像素比的值分别是2和3,即1个css像素等同于4个设备像素或者9个, 如图:

可以通过window.devicePixelRatio来获取设备像素比dpr。

viewport的概念

viewport是浏览器视口,代表浏览器的可视区域,也就是浏览器中用来显示网页的那部分区域。在桌面电脑上,由外到内分为显示器窗口、浏览器窗口、浏览器视口 (viewport)、 元素、 元素、其它元素。但viewport又不局限于浏览器可视区域的大小,它可能比浏览器的可视区域要大,也可能比浏览器的可视区域要小。在默认情况下,一般来讲,移动设备上的viewport都是要大于浏览器可视区域的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动设备上正常显示那些传统的为桌面浏览器设计的网站,移动设备上的浏览器都会把自己默认的viewport设为980px或1024px(也可能是其他值,这个是由设备自己决定的),但带来的后果就是浏览器会出现横向滚动条,因为浏览器可视区域的宽度是比这个默认的viewport的宽度要小的。

显示器窗口

显示器窗口就是你的显示器屏幕的可见区域。显示器的分辨率,描述的是显示器窗口的设备像素,又叫做物理像素,通过 screen.width 来表示水平方向的物理像素,screen.height 来表示垂直方向的物理像素。比如一台分辨率为 1920 * 1080 的显示器,它的 screen.width 为 1920,screen.height 为 1080。screen.width 和 screen.height 是固定的,不可以改变。

浏览器窗口

浏览器窗口就是浏览器中用来显示网页的可见区域,不包括工具栏的部分,但是包括水平滚动条和垂直滚动条 (只包括滚动条本身所占的像素,不包括已滚动的网页部分)。

上图中,画红线的部分代表的就是浏览器的窗口,注意包括右侧的滚动条。

通过 window.innerWidth 来表示浏览器窗口水平方向能够显示的 CSS 像素数,window.innerHeight 来表示浏览器窗口垂直方向能够显示的 CSS 像素数。由于桌面设备上,1 个 CSS 像素由 1 个物理像素来渲染,所以当浏览器占满整个显示屏幕宽度的时候,window.innerWidth 就等于 screen.width。window.innerWidth 和 window.innerHeight 是可变的,通过拉伸或者缩放浏览器可以改变。

浏览器视口

viewport 就是浏览器视口,浏览器视口和浏览器窗口类似,只不过不包括水平滚动条和垂直滚动条。如果网页里没有滚动条,则浏览器视口和浏览器窗口的大小一样。

上图中,画红线的部分代表的就是桌面浏览器的视口,也就是 viewport,注意,不包括右侧的滚动条。

通过 document.documentElement.clientWidth 来表示浏览器视口水平方向能够显示的 CSS 像素数,document.documentElement.clientHeight 来表示浏览器视口垂直方向能够显示的 CSS 像素数。如果没有滚动条,document.documentElement.clientWidthwindow.innerWidth 相等。如果有滚动条,window.innerWidth = document.documentElement.clientWidth + 垂直滚动条所占的宽度。document.documentElement.clientWidthdocument.documentElement.clientHeight 是可变的,通过拉伸或者缩放浏览器可以改变。

html元素

HTML 文档中,<html> 元素是文档根元素,通过 document.documentElement 来表示 <html> 元素。<html> 元素所占的 CSS 宽度和高度,用 document.documentElement.offsetWidth 和 document.documentElement.offsetHeight 来显示。<html> 元素默认的宽度为 100%,默认高度为 auto。viewport 可以看做 <html> 元素的父容器 <html> 元素的 width: 100%,这里就是相对于 viewport 的百分比,也就是说,默认情况下,<html> 元素的宽度和 viewport 的宽度一样。

body元素

<body> 元素的父元素是 <html> 元素,所有区块元素默认的 width 都为 100%,默认的 height 都为 auto,因此如果网页里没有内容的时候,经常会出现页头和页脚合在一起的情况。我们可以给 <html><body> 元素设置一个 height,来撑开网页。

html,
body {
  height: 100%;
}

// 或者
body {
  min-height: 100vh; // 这里的 vh 代表 viewport height 的意思,100vh 就是 100% viewport height
}

这样, 和 元素的高度都等于 viewport 的高度。

媒体查询

在桌面浏览器上,媒体查询中的 width height,代表浏览器的 viewport 的宽度和高度,也就是 document.documentElement.clientWidth 和 document.documentElement.clientHeight。

@media (min-width: 900px) {
  body {
    color: red;
  }
}

上面的代码表示,当 viewport >= 900px 的时候,字体颜色为红色。

在桌面浏览器上,媒体查询中的 device-width device-height,代表显示器的物理像素,也就是 screen.width 和 screen.height。

@media (min-device-width: 900px) {
  body {
    color: red;
  }
}

上面的代码表示,当显示器分辨率宽度 >= 900px 的时候,字体颜色为红色。我们一般不会用到 device-width 和 device-height。

rem适配方案

适配是为了使页面在不同手机设备上,相对保持统一的效果。移动端自适应方案很多,有百分比布局,弹性和模型布局等,但是最好用的要数 rem 布局了。

rem是相对于根元素的字体大小的单位,我们可以根据设备宽度动态设置根元素的 font-size,使得以 rem 为单位的元素在不同终端上以相对一致的视觉效果呈现。

如下是3种根据屏幕宽度设置rem基准值的方法:(注:为了换算方便,以下三种方法都用 1:100的比例,即 1rem = 100px)

用JS设置rem基准值

// 设计稿是750, 采用1:100的比例, 用1rem表示100px
(function(doc, win){
  var docE1 = doc.documentElement,
      resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
      recalc = function(){
          var clientWidth = docE1.clientWidth;
          if(!clientWidth) return;
          docE1.style.fontSize = 100 * (clientWidth / 750) + 'px';            
      };

  if (!doc.addEventListener) return;
  win.addEventListener(resizeEvt,recalc,false);
  doc.addEventListener('DOMContentLoaded',recalc,false);
})(document,window);

用密集的媒体查询设置font-size

// 设计稿是750, 采用1:100的比例, 用1rem表示100px, 100*(100/750)=13.33。以min-width:750px时font-size:100px为基准, min-width每缩小100px, font-size就缩小13.33px, 如需要更密集的媒体查询可以按照这个对照关系设置
@media screen and (min-width: 320px) {
  html {
    font-size: 42.6667px;
  }
}
@media screen and (min-width: 375px) {
  html {
    font-size: 50px;
  }
}
@media screen and (min-width: 425px) {
  html {
    font-size: 56.6667px;
  }
}
@media screen and (min-width: 768px) {
  html {
    font-size: 102.4px;
  }
}

用单位vw设置font-size

// 设计稿是750,采用1:100的比例,用1rem表示100px,font-size为100*(100vw/750)
html {
  font-size: 13.3334vw;
}

1vw等于屏幕可视区宽度的可是区域的百分之一

注意: 兼容性不是很好, 移动端 iOS 8 以上以及 Android 4.4 以上已经有了vw\vh单位, 如果你的手机支持该api, 也可以使用该单位方案。

图片模糊问题

一个位图像素是栅格图像(如:png,jpg,gif等)最小的数据单元。每一个位图像素都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)。理论上,1个位图像素对应于1个物理像素,图片才能得到完美清晰的展示。对于dpr=2的Retina屏幕而言,1个位图像素对应于4个物理像素,由于单个位图像素不可以再进一步分割,所以只能就近取色,导致图片看起来比较模糊,如下图:

对于图片模糊问题,比较好的方案就是用多倍图片(@2x)。如:一个200 x 300(CSS pixel)的img标签,对于dpr=2的屏幕,用 400x600 的图片,如此一来,位图像素点格式就是原来的4倍,在Retina屏幕下,位图像素点个数就可以跟物理像素点个数形成 1:1 的比例,图片自然就清晰了。

如果普通屏幕下,也用了两倍图片,会怎样呢?

在普通屏幕下,200 x 300(CSS pixel) img 标签,所对应的物理像素个数就是 200 x 300个,而两倍图片的位图像素个数是 200 x 300 x 4 个,所以就出现一个物理像素点对应 4 个位图像素点,但它的取色也只能通过一定的算法取某一个位图像素点上的色值,这个过程叫做 downsampling,肉眼看上去虽然图片不会模糊,但是会觉得图片缺少一些锐利度,或者是有点色差,如下图:

所以最好的解决方法是:不同的 dpr 下,加载不同的尺寸的图片。不管是通过CSS媒体查询,还是通过JS条件判断都是可以的。

1px 细线问题

CSS像素为1px宽的直线,对应的物理像素是不同的,可能是 2px 或者 3px,而设计师想要的 1px 宽的直线,其实就是 1 物理像素宽,如下图:

对于CSS而言,可以认为是 border:0.5px;这是多倍屏下能显示的最小单位。然而,并不是所有手机浏览器都能识别border:0.5px,有的系统里,0.5px会被当成 0 px 处理,那么如何实现这 0.5px呢?网上有很多解决方法,如 border-image 图片、background-image 渐变、box-shadow 等。

推荐两种比较好用的方法:
①.伪元素 + transform

构建1个伪元素,border为1px,再以transform缩放到 50%

/* 设计稿是750,采用1:100的比例,font-size为100*(100vw/750) */
.border-1px {
    position: relative;
}
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border-1px:before {
        content: " ";
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        height: 1px;
        border-top: 1px solid #D9D9D9;
        color: #D9D9D9;
        -webkit-transform-origin: 0 0;
        transform-origin: 0 0;
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
}

②.用JS计算rem基准值和viewport缩放值

/* 设计稿是750,采用1:100的比例,font-size为100 * (docEl.clientWidth * dpr / 750) */
var dpr, rem, scale;
var docEl = document.documentElement;
var fontEl = document.createElement('style');
var metaEl = document.querySelector('meta[name="viewport"]');
dpr = window.devicePixelRatio || 1;
rem = 100 * (docEl.clientWidth * dpr / 750);
scale = 1 / dpr;
// 设置viewport,进行缩放,达到高清效果
metaEl.setAttribute('content', 'width=' + dpr * docEl.clientWidth + ',initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale + ',user-scalable=no');
// 设置data-dpr属性,留作的css hack之用,解决图片模糊问题和1px细线问题
docEl.setAttribute('data-dpr', dpr);
// 动态写入样式
docEl.firstElementChild.appendChild(fontEl);
fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';

这个"用JS计算rem基准值和viewport值"的方案可以解决1px细线问题;以2倍Retina屏做比较,如下表格,其他多倍屏同理:

用JS计算rem基准值 用JS计算rem基准值和viewport缩放值
CSS像素为750px的普通屏 1rem = 100px;initial-scale=1;1个CSS像素=1个物理像素 1rem = 100px;initial-scale=1;1个CSS像素=1个物理像素
CSS像素为750px的Retina屏 1rem = 100px;initial-scale=1;1个CSS像素=2个物理像素 1rem = 200px;initial-scale=0.5;1个CSS像素=1个物理像素

用JS根据屏幕尺寸和dpr精确地设置不同屏幕所应有的rem基准值和initial-scale缩放值,这个JS方案已经在完美解决了1px细线问题,我们不需要再做任何事情,至于图片模糊问题,只要根据data-dpr的值动态加载不同尺寸的图就可以了。

为什么不用rem方案

当用户使用更大的屏幕的时候, 他应该能看到更多的内容, 而且设计稿被放大或者缩小的话, 会失去他原来的感觉。所以, 对于rem方案其实可能已经不太适合当前的情况了, 毕竟使用媒体查询和px以及em就能解决各种响应式问题。

参考文章

posted @ 2021-08-01 22:05  Elwin0204  阅读(553)  评论(0编辑  收藏  举报