๊ธฐ๋ณธ์ ์ผ๋ก๋ ๋ซ์๋จ๋ค๊ฐ ๋๋ฅด๋ฉด ์๋๋ก ์ด๋ฆฌ๊ณ ์ถ๊ฐ ์ ๋ณด๋ฅผ ๋ณผ ์ ์๋ ๊ธฐ๋ฅ์ ๊ฐ์ง ์ค๋ธ์ ํธ๋ฅผ ๋ง๋ค์ด๋ดค์ต๋๋ค.
์์ ์ HTML5
๋ฅผ ๊ณต๋ถํ๋ค๊ฐ ๋ณด๊ธฐ๋ง ํ๊ณ ์ง๋๊ฐ๋ <details>
์ <summary>
๊ฐ ์๊ฐ๋์ ๊ด๋ จํด์ ๊ตฌ๊ธ๋งํด๋ณด๋ฉด์ ๋ฐฉ๋ฒ์ ์ฐพ๊ณ ์ ์ฉํด๋ดค์ต๋๋ค.
<details>
์ฌ์ฉ๋ฐ๋ก ์๋ gif๋ ์คํ์ผ๋ง ์ฒ๋ฆฌ๋ง ํ ๊ฒฐ๊ณผ๋ฌผ์ธ๋ฐ ์๊ฐ๋ณด๋ค ๋๋ฌด ์ ์ ์ธ ๋๋์ด ๋ค์์ต๋๋ค.
transition
์ ์ ์ฉํ ๊ฒ์ฒ๋ผ ๋๋ฅด๋ฉด ์์ํ ์ด๋ฆฌ๋ ๊ฒ์ ์ํ์ง๋ง ์๊ฐ์ฒ๋ผ ์๋ํ์ง ์์์ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ์๋ดค์ต๋๋ค.
๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฐพ๋ค๊ฐ ResizeObserver
์ ์กด์ฌ๋ฅผ ์๊ฒ ๋์์ต๋๋ค.
ResizeObserver
๋ ํน์ ์๋ฆฌ๋จผํธ์ ํฌ๊ธฐ ๋ณํ๋ฅผ ๊ฐ์งํฉ๋๋ค.
ํด๋น ๊ธฐ๋ฅ๊ณผ <details>
๋ฅผ ํด๋ฆญํ๋ฉด height
๊ฐ ๋์ด๋๋ ๊ฒ์ ์ด์ฉํด์ transition
์ ์ ์ฉํ ๊ฒ์ฒ๋ผ ํจ๊ณผ๋ฅผ ์ค ์ ์์ต๋๋ค.
const RO = new ResizeObserver(callback)
RO.observe(element)
/*
* callback์ ์ธ์๋ ResizeObserverEntry[]๊ฐ์ด ๋ค์ด์ต๋๋ค.
* ๊ด์ฐฐํ๋ ๋ชจ๋ ์๋ฆฌ๋จผํธ์ ํน์ ์ ๋ณด๋ฅผ ๋ด์ ResizeObserverEntry์ ๋ฐฐ์ด์
๋๋ค.
*/
ResizeObserverEntry
์ ์์ฑ๋ค ( ์์ธํ ์ ๋ณด๋ mdn )target
: ๊ด์ฐฐ ๋์์
๋๋ค.contentRect
: ๊ด์ฐฐ ๋์์ ์ฌ๊ฐํ ์ ๋ณด์
๋๋ค.์๋์ ์์๋ฅผ ๋ณด๋ฉด์ ์ด๋ป๊ฒ ์ ์ฉํ๋์ง ์ค๋ช ํ๊ฒ ์ต๋๋ค. ( ์ฐธ๊ณ ํ ํฌ์คํธ )
export const setDetailsHeight = (wrapper: HTMLElement) => {
// ํน์ <details>์ ๋ซํ๊ณผ ์ด๋ฆผ์ ๊ฐ์ ๋ฏธ๋ฆฌ ๊ธฐ๋กํจ ( --expanded์ --collapsed์ ๊ธฐ๋ก )
// width๋ฅผ dataset์ ๋ฃ์ด๋๋ ์ด์ ๋ ์ฒ์ ์คํ์ธ์ง ์๋์ง ์ฆ, ๋ซํ๊ณผ ์ด๋ฆผ์ ํฌ๊ธฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ธฐ๋กํ๋ ๊ฒ์ธ์ง ํ๋จ์ ์ํด์
const setHeight = (detail: HTMLDetailsElement, open = false) => {
detail.open = open;
const { width, height } = detail.getBoundingClientRect();
detail.dataset.width = width + "";
detail.style.setProperty(
open ? `--expanded` : `--collapsed`,
`${height}px`
);
};
// ResizeObserver ๊ฐ์ฒด ์์ฑ
const RO = new ResizeObserver((entries) =>
entries.forEach((entry) => {
const detail = entry.target as HTMLDetailsElement;
const width = detail.dataset.width ? +detail.dataset.width : -1;
// ์ฒ์ ์คํ์ด๋ผ๋ฉด ์ฆ, ํด๋ฆญ์ ์ํ ์คํ์ด ์๋๋ผ๋ฉด ์คํ
// ๋จ, ์ฌ๊ธฐ์ ์ฃผ์ํด์ผ ํ ์ ์ด "entry.contentRect.width"๊ฐ์๋ padding์ด๋ border๋ฅผ ํฌํจํ์ง ์์ ๊ฐ์
// ๋ฐ๋ผ์ ํญ์ width๊ฐ์ด ๋ง์ง ์์ if๋ฌธ ์ฝ๋๋ฅผ ์คํํด์ <details>๊ฐ ์์ด๋ฆด ์ ์์
// ๊ทธ๋ฌ๋ฏ๋ก details์ ์ง์ ์ ์ผ๋ก padding์ด๋ border๋ฅผ ์์ฃผ๋ ๊ฒ์ด ์ข๊ณ ๋ง์ฝ ๊ฐ์ด ์ค๋ค๋ฉด ์์น๋ฅผ ๊ณ์ฐํด์ ์ง์ ๋ํด์ค์ผ ์ ์์ ์ผ๋ก ์๋ํจ
if (width !== entry.contentRect.width) {
detail.removeAttribute("style");
// ๋ซํ ํฌ๊ธฐ ๊ธฐ๋ก
setHeight(detail);
// ์ด๋ฆผ ํฌ๊ธฐ ๊ธฐ๋ก
setHeight(detail, true);
// ์๋ ์ํ๋ก ๋๋๋ฆผ
detail.open = false;
}
})
);
// wrapper ๋ด๋ถ์ ๋ชจ๋ <details>์ ์ฐพ์์ ๊ด์ฐฐ ๋์์ผ๋ก ์ง์
const details = wrapper.querySelectorAll("details");
details.forEach((detail) => RO.observe(detail));
};
// setDetailsHeight()์ <details>๋ค์ ํฌํจํ๋ ๊ฐ์ฅ ์์ ํ๊ทธ๋ฅผ ์ธ์๋ก ๋ฃ์ด์ฃผ๋ฉด ๋จ
// ๋ฌผ๋ก document๋ฅผ ๋ฐ๋ก ๋ฃ์ด์ค๋ ๋์ง๋ง ์ ์ฒด ํ์๋ณด๋ค๋ <details>๋ฅผ ์ฌ์ฉํ๋ ์ ์ผ ์์ํ๊ทธ๋ฅผ ๋ฃ์ด์ฃผ๋ ๊ฒ ๋ ํจ์จ์ ์ด๋ผ๊ณ ์๊ฐํจ
global.css
์ ์์ฑํ๊ธฐdetails {
height: var(--collapsed);
overflow: hidden;
transition: height 300ms cubic-bezier(0.4, 0.01, 0.165, 0.99);
}
details[open] {
height: var(--expanded);
}
๋๋ฌด ์ข๋ค์! ๋์ค์ ๊ผญ ์ฌ์ฉํด๋ด์ผ๊ฒ ์ด์ฉ~