最近在出一个前端的体系课程,里面的内容非常详细,如果你感兴趣,可以加我 v 进行联系 yunmz777
:

浪费你几秒钟时间,内容正式开始
要想知道什么时候改用这些,我们首先要这段浏览器到底是怎么拉图的。
浏览器加载图片的原理
<img>
元素 <img>
插入 DOM 后,浏览器会立即根据关键渲染路径发起请求,并可能结合预解析或预连接等优化策略。它会依据是否在视口中、是否启用懒加载、优先级提示以及 srcset/sizes
的配置,自动选择合适的资源分辨率与下载优先级。
同时,<img>
还具备语义和可访问性支持,alt
属性能为视觉障碍用户提供说明,并能被辅助技术读取。这使得 <img>
在性能和可访问性上都具有默认优化。
可以使用 alt 提供代替文本,便于无障碍支持:
html
<img src="cat.jpg" alt="一只正在睡觉的猫" />
可以结合 srcset
和 sizes
,让浏览器在不同的屏幕和视口条件下自动选择最合适的图片资源,从而兼顾清晰度与性能:
html
<img
src="cat-800.jpg"
srcset="cat-400.jpg 400w, cat-800.jpg 800w, cat-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 600px"
alt="一只正在睡觉的猫"
/>
可以通过 loading="lazy"
属性,让非首屏图片在滚动到视口时再加载,从而减少初始请求数、优化页面性能:
html
<img src="cat.jpg" alt="一只正在睡觉的猫" loading="lazy" />
可以通过 decoding="async"
让图片解码在空闲时异步进行,避免阻塞渲染、提升页面交互流畅度:
html
<img src="cat.jpg" alt="一只正在睡觉的猫" decoding="async" />
对于首屏关键图,使用 fetchpriority="high"
(可配合 loading="eager"
)提示浏览器尽快抓取,降低 LCP:
html
<img
src="hero.jpg"
alt="首页横幅"
width="1200"
height="600"
loading="eager"
decoding="async"
fetchpriority="high"
/>
对于非首屏次要图,使用 fetchpriority="low"
+ loading="lazy"
减少首屏带宽竞争与请求压力:
html
<img
src="gallery-1.jpg"
alt="相册缩略图"
width="400"
height="300"
loading="lazy"
decoding="async"
fetchpriority="low"
/>
为消除布局偏移(CLS),显式声明固有尺寸 width/height
或使用 style="aspect-ratio"
来预留占位:
html
<img
src="cat.jpg"
alt="一只正在睡觉的猫"
width="800"
height="600"
decoding="async"
loading="lazy"
/>
<!-- 或者 -->
<img
src="cat.jpg"
alt="一只正在睡觉的猫"
style="aspect-ratio: 4 / 3; width: 100%; height: auto;"
decoding="async"
loading="lazy"
/>
用 <picture>
提供现代格式回退,并在各 source
与回退的 <img>
上同时配置响应式 srcset/sizes
,让浏览器按视口选择最佳资源:
html
<picture>
<source
type="image/avif"
srcset="cat-400.avif 400w, cat-800.avif 800w, cat-1600.avif 1600w"
sizes="(max-width: 600px) 100vw, 600px"
/>
<source
type="image/webp"
srcset="cat-400.webp 400w, cat-800.webp 800w, cat-1600.webp 1600w"
sizes="(max-width: 600px) 100vw, 600px"
/>
<img
src="cat-800.jpg"
srcset="cat-400.jpg 400w, cat-800.jpg 800w, cat-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 600px"
width="800"
height="600"
alt="一只正在睡觉的猫"
loading="lazy"
decoding="async"
fetchpriority="auto"
/>
</picture>
在需要延迟解码但又希望尽快显示占位时,可以结合原生懒加载与 CSS 背景占位(或渐进式占位图 LQIP):
html
<img
src="cat-800.jpg"
alt="一只正在睡觉的猫"
width="800"
height="600"
loading="lazy"
decoding="async"
style="background: #f2f3f5;"
/>
若图片用于装饰且不承载语义,请将 alt
设为空字符串,避免冗余信息打扰读屏器;若图片可点击,应为可操作元素提供可聚焦与可见文本替代:
html
<!-- 装饰性图片:alt 为空,读屏器跳过 -->
<img src="decor-line.png" alt="" role="presentation" />
<!-- 可点击图片:给链接提供明确文本 -->
<a href="/cats">
<img src="cat-card.jpg" alt="查看全部猫咪照片" width="600" height="400" />
</a>
浏览器在遇到 <img>
时会立即根据关键渲染路径调度资源请求,并结合 srcset/sizes
、loading
、decoding
、fetchpriority
等属性动态决定下载时机、分辨率与优先级,从而在保证性能的同时兼顾可访问性。
new Image()
通过 new Image()
创建的是一个独立的 HTMLImageElement
,它默认并不会触发网络请求,只有在设置了 src
或 srcset
时才会开始加载。由于此时该元素并未插入 DOM,浏览器通常会将其判定为较低的网络优先级,从而避免和关键渲染路径上的资源争抢带宽。
开发者可以利用这一特性,将图片在后台预加载,并在 onload
回调或 decode()
解析完成后再插入 DOM,这样可以减少首次渲染时的布局抖动和解码阻塞。同时,new Image()
加载的资源仍会进入缓存池,后续 <img>
或 CSS 使用同一资源时能直接复用,提升整体性能。
示例:
html
<script>
// 创建并预加载图片
const img = new Image();
img.src = "cat-800.jpg";
// 等待加载完成再插入 DOM,避免解码阻塞
img.onload = () => {
document.body.appendChild(img);
};
// 或使用 decode(),确保解码完成再插入
const preload = new Image();
preload.src = "hero.jpg";
preload.decode().then(() => {
document.body.appendChild(preload);
});
</script>
new Image()
在设置 src
或 srcset
时才触发请求,因未挂载到 DOM 通常被赋予较低网络优先级,适合用于预加载;开发者可在 onload
或 decode()
完成后再插入 DOM,以减少渲染与解码抖动,并复用缓存。
什么时候用 new Image() 更合适?
首先第一个就是我们需要精确控制何时加载的时候,我们可以将图片的下载放到首屏稳定之后或空闲时间,让关键资源先走:
js
const onIdle = (fn) =>
window.requestIdleCallback ? requestIdleCallback(fn) : setTimeout(fn, 0);
onIdle(() => {
const img = new Image();
img.src = "/next-section/banner.avif";
});
这种使用下一屏、下一步交互、轮播图的下一章、路由借还即即将展示的图。
第二个就是先加载并完成解码,再插入 DOM:
js
function preload(src, { crossOrigin, srcset, sizes } = {}) {
return new Promise((resolve, reject) => {
const img = new Image();
if (crossOrigin) img.crossOrigin = crossOrigin; // canvas 用图需先设
if (srcset) img.srcset = srcset;
if (sizes) img.sizes = sizes;
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
async function mountDecoded(container, { src, width, height, ...rest }) {
// 提前占位,避免 CLS
if (width && height) container.style.aspectRatio = `${width} / ${height}`;
const img = await preload(src, rest);
// 等待位图解码,插入更丝滑
if (img.decode) await img.decode();
container.replaceChildren(img);
}
它的要点是写死 width/height
或 aspect-ratio
,先占出布局位。再通过 await img.decode()
能显著降低插入瞬间主线程卡顿
第三个就是缓存预热,后续几乎能实现秒显,具体操作就是把相同 URL 再赋给真正的 <img>
或 CSS background-image
,大概率命中内存/磁盘缓存,甚至复用已解码位图:
js
const imageWarmCache = new Map();
async function warm(src, options) {
if (!imageWarmCache.has(src)) {
imageWarmCache.set(
src,
preload(src, options).catch(() => null)
);
}
return imageWarmCache.get(src);
}
async function show(selector, src) {
await warm(src);
const el = document.querySelector(selector);
if (el) el.src = src; // 命中缓存 → 快速呈现
}
适用于列表到详情、缩略图到大图,以及分页/轮播的"下一张"等场景:提前预加载可在用户触发后几乎瞬时呈现内容,显著减少等待与卡顿。
如果想要处理更细致的错误处理与降级,那这个是必选项了,因为它能提前拿到 error 并做兜底,避免"破图"闪烁:
js
async function safeInsert(container, primary, fallback) {
try {
const img = await preload(primary);
if (img.decode) await img.decode();
container.replaceChildren(img);
} catch {
const alt = document.createElement("img");
alt.src = fallback;
alt.alt = "fallback";
container.replaceChildren(alt);
}
}
什么时候优先用 <img>
(甚至只用 <img>
)?
当图片是首屏关键内容(LCP)、需要语义/可访问性或不希望依赖 JS 参与渲染时,应优先直接在 DOM 里写 <img>
。这样浏览器能最早、最高优先级地调度下载与解码,你也能用语义属性(如 alt
)服务 SEO 与无障碍。
可以使用 alt
提供替代文本,便于无障碍支持;width/height
(或 aspect-ratio
)用于占位防 CLS:
html
<img src="/cats/cat.jpg" alt="一只正在睡觉的猫" width="1200" height="800" />
<!-- 或用 CSS 占位(不如写 width/height 稳妥) -->
<style>
.hero {
aspect-ratio: 3 / 2;
}
</style>
<img class="hero" src="/cats/cat.jpg" alt="一只正在睡觉的猫" />
结合 srcset
和 sizes
,让浏览器在不同屏幕和视口条件下自动选择最合适的图片资源,获得清晰度与性能的双赢:
html
<img
src="/cats/cat-800.jpg"
srcset="
/cats/cat-400.jpg 400w,
/cats/cat-800.jpg 800w,
/cats/cat-1600.jpg 1600w
"
sizes="(max-width: 600px) 100vw, 600px"
alt="一只正在睡觉的猫"
width="800"
height="533"
/>
对非首屏图片,直接使用原生懒加载 loading="lazy"
,让图片在接近视口时再下载,减少首屏请求、优化总体性能:
html
<img
src="/gallery/thumb-1.jpg"
alt="画廊缩略图 1"
loading="lazy"
width="400"
height="300"
/>
将解码放到空闲时异步进行,避免解码阻塞渲染;对大量非关键图片特别有用(LCP 图建议保持默认 auto
,让浏览器自行权衡):
html
<img
src="/gallery/photo-1.jpg"
alt="旅行照片 1"
decoding="async"
loading="lazy"
width="1200"
height="800"
/>
对于首屏关键图(LCP),请直接放 DOM,并提升优先级;可选地配合 <link rel="preload">
进一步提前抓取(注意:使用响应式资源时要带上 imagesrcset
和 imagesizes
,避免重复下载):
html
<!-- 1) 预加载(可选但推荐) -->
<link
rel="preload"
as="image"
href="/hero/hero-1600.avif"
imagesrcset="/hero/hero-800.avif 800w, /hero/hero-1200.avif 1200w, /hero/hero-1600.avif 1600w"
imagesizes="(max-width: 768px) 92vw, 1200px"
/>
<!-- 2) 关键图本体:高优先级 + 明确尺寸 -->
<img
src="/hero/hero-1600.avif"
srcset="
/hero/hero-800.avif 800w,
/hero/hero-1200.avif 1200w,
/hero/hero-1600.avif 1600w
"
sizes="(max-width: 768px) 92vw, 1200px"
alt="首页横幅"
fetchpriority="high"
loading="eager"
width="1200"
height="675"
/>
若需要格式回退/美术分发(art direction),用 <picture>
在仍然保留 <img>
语义的前提下提供多格式/多裁剪版本(依然建议写宽高占位与高优先级)。这同样适合 LCP 图:
html
<picture>
<!-- 大屏用横裁剪,小屏用竖裁剪,仅示意 -->
<source
type="image/avif"
media="(min-width: 1024px)"
srcset="/hero/desktop-1200.avif 1200w, /hero/desktop-1600.avif 1600w"
sizes="(min-width: 1280px) 1200px, 90vw"
/>
<source
type="image/avif"
media="(max-width: 1023px)"
srcset="/hero/mobile-640.avif 640w, /hero/mobile-960.avif 960w"
sizes="(max-width: 600px) 92vw, 600px"
/>
<!-- Fallback 到 JPEG/WebP -->
<img
src="/hero/desktop-1200.jpg"
alt="首页横幅"
fetchpriority="high"
loading="eager"
width="1200"
height="675"
/>
</picture>
当你需要兼顾 SEO 与可访问性时,还可以用 figure/figcaption
提供更完整的语义包装;注意 alt
是给屏幕阅读器与加载失败的替代文本,figcaption
是对所有用户可见的说明:
html
<figure>
<img
src="/article/cover-1200.jpg"
alt="日落下的海岸线"
width="1200"
height="800"
/>
<figcaption>日落下的海岸线(拍摄于青岛石老人)</figcaption>
</figure>
最后,为防止 JS 失败时"白板",关键内容务必用纯 <img>
就能渲染;如果你的页面有 JS 主导的图片渲染路径,建议加一个 noscript
兜底:
html
<noscript>
<img
src="/hero/hero-1200.jpg"
alt="首页横幅(JS 关闭时的兜底)"
width="1200"
height="675"
/>
</noscript>
首屏/LCP 与需要语义的内容图像,优先直接写 <img>
,并通过 width/height
(或 aspect-ratio
)预留空间避免 CLS,必要时加 fetchpriority="high"
与(可选)<link rel="preload">
。
非首屏图片用 loading="lazy"
与 decoding="async"
降低主线程压力。响应式场景务必提供 srcset/sizes
(或 <picture>
)以减少过大资源浪费。无论是否配合 JS 优化,只要是关键渲染路径,都不要把显示逻辑完全寄托在脚本执行之上。
总结
在浏览器的关键渲染路径里,<img>
一旦进入 DOM 就会立刻参与调度并发起下载,天然具备语义与可访问性(如 alt
被读屏器识别),还能配合 srcset/sizes
与 fetchpriority
让浏览器按视口与优先级选择最合适的资源;
而 new Image()
只有在你设置 src/srcset
时才开始请求,且因不在 DOM 通常被赋予更低网络优先级,便于把下载推迟到空闲/交互之后,并可在 await img.decode()
后再插入以避免插入卡顿,同时起到缓存预热的作用。基于此,首屏/LCP 级的关键可见图像请优先使用 <img>
,把 new Image()
留给"下一步就会出现"的图片、背景图或需要精细调度的预加载场景(切勿把 LCP 图放到 new Image()
里)。
具体什么时候该用什么,可以参考下表:
场景 | 推荐 | 写法要点 | 避坑提醒 |
---|---|---|---|
首屏关键图(LCP) | <img> |
写明 width/height 或 aspect-ratio ;fetchpriority="high" ;必要时 <link rel="preload"> ;配 srcset/sizes 或 <picture> |
不要用 new Image() 预拉 LCP,JS 后才请求会拖慢 LCP |
非首屏/数量多 | <img> |
loading="lazy" decoding="async" ;同样写尺寸占位;提供 srcset/sizes |
懒加载也要占位,避免插入时 CLS |
需要语义/SEO | <img> |
正确的 alt ;可用 figure/figcaption |
不要把语义图像仅放在 JS 里 |
精确控制时机/顺序 | new Image() |
空闲/交互后设置 src ;插入前 await img.decode() ;先占位 |
预拉过多会占带宽、影响当前体验 |
列表 → 详情 / 缩略图 → 大图 / 轮播"下一张" | new Image() |
预热缓存实现"几乎秒显";必要时也配置 srcset/sizes |
预拉与展示复用同一 URL,否则二次下载 |
背景图/Canvas | new Image() |
不能用 <img> 时先拉后用;Canvas 需像素操作 |
在设 src 之前设置 img.crossOrigin ,避免画布污染 |
JS 可能失败/延迟 | <img> |
关键渲染路径可直接渲染;可加 noscript 兜底 |
关键图像不要依赖 JS 才能出现 |
低网速/带宽敏感 | 二者结合 | 关键图走 <img> ;次要图懒加载;近未来图用 new Image() 热身并控制数量 |
管理预拉缓存并按需释放 Image 引用,避免内存上涨 |