What will you gain from this article?
- Clarify the significance of the existence of the three viewports on the mobile terminal.
- Have a clear understanding of the configuration and configuration
<meta name="viewport">
in the core tags of mobile terminal adaptation , and be able to solve practical problems through configurationwidth
initial-scale
initial-scale
- Have a clear understanding of the "confusing behavior"
flexible.js
set in - solve the "1px problem"initial-scale
- Understand and solve the problem of blurred pictures on mobile high-power screens.
The meaning of Initial-scale and width in the meta tag
A brief summary of the three viewports on the mobile terminal: the so-called layout viewport is the space where the dom layout we wrote is located, and the page layout is rendered in this space, and then the layout is rendered on the mobile phone through zooming (underlying automatic proxy) It is displayed on the screen one by one; the visual viewport refers to the space presented on the screen of the mobile phone, that is, what the user sees; the ideal viewport can be understood as a standard, a reference, dip(设备独立像素)
and since Its own dip
size is similar to a hard-coded software and hardware attribute (in the initial state where the mobile page is not zoomed, what the developer wrote is 1css像素
equal to 1dip
)
First of all, clarify the significance of the three viewports for our front-end developers- to describe how many css pixels cover the mobile phone screen for developers (generally based on the width).
- If you do not write
<meta name="viewport">
tags to configure the viewport, the width of the layout viewport may be980px
a relatively large value, and eventually the page content on the layout viewport will be reduced and placed on the mobile phone screen. For us developers, it means development When we write980px
the box will fill the width of the phone screen. - If we
<meta name="viewport">
setwidth
the property to a specific value in ordevice-width
, in fact, it is to modify the width of the layout viewport, the final effect is: write what we set during development towidth
fill the screen. Because the layout viewport still needs to be reduced to the visual viewport, that is, the mobile phone screen, this is the processing logic of the mobile device. <meta>
Another attribute of the labelinitial-scale
, the value is a number greater than 0 such as 1, 0.5. The meaning of this number isdip
the width of the device, that is, the ideal viewport width (fixed value) is the visual viewport widthinitial-scale
, that is, the size of the visual viewport is set. In any case, the visual viewport of a mobile device is ultimately presented with an entire mobile phone screen, so to put it bluntly, byinitial-scale
adjusting the size of the visual viewport, it is still describing how many css pixels the mobile phone screen (width) consists of for developers composition.
Setting width
is equivalent to modifying the size of the layout viewport, and finally the layout viewport is reduced and thrown into the mobile phone screen (both are completed by background software and hardware agents); setting initial-scale
is equivalent to directly modifying the size of the visual viewport ideal viewport
as a reference, and the visual viewport is ultimately determined by The mobile phone screen is displayed (completed in the background), so the two seem to conflict : for example, if I set the layout viewport to 800, it means that it takes 800px to fill the width of the mobile phone screen, and I set it to initial-scale
( 0.1
assuming the device dip
width is 300) , then it is equivalent to modifying the width of the visual viewport to 3000px, which means that it takes 3000px to fill the width of the mobile phone screen—a conflict occurs.
When it conflicts width
with initial-scale
the description of the screen width of the mobile phone, the screen width (how many css pixels are in the screen) takes the larger value of the width described by the two attributes of width and initial-scale .
for example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
width: 50vw;
height: 50vw;
border: 1px solid red;
}
.child {
width: 380px;
height: 380px;
background: pink;
}
</style>
</head>
<body>
<div>123
<div class="child">456</div>
</div>
<script>
console.log("width=device-width, initial-scale=1.0");
console.log(window.devicePixelRatio);
console.log("layout viewport:", document.documentElement.clientWidth);
console.log("visual viewport:", window.innerWidth);
console.log("ideal viewport:", window.screen.width);
</script>
</body>
</html>
这段代码用Pixel 5
机型(dip大小/理想视口大小: 393 * 851
)打开,代码暂时只需要关注<meta>
里,以及下面我描述的456盒子的大小,width
与initial-scale
所描述的屏幕宽度都是等于设备的dip
宽度,也就是393px
效果如下(380px的456盒子基本占满了屏幕,也就是屏幕宽度就是393px):
修改<meta>
标签的initial-scale
为0.5
,并修改456盒子的宽度为760px,代码与效果如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=0.5">
<title>Document</title>
<style>
div {
width: 50vw;
height: 50vw;
border: 1px solid red;
}
.child {
width: 760px;
height: 760px;
background: pink;
}
</style>
</head>
<body>
<div>
123
<div class="child">456</div>
</div>
<script>
console.log("width=device-width, initial-scale=0.5");
console.log(window.devicePixelRatio);
console.log("layout viewport:", document.documentElement.clientWidth);
console.log("visual viewport:", window.innerWidth);
console.log("ideal viewport:", window.screen.width);
</script>
</body>
</html>
通过对比就可以印证我所说的:width
与initial-scale
所描述的屏幕大小(多少css像素撑满屏幕)冲突时取较大值。plus:仔细观察也会发现相同font-size
的文字,1px
的边框在上面呈现的效果都不一样,印证了屏幕大小变了。
width与initial-scale的取值能为移动端适配带来什么
细心的读者会发现,上面的两个html
例子中,都有一个123盒子,他的大小是50vw * 50vw
,在两个例子中屏幕大小(css多少)不同的情况下,呈现的效果是一致的(都占了屏幕宽度的一半)。所以理论上讲:如果我们在移动端开发过程中所有的大小单位都采用vw
或者rem(本质还是占屏幕的百分比)
,那么具体屏幕有多少css像素我们毫不关心,换句话说,width
与initial-scale
的取值开发者完全不关心。?
但是这两个属性因为修改了移动端屏幕内的css像素多少,所以当我们在开发中使用px
这种绝对单位时,手机屏幕具体有多少css像素就与最终的呈现效果息息相关了。如上两个html例子,粉色盒子border
都是1px,但是上面的html例子中因为手机屏幕css大小比较小,1px就比较粗。
总结:width与initial-scale的取值影响了手机屏幕里的css像素多少,决定了项目中绝对单位(px
)的呈现效果。
解读flexible.js
适配方案的<meta>
标签
下面是flexible.js
中的一段源码,总体流程就是获取设备的dpr
,然后设置meta
标签的initial-scale
为1/dpr
:
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) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
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;
}
docEl.setAttribute('data-dpr', dpr);
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
理解上面我们所说的移动端适配方案思想的同学其实应该意识到了,flexible.js
方案做到不同设备等比缩放其实如下几行代码就做到了:
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit();
之所以还要将initial-scale
设置为1/dpr
这个“多此一举”的操作其实是为了解决移动端高倍屏的“1px”问题。
“1px问题”解读
我来说一下我理解的何为“1px问题”:
设计稿上一些线条非常细,就比如设计师的要求就是我们需要一条1px物理像素的线条,那么对于高倍屏,比如dpr=2
(两个物理像素绘制一个dip
像素,可以简单理解为css像素),我们是不是就要写0.5px(我们开发时写的是dip
像素,css像素),但是各种浏览器对于这个极小的px(小于1的px)处理逻辑是不统一的:
- chrome:把小于0.5px的当成0,大于等于0.5px的当作1px
- firefox:会把大于等于0.55px的当作1px
- safiri:把大于等于0.75px的当作1px 进一步在手机上观察iOS的Chrome会画出0.5px的边,而安卓(5.0)原生浏览器是不行的。所以直接设置0.5px不同浏览器的差异比较大。
总之我们达不到设计的要求,换句话说浏览器的差异导致我们没办法在编码时统一1px(物理像素)的呈现效果。
回归<meta>
标签对于initial-scale
的设置问题,举个例子来说比较容易理解:设备dpr=2
,编码开发时我们写的0.5px的绘制效果其实就是1个物理像素进行渲染,这里的0.5因为浏览器的处理逻辑不同就会出现各种不符合预期的情况,那么设置initial-scale=1/2
之后手机屏幕内的css像素就变成了原本的两倍,此时一个css像素就由一个物理像素进行渲染,所以我们想要1px的物理像素,就直接写1px即可,因为各种浏览器对于1px的处理逻辑是统一的,所以就规避了“1px问题”。
如果你明白了上面的思想,那么网上各种对于“1px”的解决方法其实都不难理解了,就比如下面针对dpr=2
的设备的解决方法:
.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;
}
解决方案的核心是什么:就是在开发时,代码里写1px而不是写0.5px,这样就统一了各浏览器的绘制逻辑,然后通过scale
缩放将其缩小至原来的一半。
移动端图片模糊问题
其实只要是理解了上面的思想,图片模糊问题就不难理解了,首先我们要知道我们平时使用的图片大都是位图(png、jpg...),位图是由一个个像素点组成的,每个像素都有具体的颜色,理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。 而在dpr > 1
的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在dpr > 1
的屏幕上就会模糊。
解决方案:
比如我们项目的<meta>
标签中initial-scale=1
,也就是说屏幕内的css像素数量就等于dip
像素数量,也就是一个css像素由dpr
个物理像素来渲染,那么我们就可以在不同dpr
的设备上展示不同分辨率的图片来解决这个问题。
比如:在dpr=2
的屏幕上展示两倍图(@2x)
,在dpr=3
的屏幕上展示三倍图(@3x)
,本质上就是让位图的一个小格子由一个物理像素去渲染从而有最好的渲染效果。
提供一种实施方案:
- srcset 使用
img
标签的srcset
属性,浏览器会自动根据像素密度匹配最佳显示图片:
<img src="conardLi_1x.png"
srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">