共计 6405 个字符,预计需要花费 17 分钟才能阅读完成。
前言
工作以后,大部分的业务工作都是基于移动端 H5 的,开发过程中学习了很多东西,遇到过许多问题,诸如 rememcss pxdevice px 等,本文纯属个人的归纳总结,如有问题,请指出亲喷~
PC 端
本文主要是讲解移动端的响应式布局,但是在真正进入之前,先了解一些概念。
device px(设备像素)和 css px(css 像素)
通常在 PC 端上面,我们并不需要考虑设备像素和 css 像素之间的差别,以目前的 pc 来看,1 个设备像素通常等于 1 个 css 像素。可以使用 screen.width/height 来获取我们屏幕的宽高设备像素。
screen.width // 1920
screen.height // 1080
如果你给一个元素的宽度为 width: 192px; 那么你的屏幕上 (假设你的屏幕宽度像素为 1920) 可以在一行上显示 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 会改变。
viewport 的概念
viewport 是一个限制 html 元素的功能,可以理解为 html 元素的上一层元素。听起来有点难以理解,下面讲一个例子:
假设,你给某个 div 元素设置了 width:50% 的样式后,当你缩小放大浏览器的时候,你会发现 div 元素总是占据了 50% 的宽度,我们知道,宽度百分比是依赖它的包裹元素(假设是 body),那么问题就回到了 body 的宽度身上。通常在没有设置宽度的情况下,所有块级元素都占用其父级宽度的 100%。所以 body 和 html 元素一样宽。那么 html 元素有多宽呢,默认情况下它和浏览器窗口一样宽,这也就是为什么 div 总是占据浏览器宽度的 50%,而 html 元素则是受限于 viewport(和 viewport 占据一样的宽度),换句话说,viewport 完全等于浏览器窗口,而且它不是 HTML 语言元素,所以你无法通过使用 css 对其进行影响。
我们可以通过 document.documentElement.clientWidth/clientHeight 来获取 viewport 的宽高,它的单位是 css 像素。
clientWidth/Height 和 window.innerWidth/innerHeight
上面两者都能够获取网页区域大小,但是他们之间还是有区别的。前者不包含滚动条,后者包含。
html 元素的大小
我们可以通过 document.documentElement.offsetWidth/offsetHeight 来获取 html 元素的宽高,它的单位是 css 像素。
event 事件和媒体查询
event 的三对属性:
pageX/Y: 给出 CSS 像素中相对于 html 元素的坐标
clientX/Y: 给出 CSS 像素中相对于 viewport 的坐标
screenX/Y: 给出设备像素中相对于屏幕的坐标
媒体查询:
基于 viewport(documentElement .clientWidth/Height)
@media all and (max-width: 400px)
基于屏幕(screen.width)
@media all and (max-device-width: 400px)
Mobile
在默认情况下,一般来讲,移动设备上的 viewport 都是要大于浏览器可视区域的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动设备上正常显示那些传统的为桌面浏览器设计的网站,移动设备上的浏览器都会把自己默认的 viewport 设为 980px 或 1024px(也可能是其它值,这个是由设备自己决定的),但带来的后果就是浏览器会出现横向滚动条,因为浏览器可视区域的宽度是比这个默认的 viewport 的宽度要小的。下图列出了一些设备上浏览器的默认 viewport 的宽度。
以下是关于各浏览器的 viewport
三个 viewport
前面介绍了 viewport 的概念,但是在移动端的时候,viewport 并不那么容易理解,ppk 在移动端提出了三个 viewport 的概念。
Layout viewport 也就是布局 viewport,即默认的浏览器 viewport,并且可以通过 document.documentElement.clientWidth 来获取。
图片引用自深入理解 viewport
visual viewport layout viewport 的宽度是默认的浏览器 viewport,所以我们还需要一个 viewport 来代表网页区域的大小,ppk 把这个 viewport 叫做 visual viewport。visual viewport 的宽度可以通过 window.innerWidth 来获取。
图片引用自深入理解 viewport
ideal viewport 有了两个 viewport 并不 ok,因为我们既不想让用户滚动滚动条来浏览我们的网页,也不想用户盯着缩小了的 pc 网页浏览,所以有了第三个 viewport。所谓的 ideal viewport 则是当 layout viewport 等于屏幕的宽度,如 ip6,它的 ideal viewport 就是 375px。
设置 viewport
开发过 h5 的应该都知道,我们经常会把下面这句代码复制到 head 标签中:
<meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0″>
它的作用其实就是设置了 ideal viewport。以下是它的 6 个属性:
key
value
width
设置 layout viewport 的宽度,为一个正整数,或字符串 ”width-device”
initial-scale
设置页面的初始缩放值,为一个数字,可以带小数
minimum-scale
允许用户的最小缩放值,为一个数字,可以带小数
maximum-scale
允许用户的最大缩放值,为一个数字,可以带小数
height
设置 layout viewport 的高度,这个属性对我们并不重要,很少使用
user-scalable
是否允许用户进行缩放,值为 ”no” 或 ”yes”, no 代表不允许,yes 代表允许
那么如果我们想设置 ideal viewport,只需要把 width 设置成 width-device 或者把 initial-scale 设置成 1.0 就可以了。
前者比较容易理解,后者设置成 1 就可以是为什么?首先要理解设置成 1.0 就是意味着没有缩放,而这样却可以达到 ideal viewport 的效果,那么很明显,缩放是相对于 ideal viewport 来进行缩放的,当对 ideal viewport 进行 100% 的缩放,也就是缩放值为 1 的时候,不就得到了 ideal viewport。
如果两个属性都能设置 ideal viewport,那么当两个属性冲突时怎么解决?
遇到这种情况时,浏览器会取它们两个中较大的那个值。例如,当 width=400,ideal viewport 的宽度为 320 时,取的是 400;当 width=400,ideal viewport 的宽度为 480 时,取的是 ideal viewport 的宽度。
css 像素和设备像素
在移动端中,1 个 css 像素并不等于 1 个设备像素,而是取决于设备像素比(物理像素(设备像素)/ 独立像素(css 像素)),像 Iphone 的 Retina 屏幕,就有 2 倍屏(ip6s)、3 倍屏(ip6 plus),也就是设备像素比的值分别是 2 和 3,即 1 个 css 像素等同于 4 个设备像素或者 9 个,如图:
并且,我们可以通过 window.devicePixelRatio 来获取设备像素比 dpr。
1px 的产生和解决
问题的产生
公司的设计大佬通常给的设计稿是基于 ip6s 的,也就是 750px(i6s 的屏幕是 375px,而且是上面说的两倍屏,所以有 750 个物理像素)。假设设计稿上面有个 1px 的 border,我们通常直接这样写:
border {
border: 1px solid #ccc;
}
然后设计审核的时候就被打回来了,因为设计觉得变大了,也就是他觉得是 2px 的线了。
究其原因,是因为设计稿是 750px,里面的 1px 实际上在真机只有 0.5px,所以就有了著名的 1px 问题。
问题的解决
1. 直接使用 0.5px 在 iOS8 下,苹果系列都已经支持 0.5px 了,那么意味着在 devicePixelRatio = 2 时,我们可以借助媒体查询来处理:著作权归作者所有。
@media (-webkit-min-device-pixel-ratio: 2) {
.border {
border-width: 0.5px
}
}
这种使用简单,但是兼容性不太好。
2. 使用 border-image 或者 background 也就是拿一张图片,一半透明,一半是我们想要的颜色,然后填充上去,具体的例子就不讲了,这种基本没啥人会用,改个颜色还要修改图片,太麻烦了。
3.box-shadow
.box-shadow-1px {
box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}
这种颜色有阴影,估计过不了设计大佬的那关。
4.PostCSS 的插件 postcss-write-svg 直接借助插件帮助我们实现,其实也就是 postcss 帮我们生成图片而已。
@svg 1px-border {
height: 2px;
@rect {
fill: var(–color, black); width: 100%; height: 50%;
}
}
.example {border: 1px solid transparent;
border-image: svg(1px-border param(–color #00b1ff)) 2 2 stretch;
}
最后 Postcss 会把对应的 css 编译出来,这种兼容性好,就是依赖于插件。
.example {border: 1px solid transparent;
border-image: url(“data:image/svg+xml;charset=utf-8,%3Csvg xmlns=’http://www.w3.org/2000/svg’ height=’2px’%3E%3Crect fill=’%2300b1ff’ width=’100%25′ height=’50%25’/%3E%3C/svg%3E”) 2 2 stretch
}
5. 伪类 + transform 实现 原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
.scale-1px{
position: relative;
border:none;
}
.scale-1px:after{
content: ”;
position: absolute;
bottom: 0;
background: #000;
width: 100%;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
这种兼容好,但是会和伪类冲突,也是我司采用的方式。
6. 缩小 viewport 原理是使用 meta 标签中的 viewport,也就是上面所说的设置 viewport,将整个页面缩小 0.5 倍,这个主要是麻烦在其他的元素也要跟着放大一倍再缩小,为了这个小问题而这样,似乎有点得不偿失。
然而,淘宝的 flexible 方案(rem 布局,见下文)帮我们搞定了这个问题。
flexible 和 rem
上面提到了 flexible 和 rem 的布局方案,在刚推出的时候,确实很火,公司的一些项目目前仍然是采用该方案,这里简单的说下其原理。
px、em、rem
px: px 在前面已经讲了,就是一个固定单位。
em: em 作为 font-size 的单位时,其代表父元素的字体大小,em 作为其他属性单位时,代表自身字体大小。
rem: rem 作用于非根元素时,相对于根元素字体大小;rem 作用于根元素字体大小时,相对于其出初始字体大小。
/* 作用于根元素,相对于原始大小(16px),所以 html 的 font-size 为 32px*/
html {font-size: 2rem}
/* 作用于非根元素,相对于根元素字体大小,所以为 64px */
p {font-size: 2rem}
flexible rem 布局原理 flexible rem 布局原理即是把设计稿等比宽的切成 100 份,假设每份的单位是 x,那么我们在布局的时候就可以以 x 为单位,将设计稿等比例的放大缩小到对应的屏幕了,这样就不用为各个屏幕做适配。
然而像上面所说的 x 是不存在的,不过好在我们有 rem,只要我们将 rem 设置成 1x,那么开发过程中,不就达到我们的目的了吗?
如何将 rem 设成 1x 呢?回想一下,我们是不是能取得 viewport 的宽度(document.documentElement.clientWidth),我们能取得设备像素比(window.devicePixelRatio), 我们能够设置 html 的样式(html.style.fontSize = ‘…’),所以简单的实现方案就有了
document.documentElement.style.fontSize = document.documentElement.clientWidth / 100 + ‘px’;
当然,flexible 并不这么简单,它还对于不同 dpr 做了处理,它帮你处理了 2 倍屏、3 倍屏等情况,通过设置 viewport 的缩放比,这也就是上面所说的 0.5px 处理方案之一,具体的这里不再赘述,有兴趣的同学可以看看原文。
最后,移动端 iOS 8 以上以及 Android 4.4 以上已经有了 vw\vh 单位,1vw1vh 相当于 viewport 的百分之一宽 / 高,也就是我们上面所说的 x 单位,如果你的手机支持该 api,也可以使用该单位方案。
为什么不用 rem 方案
依稀记得,某次使用了 rem 处理活动页的时候,被设计大佬驳回了。大佬认为,当用户使用更大的屏幕的时候,他应该能看到更多的内容,而且设计稿被放大或者缩小的话,会失去他原来的感觉。所以,对于 rem 方案其实可能已经不太适合当前的情况了,毕竟使用媒体查询和 px 以及 em 就能解决各种响应式问题,虽然效率会比较低下,而关于这个,也恰好看到了有人在知乎上提了这么个问题,有兴趣的可以前去围观。
为什么很多 web 项目还是使用 px,而不是 rem?
总结
本文多是概念上的,也参考了许多文章,要真正理解还需要多参考实际项目。
参考 & 引用
移动前端开发之 viewport 的深入理解 A tale of two viewports — part one A tale of two viewports — part two Meta viewport 7 种方法解决移动端 Retina 屏幕 1px 边框问题 再谈 Retina 下 1px 的解决方案 vh,vw 单位你知道多少?Rem 布局的原理解析