freedomcly / 1px

移动端 1px 解决方案

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Flexible 原理浅析

一、为解决缩放问题,固定屏幕宽度(移动端为什么有 1px 问题)

缩放

默认情况下,html 文件在移动端上是可以缩放的。

有两个 pixel 单位:

  • device pixel,如screen.width/screen.heightevent.screenX/event.screenY
  • CSS pixel,其余大多数值,如document.documentElement.clientWidth

在屏幕上 device pixel 的长度是不变的,可以认为是设备属性。CSS pixel 的长度可以通过缩放改变。

缩放的本质就是 CSS pixel 和 device pixel 的比例改变。

CSS pixel 和 device pixel 的比例是 100%:

放大:

缩小:

因此,默认情况下屏幕最小单位的 1px hairline,页面缩小到一定程度是会出现的。见 demo: 1-zoom

屏幕宽度

由于存在两种 pixel 单位,屏幕宽度是多少呢?

  • screen.width,用 device pixel 作为单位
  • document.documentElement.clientWidth(或window.innerWidth),用 CSS pixel 作为单位

iPhone 6 中:

screen.width // 375
document.documentElement.clientWidth // 980

禁止缩放

缩放让页面失控,为了提升用户体验,移动端通常会在 html 中引入这段代码:

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

这段代码中,

user-scalable=no表示禁止缩放。

width=device-width表示页面宽度设置为设备宽度,也就是把可变的 CSS pixel 设置为与 device pixel 相等,当然也就是把document.documentElement.clientWidth设置为与screen.width相等。见 demo: 2-device-width

initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0表示每个 CSS pixel 放大倍数,如设置为 1,则document.documentElement.clientWidth等于screen.width,如设置为 0.5,则document.documentElement.clientWidth / screen.width === 2

scale 的优先级高于 width=device-width

demo: 3-scale

在一段时间内,这种方法非常有效地提升了移动端的用户体验。但 retina 屏幕出现后,又出问题了。

retina 设备上引入这段代码后,1 个 device pixel 不是屏幕最小长度单位,而是{{devicePixelRadio}}个。若把可变的 CSS pixel 设置为与 device pixel 相等,1px 其实是 devicePixelRadio 倍的屏幕最小单位。

二、为解决 1px 问题,动态改变 scale

既然能拿到 devicePixelRadio,可以动态地设置 scale:

var dpr = window.devicePixelRatio || 1
var scale = 1 / dpr
var content = 'width=device-width, initial-scale=' + scale + ', minimum-scale=' + scale + ', maximum-scale=' + scale + ', user-scalable=no'
var viewportEl = document.querySelector(flexible)

if (viewportEl) {
  viewportEl.setAttribute('content', content)
} else {
  viewportEl = document.createElement(flexible)
  viewportEl.setAttribute('name', 'viewport')
  viewportEl.setAttribute('content', content)
  document.head.appendChild(viewportEl)
}

demo: 4-dynamic-scale

1px 的问题完美解决,然而又引入了新问题,px 单位长度在不同 dpr 的设备上非常的不同,因此用什么单位又成了问题。

三、为解决 px 单位问题,引入 rem 布局

引入动态 scale 后,在 iPhone 6 上,屏幕宽度 document.documentElement.clientWidth 是 750,在 iPhone 6 plus 上,屏幕宽度 document.documentElement.clientWidth 是 1242,同样 100px 的小方块,在两个屏幕上的占比非常不一样。因此,px 不再适用。

为了适配不同屏幕,可以引入 rem 布局。rem 在 html 中的 fontSize 值怎么确定呢?

为了方便计算,我们希望1rem = 1px。设计师的基准屏幕宽度是 750px。那么有以下公式:

屏幕宽度 / 750 = fontSize / 1,即 fontSize = 屏幕宽度 / 750。

问题又出现了,fontSize 不能小于 12px,否则直接设置为 12px,因此现在1rem === 12px

demo: 5-font-size

四、为解决 fontSize 过小问题,引入倍数

屏幕宽度 / 750 = fontSize / 1,这个公式建立在我们希望1rem = 1px

如果1rem = 12px,问题得到解决。但是每次测量都需要把 px 值除以 12 才能得到 rem 值。

直接1rem = 100px,可以让换算过程更加简单。

到此为止,问题都得到很好的解决。见 demo: 6-100

其他解决方案

  • gradient,原理是在background-image中写一个线性渐变linear-gradient,可以得到 0.5 倍的线,但在多倍屏上有局限性)
  • postcss-write-svg

参考资料

About

移动端 1px 解决方案


Languages

Language:JavaScript 100.0%