这篇文章进入了性能优化的另一个大头------ "图片性能优化(Image Performance)" 。
图片通常是网页上体积最大、最占带宽的资源。文章的核心思想是: "在不牺牲肉眼可见画质的前提下,通过各种手段让浏览器下载最少的图片字节数" 。
主要内容可以概括为以下四个核心技术维度:
1. 响应式图片与尺寸优化(Dimensions & Density)
-
核心痛点 :由于手机和电脑存在设备像素比(DPR,如 Retina 屏 DPR=2 或 3) ,且不同设备的屏幕宽度不同,不能给所有设备都发同一张高清大图(太浪费流量),也不能发小图(高分屏上会糊)。
-
解决方案:
srcset与sizes属性 :用srcset提供不同分辨率/宽度的图片候选(如1000w表示固有宽度 1000 像素),用sizes告诉浏览器图片在不同视口下的布局宽度。让浏览器自己计算并下载最合适的那张。<picture>元素 :比<img>更高级。可以用<source media="...">强行命令浏览器在特定屏幕尺寸(或高 DPR)下切换不同尺寸、甚至不同裁剪方向的图片。
2. 现代文件格式与压缩(Formats & Compression)
-
下一代格式 :大力推荐 WebP 和 AVIF。它们比传统的 JPEG 和 PNG 压缩率更高(AVIF 在同画质下能比 JPEG 节省 50% 以上体积)。
-
格式降级方案 :利用
<picture>的标签嵌套特性,优先让浏览器读 AVIF,不认识就读 WebP,再不认识就降级到传统的 JPEG。 -
有损 vs 无损:
- 有损压缩(JPEG、WebP、AVIF):适合色彩丰富的照片,通过舍弃肉眼不易察觉的色彩细节换取巨幅体积暴减。
- 无损压缩(PNG、GIF、WebP、AVIF):适合线条艺术、带文字的图片,压缩但不丢失任何像素数据。
3. 服务端动态投喂(Content Negotiation)
- Accept 请求头 :浏览器在请求图片时,会在 HTTP 请求头带上
Accept: image/avif,image/webp,...。 - 自动优化 :大厂的高级做法是在后端服务器或图片 CDN 上做拦截 。如果发现用户的浏览器支持 AVIF,即便 HTML 里写的是
src="pic.jpg",后端也会自动返回pic.avif格式。这样可以保持前端 HTML 代码的简洁。
4. 浏览器渲染与加载控制(Loading & Decoding)
loading="lazy"(延迟加载) :极度重要。加上它之后,不在首屏(视口外)的图片先不下载,等用户滚动到附近时才加载。瞬间省下大量首屏带宽。decoding="async"(异步解码) :告诉浏览器在后台默默去把图片的数据流解码成像素,不要卡住主线程渲染其他文字或布局,对高分辨率大图很有用。
总结: 前一章讲的是用"资源提示"来调控下载时机 ,而这一章讲的是用"自适应技术"来控制资源本身的大小 。大厂在做图片优化时,通常是 loading="lazy"(非首屏延迟) + 现代格式(AVIF/WebP) + 响应式尺寸(srcset)组合出击。
提问
DPR这个是什么?为什么不同屏幕同一张图要求不一样,怎么计算这种不一样找到合适的点
这又是前端性能优化和移动端适配中一个极其核心的考点。大厂面试官非常喜欢用它来测试你对"移动端物理像素和逻辑像素"的底层理解。
我们把 DPR 拆开来聊透,看看它到底是怎么折腾前端开发者的,又是怎么计算和优化的。
一、 DPR(Device Pixel Ratio)到底是什么?
DPR 的全称是 设备像素比。它的公式非常简单:
DPR=逻辑像素(CSS 像素)物理像素(设备像素)
- 逻辑像素(CSS 像素) :就是我们在写代码时写的
width: 300px。它是浏览器里的一种"虚拟单位"。 - 物理像素:手机屏幕上由多少个真正的"发光小微粒(LED点)"组成。
💡 为什么会有这个东西?
在 2010 年以前(iPhone 3 时代),1px 的 CSS 逻辑像素 = 1 个物理发光点,此时 DPR=1。 但 2010 年 iPhone 4 带来了 Retina(视网膜)屏幕 ,它的物理尺寸和 iPhone 3 一样大,但苹果塞进去了两倍的物理发光点。
如果这时候继续让 1px CSS 像素对应 1 个物理发光点,整个网页在 iPhone 4 上看起来就会直接缩小到原来的一半。
所以苹果发明了网格映射:在 iPhone 4 上,让 1px 的 CSS 逻辑像素,横向对应 2 个物理像素,纵向对应 2 个物理像素。也就是说,用 2×2=4 个物理像素去渲染 1 个 CSS 像素。 这样网页大小没变,但是文字和线条变得极度清晰、没有锯齿。这就叫 DPR=2。
二、 为什么不同屏幕同一张图要求不一样?
这就是"为什么图片会糊"或者"为什么浪费流量"的根源。
图片是由一格一格的"像素位(固有尺寸)"组成的。 假设你在页面上写了一个 width: 500px; height: 500px; 的容器。
-
在 DPR = 1 的普通电脑屏幕上 : 容器实际需要 500x500 个物理像素。你给他一张 500x500 像素的图片,一比一完美对应,画面很清晰。
-
在 DPR = 2 的高端手机屏幕上 : 因为 DPR=2,这个 500x500 的 CSS 容器在手机屏幕上实际上占用了 1000x1000 个物理发光点。
- 如果你还给它 500x500 的图 :图片只有 25 万个像素信息,却要填满 100 万个发光点,浏览器只能把图片拉伸放大,图片立刻就变糊了。
- 如果你给它 1000x1000 的图:图片像素和物理发光点一比一对应,图片在手机上就非常锐利、清晰。
-
在 DPR = 3 的超级旗舰机上 : 这个容器实际需要 1500x1500 个物理发光点。你得给它 1500x1500px 的图才最完美。
⚠️ 矛盾点来了:
那我直接所有人统一发 1500x1500px 的超清大图不就行了? 不行! 如果 DPR = 1 的老电脑用户下载了 1500x1500px 的图,他的屏幕只能显示 500x500,浏览器会做下采样(压缩),用户肉眼完全看不出区别,却白白多下载了 9 倍的文件体积(面积比是 3×3=9),浪费了大厂昂贵的 CDN 流量和用户的手机话费。
三、 怎么计算和找到这个"合适的点"?
大厂的核心优化思路是:自适应投喂 ------ 算好设备的 DPR 和容器宽度,它要多少,我给多少。
业界有两种主流的落地计算和实现方案:
方案 1:现代 HTML5 标签自动计算法(Srcset + Sizes)
让浏览器自己去算。你只要把各种尺寸的图片备好,在代码里给提示:
HTML
ini
<img
src="img-500.jpg"
sizes="(min-width: 768px) 500px, 100vw"
srcset="img-500.jpg 500w, img-1000.jpg 1000w, img-1500.jpg 1500w"
alt="示例"
>
-
浏览器的计算逻辑:
- 某个用户用的是 iPhone 13,屏幕宽 390px,DPR=3。
- 浏览器看
sizes:此时视口小于 768px,图片宽度为100vw(即占满屏幕 390px)。 - 浏览器开始计算物理像素需求:390px×3(DPR)=1170 物理像素。
- 浏览器看
srcset候选:有 500w、1000w、1500w。1000w有点不够(会糊),最接近且能满足的是1500w。 - 结果:浏览器自动决定下载
img-1500.jpg。
方案 2:图片 CDN 动态裁剪法(大厂最常用)
大厂(如阿里、腾讯、字节)都有专门的图片 CDN 服务。前端可以通过 JS 算出当前设备的 DPR,然后拼接 URL 参数去向后端要图。
JavaScript
ini
// 1. 获取当前设备的 DPR
const dpr = window.devicePixelRatio || 1;
// 2. 假设在当前布局下,图片容器的 CSS 宽度是 300px
const containerWidth = 300;
// 3. 计算出最完美的物理图片宽度
const targetWidth = containerWidth * dpr; // 如果 dpr 是 2,就是 600
// 4. 动态拼接阿里云/腾讯云等 CDN 裁剪参数
const imageUrl = `https://cdn.example.com/product.jpg?x-oss-process=image/resize,w_${targetWidth}`;
// 5. 把这个 URL 赋给 img 标签
myImage.src = imageUrl;
💡 追问:人眼是有极限的(大厂的省钱绝招)
面试官可能会继续深挖:"既然 DPR = 3 需要 3 倍图,那我们需要无限追求高清吗?"
- 大厂的标准答案 :不需要,DPR = 2 是性价比最高的点。
- 科学研究表明,在正常观看手机的距离下,人眼几乎无法分辨 DPR = 2 和 DPR = 3 的画质区别。
- 因此,大厂为了节省昂贵的带宽成本,通常在计算时会做一个截断优化:如果是 DPR >= 2 的设备,一律只返回 2 倍尺寸的图片(把目标上限卡在 DPR=2)。这样既保证了画面绝不糊,又砍掉了 3 倍大图带来的巨大流量开销。