[Algorithm] ๋ณต์กํ DOM ์์ ๋ก ๋ณด๋ DFS ํ์ ์๊ณ ๋ฆฌ์ฆ
๋ชฉํ
์๋ DOM ๊ตฌ์กฐ์์ ๊ฐ์ฅ ์์ชฝ ์์๋ถํฐ ์์ํด ๋ถ๋ชจ ์์๋ก ๊ฐ ์๋ก ์ค์ฒฉ ๋ ๋ฒจ์ด 1์ฉ ๋์ด๋๊ณ , class
์ ๋์ํ๋ dataset
์ ์ค์ฒฉ ๋ ๋ฒจ ๊ฐ์ ํ ๋นํด์ผ ํ๋ค. ์๋ฅผ๋ค์ด class๊ฐ "clause"
์ด๊ณ , ํด๋น ์์์ ์ค์ฒฉ ๋ ๋ฒจ์ด 2๋ผ๋ฉด data-clause-lv="2"
์์ฑ์ ํ ๋นํ๋ค.
<p ref="{ref}">
<span class="clause" data-clause-lv="2">
<span class="word" data-word-lv="1">
<span data-index="0">Hello</span>
</span>
</span>
</p>;
๋ง์ฝ ์์ ์์๊ฐ 2๊ฐ ์ด์์ผ ๋ ์์ ์์๋ค์ค ์ค์ฒฉ ๋ ๋ฒจ์ด ๊ฐ์ฅ ๋์ ๊ฐ + 1์ด ๋ถ๋ชจ ์์์ ์ค์ฒฉ ๋ ๋ฒจ์ด ๋๋ค. ์๋ ์์๋ฅผ ๊ธฐ์ค์ผ๋ก 1๋ฒ์งธ ์์์ ์ค์ฒฉ ๋ ๋ฒจ(data-word-lv="1"
) ๋ณด๋ค, 2๋ฒ์งธ ์์์ ์ค์ฒฉ ๋ ๋ฒจ(data-phrase-lv="2"
)์ด ๋ ๋์ผ๋ฏ๋ก, ๋ถ๋ชจ ์์์ ์ค์ฒฉ๋ ๋ฒจ์ 3์ด ๋๋ค(data-clause-lv="3"
).
<p ref="{ref}">
<span class="clause" data-clause-lv="3"> <!-- ๋ถ๋ชจ -->
<span class="word" data-word-lv="1"> <!-- ์์ 1 -->
<span data-index="0">Hello</span>
</span>
<span class="phrase" data-phrase-lv="2"> <!-- ์์ 2 -->
<span class="word" data-word-lv="1">
<span data-index="1">World</span>
</span>
</span>
</span>
</p>
์์
์์ด ๋ฌธ์ฅ์์ ๋จ์ด ํน์ ๋ฌธ์ฅ๋ถํธ๋ฅผ ์ธ๋ฑ์ค ๊ธฐ์ค์ผ๋ก ์ก๊ณ (ํ ํฐ ์ธ๋ฑ์ค), ๊ทธ ํ ํฐ๋ค์ด ์๋ก ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋์ด ๋ฌธ์ฅ์ ๊ตฌ์ฑํ๋์ง์ ๋ํ ์ ๋ณด๋ฅผ ์ค์ฒฉ ๊ตฌ์กฐ๋ก ํํํ๋ฉด ์๋์ ๊ฐ์ DOM ๊ตฌ์กฐ๋ฅผ ๊ฐ๋๋ค. ์ด์ ์๋ DOM ๊ตฌ์กฐ์์ ?
๋ก ํ์๋ ์ค์ฒฉ ๊น์ด๋ฅผ ๊ณ์ฐํด์ผ ํ๋ค.
<p class="text-xl">
<span data-index="0">I</span>
<span data-index="1">am</span>
<span class="kc phrase" data-phrase-lv="?">
<span data-index="2">a</span>
<span data-index="3">boy</span>
<span class="kc clause" data-clause-lv="?">
<span data-index="4">who</span>
<span data-index="5">likes</span>
<span class="kc phrase" data-phrase-lv="?">
<span class="kc phrase" data-phrase-lv="?">
<span data-index="6">to</span>
<span class="kc word" data-word-lv="?">
<span class="kc word" data-word-lv="?">
<span class="kc word" data-word-lv="?">
<span data-index="7">play</span>
</span>
</span>
</span>
<span data-index="8">tennis</span>
</span>
<span class="kc clause" data-clause-lv="?">
<span data-index="9">which</span>
<span data-index="10">is</span>
<span class="kc word" data-word-lv="?">
<span class="kc word" data-word-lv="?">
<span data-index="11">fun</span>
</span>
</span>
<span data-index="12">.</span>
</span>
</span>
</span>
</span>
</p>
๊ตฌํ
assignCalculatedLevel
ํจ์๋ ์์ ์์ ์ค ๊ฐ์ฅ ๋์ ์ค์ฒฉ ๋ ๋ฒจ์ ์ฐพ๋ ๊ฒ์ ๋ชฉํ๋ก ํ๋ค. ์ด๋ฅผ ์ํด ์์ ์์๊ฐ ์์ ๋๋ง๋ค ์์ ์ ์ฌ๊ท์ ์ผ๋ก ํธ์ถํ๋ DFS ๋ฐฉ์์ผ๋ก ํ์ํ์ฌ ๊ฐ์ฅ ์์ชฝ ์์๋ถํฐ ๊ณ์ฐํด ๋๊ฐ๋ค.
์ด ๊ณผ์ ์์ word
, phrase
, clause
ํด๋์ค๋ฅผ ๊ฐ์ง ์์์ ๋ํด์ ํด๋น ์ค์ฒฉ ๋ ๋ฒจ ์ ๋ณด๋ฅผ data-
์์ฑ์ ํ ๋นํ๋ค. ์ด๋ ๊ฒ ๊ฐ ์์์ ์ต๋ ์ค์ฒฉ ๋ ๋ฒจ์ด ๊ฒฐ์ ๋๋ฉด ๊ทธ ๊ฐ์ ๋ฐํํ์ฌ ์์ ์์์ ์ค์ฒฉ ๋ ๋ฒจ ๊ณ์ฐ์ ํ์ฉํ๋ค.
const assignCalculatedLevel = (element: HTMLElement) => {
// element ์์ ์์์ค ๊ฐ์ฅ ๋์ ๋ ๋ฒจ์ ์ ์ฅํ ๋ณ์
let maxChildLevel = 0;
// element์ ๋ชจ๋ ์์ ์์ ์ํ
for (const child of element.children) {
// ๊น์ด ์ฐ์ ํ์ ๋ฐฉ์์ผ๋ก ์์ ์์ ๋ ๋ฒจ ๊ณ์ฐ
const childLevel = assignCalculatedLevel(child as HTMLElement);
// maxChildLevel, childLevel ๋ ์ค ๋ ํฐ ๊ฐ์ maxChildLevel๋ก ์ง์
maxChildLevel = Math.max(maxChildLevel, childLevel);
}
const hasChild = element.children.length > 0;
// ํ์ฌ element์ ์์ ์์๊ฐ ์์ผ๋ฉด maxChildLevel + 1 ๊ฐ์ ํ์ฌ ์์์ ๋ ๋ฒจ๋ก ์ง์
// ํ์ฌ element์ ์์ ์์๊ฐ ์์ผ๋ฉด maxChildLevel ๊ฐ์ ํ์ฌ ์์์ ๋ ๋ฒจ๋ก ์ง์
const currentLevel = hasChild ? maxChildLevel + 1 : maxChildLevel;
const classesToCheck: TagType[] = ['word', 'phrase', 'clause'];
classesToCheck.forEach((className) => {
// ํ์ฌ ์์๊ฐ classesToCheck ๋ฐฐ์ด์ ์๋ ํด๋์ค๋ฅผ ํฌํจํ๋์ง ๊ฒ์ฌ
if (element.classList.contains(className)) {
// ํด๋์ค๋ฅผ ํฌํจํ๋ฉด ํด๋น ํด๋์ค์ ๋ํ ๋ ๋ฒจ ์ ๋ณด๋ฅผ `data-${className}Lv` ์์ฑ์ ํ ๋น
element.dataset[`${className}Lv`] = `${currentLevel}`;
}
});
// ํ์ฌ ์์์ ๋ ๋ฒจ ๊ฐ ๋ฐํ
return currentLevel;
};
const calculateNestingLevel = (ref: RefObject<HTMLParagraphElement>) => {
const spans = ref.current?.children;
if (!spans) return;
Array.from(spans).forEach((span) => {
const spanElement = span as HTMLElement;
// dataset.kc๊ฐ ์๋ ์์์ ๋ํด์๋ง ํธ์ถ
if (spanElement.dataset.kc) assignCalculatedLevel(spanElement);
});
};
๊ฐ ์์์ ์ฃผ์์ผ๋ก ์ถ๊ฐํ ์ซ์ ์๊ดํธ๋ ๊ฐ์ฅ ์์ชฝ ์์๊น์ง ๋ฐฉ๋ฌธํ ๋ค ๋ฆฌํดํ๋ ์์๋ฅผ ๋ํ๋ธ๋ค
<p class="text-xl"> <!-- calculateNestingLevel ํจ์ ์์ -->
โ
โโโ <span data-index="0">I</span> <!-- skip -->
โโโ <span data-index="1">am</span> <!-- skip -->
โโโ <span class="kc phrase" data-phrase-lv="7"> <!-- assignCalculatedLevel ํจ์ ์์ -->
โโโ <span data-index="2">a</span> <!-- return 0 -->
โโโ <span data-index="3">boy</span> <!-- return 0 -->
โโโ <span class="kc clause" data-clause-lv="6"> <!-- (13) return 6 -->
โโโ <span data-index="4">who</span> <!-- return 0 -->
โโโ <span data-index="5">likes</span> <!-- return 0 -->
โโโ <span class="kc phrase" data-phrase-lv="5"> <!-- (12) return 5 -->
โโโ <span class="kc phrase" data-phrase-lv="4"> <!-- (6) return 4 -->
โ โโโ <span data-index="6">to</span> <!-- return 0 -->
โ โโโ <span class="kc word" data-word-lv="3"> <!-- (4) return 3 -->
โ โ โโโ <span class="kc word" data-word-lv="2"> <!-- (3) return 2 -->
โ โ โโโ <span class="kc word" data-word-lv="1"> <!-- (2) return 1 -->
โ โ โโโ <span data-index="7">play</span> <!-- (1) return 0 -->
โ โโโ <span data-index="8">tennis</span> <!-- (5) return 0 -->
โโโ <span class="kc clause" data-clause-lv="3"> <!-- (11) return 3 -->
โโโ <span data-index="9">which</span> <!-- return 0 -->
โโโ <span data-index="10">is</span> <!-- return 0 -->
โโโ <span class="kc word" data-word-lv="2"> <!-- (9) return 2 -->
โ โโโ <span class="kc word" data-word-lv="1"> <!-- (8) return 1 -->
โ โโโ <span data-index="11">fun</span> <!-- (7) return 0 -->
โโโ <span data-index="12">.</span> <!-- (10) return 0 -->
๋ฒ์ธ
word
, phrase
, clause
์ค์ฒฉ ๋ ๋ฒจ ์ ๋ณด๋ฅผ ํด๋นํ๋ ์์์ data-*
์์ฑ์ ํ ๋นํ์ผ๋ฏ๋ก ๋ ๋ฒจ ๊ฐ์ ๋์ํ๋ ๋์ด๋ฅผ ์ง์ ํ ์ ์๋ค. ์๋ฅผ๋ค์ด ๊ธฐ๋ณธ ๋์ด๊ฐ 1rem
์ด๋ฉด ๋ ๋ฒจ 1๋ถํฐ 1rem * 1
, 1rem * 2,
, ...
๋ฐฉ์์ผ๋ก ๋์ด๋ฅผ ๊ฐ์ง ์ ์๋ ๊ฒ.
SCSS์ @for
๋ฌธ์ ์ด์ฉํ๋ฉด ๊ฐ ๋ ๋ฒจ์ ๋ํ ์คํ์ผ์ ๋์ ์ผ๋ก ์์ฑํ ์ ์๋ค.
$clause-padding-base: 0.1rem; // ์ /๊ตฌ ํ๋จ ์ฌ๋ฐฑ
$clause-top-base: 1.5rem; // ์ /๊ตฌ ์ด๋ฆ ์๋จ ์ฌ๋ฐฑ
$clause-offset-base: 1.3rem; // ์ /๊ตฌ ์ ์ ์ํ ๊ฐ๊ฒฉ
$word-top-base: 1.5rem; // ๋จ์ด ์๋จ ์ฌ๋ฐฑ
$word-offset-base: 1rem; // ๋จ์ด ์ํ ๊ฐ๊ฒฉ
@for $i from 1 through 30 {
$offset: $clause-offset-base * ($i - 1);
.kc.clause[data-clause-lv='#{$i}'],
.kc.phrase[data-phrase-lv='#{$i}'] {
padding-bottom: $clause-padding-base + $offset;
&:after {
top: $clause-padding-base + $clause-top-base + $offset;
}
}
}
@for $i from 1 through 30 {
$offset: $word-offset-base * ($i - 1);
.kc.word[data-word-lv='#{$i}']:after {
top: $word-top-base + $offset;
}
}
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ES2023 ๋ถ๋ณ์ฑ ๋ฐฐ์ด ๋ฉ์๋ ํบ์๋ณด๊ธฐ
[JS] ์๋ฐ์คํฌ๋ฆฝํธ ES2023 ๋ถ๋ณ์ฑ ๋ฐฐ์ด ๋ฉ์๋ ํบ์๋ณด๊ธฐ
2024.05.23 -
[JS] ์์ด ์ถ์ฝ์ด ๊ด๋ จ ์ ํธ๋ฆฌํฐ ํจ์ ๋ชจ์
[JS] ์์ด ์ถ์ฝ์ด ๊ด๋ จ ์ ํธ๋ฆฌํฐ ํจ์ ๋ชจ์
2024.05.22 -
[Algorithm] ๋ฐ์ดํฐ ์ถ๊ฐ, ์ญ์ , ์ ๋ ฌ๋ก ๋ณด๋ BFS / DFS ํ์ ์๊ณ ๋ฆฌ์ฆ
[Algorithm] ๋ฐ์ดํฐ ์ถ๊ฐ, ์ญ์ , ์ ๋ ฌ๋ก ๋ณด๋ BFS / DFS ํ์ ์๊ณ ๋ฆฌ์ฆ
2024.05.21 -
[React/JS] ๋๋๊ทธํ ๋ฌธ์์ด ๋ถ๋ฆฌ(๋ฉํ)ํ๊ธฐ / Selection API
[React/JS] ๋๋๊ทธํ ๋ฌธ์์ด ๋ถ๋ฆฌ(๋ฉํ)ํ๊ธฐ / Selection API
2024.05.21