[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 API2024.05.21