๐ JavaScript๋ก ๊ฐ์ ๋๋ ๋ ์ด์์ ๊ณ์ฐ: ์ฑ๋ฅ ์ ํ์ ์ ์ง๋ณด์์ ๋ช
Summary
JavaScript๋ฅผ ์ฌ์ฉํด UI์ ํฌ๊ธฐ์ ์์น๋ฅผ ์ง์ ๊ณ์ฐํ๊ณ ์ ์ฉํ๋ ๊ฒ์ ์ฆ์ ๋ ์ด์์ ์ฌ๊ณ์ฐ์ผ๋ก ์ฑ๋ฅ์ ์ ํ์ํค๊ณ ์ฝ๋ ๋ณต์ก์ฑ์ ๋์ฌ ์ ์ง๋ณด์๋ฅผ ์ด๋ ต๊ฒ ๋ง๋ญ๋๋ค. CSS์ ๋ ์ด์์ ๊ธฐ๋ฅ๊ณผ ํด๋์ค ํ ๊ธ๋ง์ ํ์ฉํ์ฌ ์ฑ
์ ๋ถ๋ฆฌ ์์น์ ์งํค๊ณ , ํ์์ CSS Custom Properties๋ requestAnimationFrame
์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
Why Wrong?
JavaScript๋ฅผ ์ฌ์ฉํ์ฌ UI ์์์ ํฌ๊ธฐ๋ ์์น๋ฅผ ๋์ ์ผ๋ก ๊ณ์ฐํ๊ณ ์ง์ ์คํ์ผ์ ์ ์ฉํ๋ ๊ฒ์ ๋ถํ์ํ ๋ ์ด์์ ์ฌ๊ณ์ฐ(Reflow)๊ณผ ํ๋ฉด ์ฌ๊ทธ๋ฆฌ๊ธฐ(Repaint)๋ฅผ ๋น๋ฒํ๊ฒ ์ ๋ฐํ์ฌ ์น ์ฑ๋ฅ์ ์ฌ๊ฐํ๊ฒ ์ ํ์ํต๋๋ค. ๋ธ๋ผ์ฐ์ ๋ DOM ์์์ ํฌ๊ธฐ๋ ์์น๋ฅผ JavaScript๋ก ๋ณ๊ฒฝํ๊ฑฐ๋ offsetWidth
, offsetHeight
์ ๊ฐ์ ๊ณ์ฐ๋ ์คํ์ผ ๊ฐ์ ์ฝ์ ๋๋ง๋ค ๋ ์ด์์์ ๋ค์ ๊ณ์ฐํ๊ฒ ๋๋ฉฐ, ์ด๋ ํนํ ์คํฌ๋กค ์ด๋ฒคํธ๋ ์ ๋๋ฉ์ด์
๊ณผ ๊ฐ์ด ๋ฐ๋ณต์ ์ธ ์์
์์ ์น๋ช
์ ์ธ ๋ณ๋ชฉ ํ์์ ์ด๋ํฉ๋๋ค. ๋ํ, ์ฝ๋์ ๋ณต์ก์ฑ์ ์ฆ๊ฐ์์ผ UI์ ์๊ฐ์ ์์๊ฐ JavaScript ๋ก์ง์ ์์ด๊ฒ ๋๊ณ , ์ด๋ ์ฝ๋์ ์์ง๋๋ฅผ ๋จ์ด๋จ๋ฆฌ๊ณ HTML, CSS, JavaScript ํ์ผ์ ๋ชจ๋ ํ์ธํด์ผ ํ๋ ์ํฉ์ ๋ง๋ค์ด ์ ์ง๋ณด์ ๋น์ฉ์ ์ฆ๊ฐ์ํต๋๋ค. CSS๋ ๋ ์ด์์ ๊ณ์ฐ๊ณผ ์ต์ ํ์ ํนํ๋์ด ์์ผ๋ฉฐ, GPU ๊ฐ์์ ํ์ฉํ ์ ์๋ ์์ฑ(์: transform
, opacity
)์ ํตํด ๋ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
์ ๊ตฌํํ ์ ์์ต๋๋ค. JavaScript๋ก ๋ ์ด์์์ ๊ฐ์ ํ๋ ๊ฒ์ ์น ํ์ค์ ์ฑ
์ ๋ถ๋ฆฌ ์์น(HTML์ ๊ตฌ์กฐ, CSS๋ ํํ, JavaScript๋ ๋์)์ ์๋ฐฐํ์ฌ ์ฝ๋์ ๋ช
ํ์ฑ์ ํด์น๊ณ ํ์
์ ์ด๋ ต๊ฒ ๋ง๋ค๊ธฐ๋ ํฉ๋๋ค.
How to Fix?
๋๋ถ๋ถ์ ๋ ์ด์์๊ณผ ๊ธฐ๋ณธ์ ์ธ ์คํ์ผ๋ง์ ํญ์ CSS๋ฅผ ํตํด ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. Flexbox, Grid์ ๊ฐ์ ๊ฐ๋ ฅํ CSS ๋ ์ด์์ ์์คํ
์ ํ์ฉํ์ฌ ๋ฐ์ํ ๋ฐ ๋์ ์ธ UI๋ฅผ ์ ์ธ์ ์ผ๋ก ๊ตฌ์ถํ๊ณ , vw
, vh
, rem
, em
๊ณผ ๊ฐ์ ์๋ ๋จ์๋ก ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ์ ์ ์ฐํ๊ฒ ๋์ํฉ๋๋ค. JavaScript๋ UI ์์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ์ฌ CSS ํด๋์ค๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ ๊ฑฐํ๋ ๋ฐฉ์์ผ๋ก ์คํ์ผ์ ์ ์ดํ๋ ๋ฐ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, active
ํด๋์ค๋ฅผ ํ ๊ธํ์ฌ ํน์ UI ์์์ ํ์ ์ฌ๋ถ๋ ์ํ ๋ณํ์ ๋ฐ๋ฅธ ์คํ์ผ ๋ณ๊ฒฝ์ CSS์์ ์ ์ํ๋๋ก ํฉ๋๋ค. ๋์ ์ผ๋ก ๋ณ๊ฒฝํด์ผ ํ ์คํ์ผ ๊ฐ(์: ํ
๋ง ์์)์ด ์๋ค๋ฉด, JavaScript์์ CSS Custom Properties(CSS ๋ณ์)๋ฅผ ์ ์ดํ์ฌ ์คํ์ผ์ ์ผ๊ด์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๋์ผ ์ ์์ต๋๋ค. ์ ๋ง๋ก JavaScript๋ก ์๊ฐ์ ์
๋ฐ์ดํธ๊ฐ ํ์ํ ๊ณ ์ฑ๋ฅ ์ ๋๋ฉ์ด์
์ ๊ฒฝ์ฐ์๋, requestAnimationFrame
์ ์ฌ์ฉํ์ฌ ๋ธ๋ผ์ฐ์ ์ ๋ค์ ๋ฆฌํ์ธํธ ์์ ์ ๋ง์ถฐ ์
๋ฐ์ดํธ๋ฅผ ์์ฝํจ์ผ๋ก์จ ๋ถํ์ํ ๋ ์ด์์ ์ฌ๊ณ์ฐ์ ์ต์ํํ๊ณ ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
์ ๋ณด์ฅํด์ผ ํฉ๋๋ค. ํ์ง๋ง ์ด๋ CSS ์ ๋๋ฉ์ด์
์ผ๋ก ๋์ฒด ๋ถ๊ฐ๋ฅํ ์ํฉ์๋ง ๊ตญํ๋์ด์ผ ํฉ๋๋ค.
Before Code (Bad)
// HTML: <div id="myElement" style="position: absolute;"></div>
const myElement = document.getElementById('myElement');
function updatePositionAndSize() {
// ๋ทฐํฌํธ ๋๋น์ 10%๋งํผ ์ผ์ชฝ์ผ๋ก ์ด๋ (์ฆ์ Reflow ๋ฐ์)
const newLeft = window.innerWidth * 0.1;
myElement.style.left = `${newLeft}px`;
// ๋ทฐํฌํธ ๋์ด์ 20%๋งํผ ์์์ ์๋๋ก ์ด๋ (์ฆ์ Reflow ๋ฐ์)
const newTop = window.innerHeight * 0.2;
myElement.style.top = `${newTop}px`;
// ๋ทฐํฌํธ ๋๋น์ 50%๋งํผ ๋๋น ์ค์ (์ฆ์ Reflow ๋ฐ์)
const newWidth = window.innerWidth * 0.5;
myElement.style.width = `${newWidth}px`;
// ๊ณ์ฐ๋ ์คํ์ผ ๊ฐ ์ฝ๊ธฐ (๊ฐ์ Reflow ๋ฐ์)
console.log('Current offsetWidth:', myElement.offsetWidth);
}
// ์๋์ฐ ํฌ๊ธฐ ๋ณ๊ฒฝ ์๋ง๋ค ์์น ๋ฐ ํฌ๊ธฐ ์ฌ๊ณ์ฐ
window.addEventListener('resize', updatePositionAndSize);
updatePositionAndSize(); // ์ด๊ธฐ ์ค์
After Code (Good)
/* CSS: ๋ ์ด์์ ๋ฐ ๋ฐ์ํ ์คํ์ผ์ CSS๋ก ์ ์ */
#myElement {
width: 50vw; /* ๋ทฐํฌํธ ๋๋น์ 50% */
height: 20vh; /* ๋ทฐํฌํธ ๋์ด์ 20% */
background-color: lightblue;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 50%;
top: 50%;
/* transform์ ๋ ์ด์์์ ์ํฅ์ ์ฃผ์ง ์๊ณ GPU ๊ฐ์์ด ๊ฐ๋ฅํ์ฌ ์ฑ๋ฅ ์ฐ์ */
transform: translate(-50%, -50%);
transition: transform 0.3s ease-out, background-color 0.3s ease-out;
}
/* JavaScript๋ก ํ ๊ธ๋ ์ํ ํด๋์ค */
#myElement.active {
transform: translate(0%, -50%); /* ์ผ์ชฝ์ผ๋ก 50% ์ด๋ */
background-color: coral;
}
/* JavaScript: ์ํ ๋ณํ์ ๋ฐ๋ฅธ ํด๋์ค ์ ์ด */
// HTML: <button id="toggleButton">์์น ๋ฐ ์์ ๋ณ๊ฒฝ</button>
const myElement = document.getElementById('myElement');
const toggleButton = document.getElementById('toggleButton');
toggleButton.addEventListener('click', () => {
myElement.classList.toggle('active');
});