<details>内图片不会因折叠而懒加载,浏览器解析到src即发起请求;需用data-src配合toggle事件延迟赋值,并用setTimeout(0)确保读取正确open状态,避免重复加载和scrollHeight计算错误。

details标签里图片不懒加载,是浏览器默认行为
很多人以为<details>折叠时内部<img>会自动延迟加载,实际完全不是——只要<img src="xxx">出现在初始HTML中,浏览器解析到就会发起请求,哪怕父级<details open="false">或style="display:none"。这不是bug,是规范行为。
真正能控制加载时机的,只有loading="lazy"(对视口外图片有效)或手动接管src赋值。而<details>的toggle事件,正是触发“只在展开时加载”的唯一可靠入口。
用data-src + toggle事件手动赋值src
核心思路:把图片的src挪到data-src,初始不设src;监听toggle,仅当details.open === true时才把data-src写回src。
- 必须检查
img.src是否已存在,避免重复赋值触发多次请求 -
<details>要加class或id便于定位,比如<details class="lazy-details"> - 不要用
click或change事件替代toggle——change根本不派发,click无法区分点击位置,且不响应键盘操作
示例代码:
立即学习“前端免费学习笔记(深入)”;
const details = document.querySelector('.lazy-details');
const img = details.querySelector('img');
details.addEventListener('toggle', () => {
if (details.open && !img.src) {
img.src = img.dataset.src;
}
});
toggle事件里读open状态要延迟
toggle事件回调中直接读details.open,拿到的是变化前的值。比如用户点击收起,回调里details.open还是true,直到回调执行完才变false。这会导致刚展开就插入内容、但open读出来仍是false的逻辑错乱。
稳妥做法是用setTimeout(0)延迟读取:
details.addEventListener('toggle', () => {
setTimeout(() => {
if (details.open) {
// 此时才是真正的展开后状态
loadContent();
}
}, 0);
});
如果只是简单切换图标或显隐,直接读open通常没问题;但涉及DOM插入、资源加载、动画起始判断,必须延迟。
动态内容防重复渲染
常见错误是在toggle里每次展开都fetch接口并innerHTML写入——用户反复展开/收起,就会反复请求、反复渲染,既浪费带宽又可能造成状态残留。
- 首次展开后,给
<details>加个标记,比如data-loaded="true" - 后续进入
toggle时先检查details.hasAttribute('data-loaded'),有则跳过加载 - 如果内容需刷新,应提供单独的“刷新”按钮,而非绑定在展开动作上
最麻烦的是图片加载时机影响scrollHeight计算——动画中若<img>还没加载完,scrollHeight会偏小,导致max-height过渡卡住。这时得等img.onload后再调一次高度重设,或者干脆放弃高度动画,改用opacity和transform过渡更稳。



















