为什么 window.getComputedStyle 不调用重新计算样式和重排?
Why doesn't window.getComputedStyle invoke recalculate styles and reflow?
看这个例子:
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)
实际上returns一个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>
获取此对象不需要执行完整的重新计算,只有它的 getters
会强制执行。
但现在的浏览器比这更聪明,它们甚至不会触发影响正在检查的 CSSStyleDeclaration 对象的 CSSOM 树之外的某些属性的重排。
例如,在下面的示例中,我们可以看到从检查器内部元素的 CSSStyleDeclaration 获取 fontSize
属性 将强制重排影响我们的检查器,同时获取一种形式outside 不会,因为与 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>
看这个例子:
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)
实际上returns一个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>
获取此对象不需要执行完整的重新计算,只有它的 getters
会强制执行。
但现在的浏览器比这更聪明,它们甚至不会触发影响正在检查的 CSSStyleDeclaration 对象的 CSSOM 树之外的某些属性的重排。
例如,在下面的示例中,我们可以看到从检查器内部元素的 CSSStyleDeclaration 获取 fontSize
属性 将强制重排影响我们的检查器,同时获取一种形式outside 不会,因为与 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>