本文从设备像素,CSS像素这些基本概念出发,先向你们介绍移动端适配必需要掌握的一些基本像素知识及viewport理论,最后在向你们介绍移动端适配解决方案:flexible.js和vw/vhhtml
设备像素:device pixel,dp,物理像素,不可变,下图标红的部分指的就是设备像素前端
CSS像素:web编程用到的,咱们在JS和CSS中使用的10px就是CSS像素,是可变的。CSS像素受屏幕缩放和设备像素比(dpr)的影响。如咱们网页的中的字体在网页放大以后会变大,还有在移动端看起来会比PC端小一些android
设备独立像素:device independent pixel,dip,与设备无关的像素web
再来聊下它们之间的关系:PC端和移动端编程
100%缩放浏览器
1设备独立像素 = 1设备像素bash
200%缩放app
1设备独立像素 = 2设备像素iphone
由于不一样设备的PPI不一样,在标准屏幕下(160PPI)ide
1设备独立像素 = 1设备像素
至于CSS像素和他们之间的关系,等下面讲到了设备像素比再说
device pixel ratio,dpr
DPR = 设备像素 / 设备独立像素
下图是一些手机的设备像素比数据
在JS中咱们能够经过window.devicePixelRatio来获得当前设备的dpr
在CSS中咱们能够经过-webkit-device-pixel-ratio来进行媒体查询
注意,当咱们放大或者缩小屏幕的时候,window.devicePixelRatio是可变的
有了dpr再来讲说,CSS像素和设备像素之间的关系
当dpr为1,1个CSS像素对应1个设备像素
当dpr为2,1个CSS像素对应4个设备像素
当dpr为3,1个CSS像素对应9个设备像素
......
针对有些同窗后面提出的疑问,我详细解释这块,当dpr为1,此时1个CSS像素对应1个设备像素,这个仍是很好理解的,当dpr为2,此时在水平方向上的设备像素是dpr为1的两倍,竖直方向上的设备像素也是dpr为1的两倍,因此此时的1个CSS像素对应2^2个设备像素,这个就至关于咱们把一个矩形的长宽放大为以前的两倍,此时的矩形面积为以前的四倍,当dpr为3时,此时的1个CSS像素对应3^2个设备像素,因此1个CSS像素对应dpr^2个设备像素
下图生动的表示了他们之间的关系
这里跟你们说一个小技巧,就是在移动端的时候能够根据dpr的值,使用不一样分辨率的图片,如2X仍是3X,这样能够保证与在普通屏幕上看到的图片效果一致,不至于失真
DPI(Dots Per Inch)源于印刷行业,表示每英寸打印机喷的墨汁点数
PPI(Pixels Per Inch)计算机借鉴了DPI,创造了PPI,表示每英寸的像素数量,即像素密度
PPI的计算公式以下:
如今两者均可用于描述计算机显示设备的像素密度,意思同样
下面一张图描述了iphone三个机型的参数
其中能够发现iphoneX的像素密度达到了458ppi,完爆其它两个
Retina屏幕即视网膜屏幕,是苹果发布iphone 4提出的。之因此称做是视网膜屏幕,是由于ppi过高,人类没法分辨出屏幕上的像素点,目前不少智能手机都采用Retina屏幕
iphone3G/S 和 iphone4的屏幕尺寸都是3.5寸,可是iphone4在水平和竖直方向的物理像素都是iphone3G/S的一倍
PC端有几个尺寸咱们须要弄懂下,它们是:
screen.width指的是咱们显示器的水平方向的像素时,不随着咱们浏览器窗口的变化而变化,是用设备像素衡量的
window.innerWidth指的是浏览器窗口的宽度,是能够变化的,因此使用的是CSS像素
下面是100%缩放,window.innerWidth的截图
能够发如今100%缩放状况下,window.innerWidth的值为1192,window.innerHeight的值为455,接着咱们尝试将放大到200%,再来看看效果
能够看到当放大2倍以后,window.innerWidth和window.innerHeight都变成了放大以前的1/2,可是此时window.devicePixelRatio变成了放大以前的2倍,为何是这样子呢?
其实这个也不难理解?由于window.innerWidth是用CSS像素衡量的,放大两倍以后,浏览器窗口只能看到以前一半的内容,因此window.innerWidth是以前的一半,而dpr = 设备像素 / 设备独立像素,这里的设备独立像素就是咱们的window.innerWidth,因此dpr变为原来的2倍,若是看的有点晕,不如尝试缩放本身的浏览器看下效果就知道了
document.documentElement.clientWidth指的是viewport的宽度,与window.innerWidth的区别就只差了一个滚动条
document.documentElement.offsetWidth则是取得html标签的宽度
看到没document.documentElement.offsetHeight此时为0,我打开调试定位了下,发现此时html高度确实是为0,而document.documentElement.clientHeight此时为455,是viewport的高度,只不过此时viewport的高度和window.innerHeight相等
对于pc端,总之记住如下几点:
移动端的话和PC端大相径庭,咱们必须先要掌握三个viewport:
布局layout,和PC端的viewport很像,PC端的viewport的宽由浏览器窗口的宽决定的,用户能够经过拖动窗口或者缩放改变viewport的大小,可是在移动端则不一样,在IOS中 layout viewport默认大小980px,在android中layout viewport为800px,很明显这两个值都大于咱们浏览器的可视区域宽度。咱们能够经过document.documentElement.clientWidth来获取layout viewport的宽度
有了layout viewport,咱们还须要一个viewport来表示咱们浏览器可视区域的大小,这个就是visual viewport。visual viewport的宽度能够经过window.innerWidth获取
移动端浏览器为了避免让用户经过缩放和滑动就能看到整个网页的内容,默认状况下会将visual viewport进行缩放到layout viewport同样大小,这也就解释了为何PC端设计的网页在手机上浏览会缩小,其实这是跟移动浏览器默认的行为有关系
设备理想viewport,有如下几个要求:
这个viewport就叫作ideal viewport。可是不一样的设备的ideal viewport不同,有320px,有360px的,还有384px的......
总之在移动端布局中咱们须要的是ideal viewport。它等于咱们移动设备的屏幕宽度,这样针对ideal viewport设计的网站,在不一样分辨率的屏幕下,不须要缩放,也不须要用户滚动,就能够完美呈现
咱们能够经过meta标签设置咱们viewport,下面这段代码你应该见过不止一次了
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
复制代码
这段代码的做用就是设置当前的layout viewport的宽度为设备的宽度,初始化缩放为1.0,同时不容许缩放,这应该是咱们你们都想要的效果
关于meta viewport标签最初是由Apple公司在Safari浏览器中引入的,目的就是为了解决移动设备的viewport的问题,后来其他公司纷纷效仿,它有如下几个属性:
属性 | 说明 |
---|---|
width | 设置layout viewport的宽度,数字或者device-width |
height | 设置layout viewport的高度,数字或者device-height |
initial-scale | 页面初始缩放值 |
maximum-scale | 用户最大缩放值 |
minimum-scale | 用户最小缩放值 |
user-scalable | 容许用户缩放,yes或no |
设置当前的layout viewport的宽度为设备的屏幕宽度,这样咱们的网站就是针对设备的屏幕宽度进行排版的,而这个不正是上面所说的ideal viewport,因此经过这样咱们可让咱们layout viewport的宽度等于ideal viewport的宽度
可是在iphone和ipad上,不管是横屏仍是竖屏,device-width都是竖屏的屏幕宽度
下面是我设置了width=device-width以后
能够看到设置了width=device-width以后,document.documentElement.clientWidth和window.innerWidth都变成了375,即设备的屏幕宽度
经过这种方式咱们也可让咱们的layout viewport的宽度等于ideal viewport的宽度,缘由是initial-scale是针对ideal viewport进行缩放的,当咱们设置为1.0也就是缩放100%,就可让咱们的layout viewport和ideal viewport同样大
下面是我设置了initdial-scale=1.0以后
效果同设置了width=device-width同样
但此次咱们发如今winphone上,不管横屏仍是竖屏,都将layout viewport的宽度设置为竖屏的屏幕宽度
因此为了兼容,建议咱们同时写上这两个属性,即
width=device-width,initial-scale=1.0
当咱们设置init-scale为其余值又是个什么状况呢?
下图是我设置了initial-scale=0.5的效果
能够看到document.documentElement.clientWidth和window.innerWidth都变成了750,为initial-scale=1的两倍,由此咱们能够有一个假设:
layout viewport宽度 = ideal viewport宽度 / initial-scale
咱们继续设置initial-scale=3,按照上述的结论,此时document.documentElement.clientWidth和window.innerWidth应该为125
事实证实咱们上述的假设是正确的
flexible.js是阿里无线前端团队开源的用于移动端适配的库。虽然如今官方都认可能够放弃这个解决方案了,可是了解其中的思想仍是很重要的
因为viewport单位获得众多浏览器的兼容,lib-flexible这个过渡方案已经能够放弃使用,不论是如今的版本仍是之前的版本,都存有必定的问题。建议你们开始使用viewport来替代此方案。vw的兼容方案能够参阅《如何在Vue项目中使用vw实现移动端适配》一文。
废话很少说,直接上代码
(function flexible (window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
复制代码
因为版本不一样,可能你们拿到的代码局部地方有所不一样,可是总体思路仍是不变的。
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
复制代码
setBodyFontSize这个函数的做用就是设置body标签的fontSize,fontSize的值dpr * 12,这个函数的做用是为了覆盖html的fontSize
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
复制代码
首选获取document.documentElement.clientWidth的值,这个值表示当前设备layout viewport的宽度(你能够理解html标签的宽度),在iphone6 7 8下这个值是750,而后将整个视口分红10份,这样每一份的宽度为clientWidth / 10,即1rem = clientWidth / 10,之因此分红10份,彻底是方便计算,你也能够随意切分,可是最小值不要小于12,由于在谷歌浏览器中有最小fontSize的限制
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
复制代码
这段代码是为了在window触发了resize和pageShow事件以后自动调整html的fontSize值
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
复制代码
这句话的代码是检测0.5px的支持,可是我本身还没弄懂,有哪位同窗若是弄明白了,能够在下面发个评论,你们互相学习
还有一点差点忘记了,设置viewport
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
复制代码
看了下flexible的实现,我本身也简单的实现了下,基本思想同flexible.js同样,只不过我添加了一个自动计算scale的功能
var docuEl = document.documentElement
var metaEl = document.createElement('meta')
var dpr = window.devicePixelRatio
scale = 1 / dpr
metaEl.setAttribute('name', 'viewport')
metaEl.setAttribute('content', `initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale},user-scalable=no`)
docuEl.setAttribute('data-dpr', dpr)
document.head.appendChild(metaEl)
function resizeFontSize() {
docuEl.style.fontSize = docuEl.clientWidth / 10 + 'px'
}
resizeFontSize()
window.onresize = resizeFontSize
window.onpageshow = function(e) {
if (e.persisted) {
resizeFontSize()
}
}
复制代码
现在flexible.js已经成了过去式,咱们实现移动端自动适配,还要在head中添加js代码,对于任何一个追求完美的人确实不能忍,还好咱们有vw和vh,并且它们在现在大部分手机中都获得了支持
未完待续