2、视口与像素:视口(Viewport)概念、物理像素与逻辑像素、设备像素比(DPR)、CSS像素与视口单位(vw/vh)

好,咱们进入第二个话题。说实话,视口和像素这块,是很多前端新手翻车的重灾区。我自己刚入行那会儿,也在这上面栽过跟头——明明在电脑上看着好好的页面,一放到手机上就变得跟蚂蚁一样小。嗯,今天咱们就把这事彻底捋清楚。

2.1 视口(Viewport)到底是什么?

先问个问题:你打开手机浏览器,看到的那个矩形区域,它有多大?

你可能觉得,手机屏幕多大,它就有多大。但实际不是这样。浏览器里有个「虚拟窗口」,它决定了网页怎么布局、怎么缩放。这个虚拟窗口,就是视口。

我习惯把视口分成两种来理解:

  • 布局视口(Layout Viewport):CSS 布局参考的「画板」宽度。早期手机为了显示 PC 网页,默认设成 980px 左右。
  • 视觉视口(Visual Viewport):你眼睛实际看到的那个窗口。比如你双指缩放时,视觉视口在变,但布局视口不变。

说白了,布局视口是「画布」,视觉视口是「取景框」。两者不一致时,就会出现滚动条或者缩放效果。

关键点:移动端必须加 <meta name="viewport" content="width=device-width, initial-scale=1.0">。不加的话,布局视口默认 980px,你的 375px 设计稿放进去,字就跟芝麻一样小。

我的习惯:每次新建项目,第一件事就是检查这个 meta 标签。曾经有个同事忘了加,上线后用户反馈「字太小看不清」,排查了半天才发现是这行代码漏了。

2.2 物理像素 vs 逻辑像素

这两个概念,我当年学的时候绕了好久。咱们用大白话讲:

  • 物理像素:屏幕面板上真实发光的点。iPhone 14 Pro 的屏幕有 2556×1179 个物理像素点。
  • 逻辑像素:CSS 里你写的 width: 375px 那个 px。它是个抽象单位,跟物理像素没有直接对应关系。

为什么会这样?因为屏幕技术发展了。早期屏幕 1 个逻辑像素 = 1 个物理像素。后来 Retina 屏出来了,同样大小的屏幕塞进了更多物理像素,显示更细腻。但如果你写 CSS 时直接按物理像素来,那 1px 的边框在 Retina 屏上会细到看不见。

所以行业约定:我们写 CSS 时用逻辑像素,浏览器自己换算成物理像素去渲染。

概念 本质 举例
物理像素 屏幕硬件的最小发光单元 iPhone 14 Pro 是 2556×1179
逻辑像素 CSS 里写的 px,与设备无关 设计稿标注的 375px

2.3 设备像素比(DPR)—— 换算的桥梁

物理像素和逻辑像素之间怎么换算?靠的就是 DPR。

DPR = 物理像素 / 逻辑像素

举个例子:

  • iPhone 6/7/8:逻辑宽度 375px,物理宽度 750px,DPR = 2
  • iPhone 14 Pro Max:逻辑宽度 430px,物理宽度 1290px,DPR = 3

你写 width: 100px,在 DPR=2 的屏幕上,实际用了 200 个物理像素去渲染。这就是为什么 Retina 屏上文字更清晰——同样的 CSS 尺寸,用了更多物理点来描绘细节。

我曾经踩过的坑:做 1px 边框时,直接写 border: 1px solid #000,在 DPR=3 的屏幕上看起来太粗。后来用 transform: scale(0.333) 或者 0.5px 方案才解决。记住:1px CSS 不等于 1px 物理线宽。

2.4 CSS 像素与视口单位(vw/vh)

CSS 像素就是我们一直在说的逻辑像素。但除了 px,还有两个特别有用的单位:vwvh

  • 1vw = 视口宽度的 1%
  • 1vh = 视口高度的 1%

举个例子:如果视口宽度是 375px,那么 width: 50vw 就等于 187.5px。这个值是实时计算的——你旋转手机、改变窗口大小,它都会跟着变。

我个人特别喜欢用 vw 做字体大小。比如:

/* 字体随视口宽度等比缩放 */
h1 {
  font-size: 4vw;  /* 375px 宽度下 = 15px,750px 宽度下 = 30px */
}

/* 全屏容器 */
.full-screen {
  width: 100vw;
  height: 100vh;
}

避坑指南:100vh 在移动端 Safari 上有个经典问题——地址栏会占用一部分高度,导致 100vh 的内容被地址栏挡住。我曾经因为这个被测试妹子追着改 bug。解决方案是用 100dvh(动态视口高度)或者用 JS 动态计算。

2.5 实际项目中的组合用法

说了这么多理论,咱们看个真实场景。假设设计稿是 375px 宽,你要做一个自适应布局:

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    /* 用 vw 做基准,配合 calc 做精细控制 */
    .container {
      width: 92vw;           /* 留出左右边距 */
      max-width: 1200px;     /* 大屏限制最大宽度 */
      margin: 0 auto;
      padding: 2vw;
    }

    .card {
      width: calc(50vw - 4vw);  /* 两列布局,减去间距 */
      margin: 1vw;
      font-size: calc(14px + 0.5vw);  /* 基础字号 + 动态增量 */
    }

    /* 处理高 DPR 屏幕的 1px 边框 */
    .border-1px {
      position: relative;
    }
    .border-1px::after {
      content: '';
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      height: 1px;
      background: #ddd;
      transform: scaleY(0.5);  /* DPR=2 时缩放到物理 1px */
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="card">卡片 1</div>
    <div class="card">卡片 2</div>
  </div>
</body>
</html>

这段代码里,我用了 vw 做整体布局,calc 做精细调整,还处理了 1px 边框问题。你想想看,如果不用 vw,而是写死 px,那在不同宽度的手机上,卡片间距和字体大小就会忽大忽小,用户体验很差。

2.6 总结一下

视口和像素这块,核心就三件事:

  1. 加 meta viewport 标签——这是移动端适配的起点,忘了它后面全白搭。
  2. 理解 DPR 的存在——写 CSS 时用逻辑像素,但要知道物理像素的存在,尤其是处理 1px 和图片清晰度时。
  3. 善用 vw/vh——它们让响应式布局变得简单,但要注意 Safari 的 100vh 陷阱。

我记得刚带团队时,有个新人问我:「为什么我写的 rem 方案在有些手机上不准?」我让他查了 DPR 和视口设置,结果发现他忘了加 meta 标签。嗯,基础的东西往往最容易忽略。希望今天的内容能帮你少走这些弯路。


公众号:蓝海资料掘金营,微信 deep3321