为什么 window.getComputedStyle 调用重新计算样式并重排?



看看这个例子:

const startTime = performance.now();
setTimeout(() => console.log(`taken time: ${performance.now() - startTime}ms`))
for(let i = 0; i < 1000; i++){
const element = document.createElement("div");
document.body.appendChild(element);
element.textContent = `Текст n${i}`;
window.getComputedStyle(document.body);
}

看看另一个:

const startTime = performance.now();
setTimeout(() => console.log(`taken time: ${performance.now() - startTime}ms`))
for(let i = 0; i < 1000; i++){
const element = document.createElement("div");
document.body.appendChild(element);
element.textContent = `Текст n${i}`;
window.getComputedStyle(document.body).width;
}

差异很小:在第一种情况下,我只调用window.getComputedStyle(document.body)而不获取属性,在第二种情况下我使用width属性进行调用。因此,在第一个例子中,我们没有看到重新计算样式和回流,但在第二个例子中我们看到了相反的情况。为什么?

这是因为getComputedStyle(element)实际上返回了一个live对象。

const el = document.querySelector("div");
const style = getComputedStyle(el);
console.log("original color:",  style.color);
el.classList.add("colored");
// we use the same 'style' object as in the first step
console.log("with class color:",  style.color);
.colored {
color: orange;
}
<div>hello</div>

获取此对象不需要执行完全的recalc,只有其getters会强制它。

但现在的浏览器甚至比这更聪明,它们甚至不会触发CSSOM树之外的某些属性的回流,从而影响被检查的CSSTyleDeclaration对象。

例如,在下面的示例中,我们可以看到,从检查器内部元素的CSSTYLEDeclaration中获取fontSize属性将强制回流影响检查器,而在外部获取一个表单则不会,因为与width不同,fontSize属性只受祖先的影响,而不受兄弟姐妹的影响。

function testReflow(func) {
return new Promise( (res, rej) => {
const elem = document.querySelector(".reflow-tester");
// set "intermediary" values
elem.style.opacity = 1;
elem.style.transition = "none";
try { func(elem); } catch(err) { rej(err) }
elem.style.opacity = 0;
elem.style.transition = "opacity 0.01s";
// if the tested func does trigger a reflow
// the transition will start from 1 to 0
// otherwise it won't happen (from 0 to 0)    
elem.addEventListener("transitionstart", (evt) => {
res(true); // let the caller know the result
}, { once: true });
// if the transition didn't start in 100ms, it didn't cause a reflow
setTimeout(() => res(false), 100);
});
}
(async () => {
await new Promise(res=>setTimeout(res, 1000));
let styles;
const gCS_recalc_inner = await testReflow(() => {
return (styles = getComputedStyle(document.querySelector("#inner")));
});
console.log("getComputedStyle inner recalc:", gCS_recalc_inner);
const gCS_inner_prop_recalc = await testReflow(() => {
return styles.fontSize;
});
console.log("getComputedStyle inner getter recalc:", gCS_inner_prop_recalc);
const gCS_recalc_outer = await testReflow(() => {
return (styles = getComputedStyle(document.querySelector("#outer")));
});
console.log("getComputedStyle outer recalc:", gCS_recalc_outer);
const gCS_outer_prop_recalc = await testReflow(() => {
return styles.fontSize;
});
console.log("getComputedStyle outer getter recalc:", gCS_outer_prop_recalc);  

})().catch(console.error);
.reflow-tester {
opacity: 0;
}
.hidden {
display: none;
}
<div class="reflow-tester">Tester<div id="inner"></div></div>
<div id="outer"></div>

在这两种情况下都会触发对width的相同检查,因为width可能会受到兄弟姐妹的影响:

function testReflow(func) {
return new Promise( (res, rej) => {
const elem = document.querySelector(".reflow-tester");
// set "intermediary" values
elem.style.opacity = 1;
elem.style.transition = "none";
try { func(elem); } catch(err) { rej(err) }
elem.style.opacity = 0;
elem.style.transition = "opacity 0.01s";
// if the tested func does trigger a reflow
// the transition will start from 1 to 0
// otherwise it won't happen (from 0 to 0)    
elem.addEventListener("transitionstart", (evt) => {
res(true); // let the caller know the result
}, { once: true });
// if the transition didn't start in 100ms, it didn't cause a reflow
setTimeout(() => res(false), 100);
});
}
(async () => {
await new Promise(res=>setTimeout(res, 1000));
let styles;
const gCS_recalc_inner = await testReflow(() => {
return (styles = getComputedStyle(document.querySelector("#inner")));
});
console.log("getComputedStyle inner recalc:", gCS_recalc_inner);
const gCS_inner_prop_recalc = await testReflow(() => {
return styles.width;
});
console.log("getComputedStyle inner getter recalc:", gCS_inner_prop_recalc);
const gCS_recalc_outer = await testReflow(() => {
return (styles = getComputedStyle(document.querySelector("#outer")));
});
console.log("getComputedStyle outer recalc:", gCS_recalc_outer);
const gCS_outer_prop_recalc = await testReflow(() => {
return styles.width;
});
console.log("getComputedStyle outer getter recalc:", gCS_outer_prop_recalc);  

})().catch(console.error);
.reflow-tester {
opacity: 0;
}
.hidden {
display: none;
}
<div class="reflow-tester">Tester<div id="inner"></div></div>
<div id="outer"></div>

最新更新