코딩/Javascript

[JS-중급편-DOM] 6. DOM 생성, 삭제, 복제

드리프트 2021. 1. 24. 21:47
728x170

 

 

우리는 HTML 요소를 허공에 만들고 몇 줄의 JavaScript를 사용하여 DOM에 추가할 수 있습니다.

 

DOM에서 요소를 동적으로 생성하고 수정하는 기능은 우리가 즐겨 찾는 많은 웹 사이트와 애플리케이션을 다이내믹하게 만드는 중요한 요소입니다.

 

HTML에 모든 것을 미리 정의하는 것은 매우 제한적입니다.

 

우리는 새로운 데이터를 가져올 때, 또는 페이지와 상호 작용할 때, 또는 더 많이 스크롤할 때, 그리고 다른 작업을 수행할 때 콘텐츠가 변경되고 적응되기를 원합니다.

 

이 튜토리얼에서는 이 모든 작업을 수행하는 기본 사항을 다룰 것입니다.

 

요소를 생성하고, 요소를 제거하고, 부모 요소를 다시 만들고, 요소를 복제하는 방법을 살펴 보겠습니다.

 

 

요소 생성

 

대화형 사이트 및 웹앱에서 HTML 요소를 동적으로 만들고 유지하는 것은 매우 일반적입니다.

 

요소를 만드는 방법은 createElement 메서드를 사용하는 것입니다.

 

createElement의 작동 방식은 매우 간단합니다.

 

document 객체를 통해 호출하고 생성하려는 요소의 HTML 태그 이름을 전달합니다.

 

다음 스니펫에서는 문자 p로 표시되는 단락 요소를 만듭니다.

 

let myElement = document.createElement("p");

 

myElement 변수는 새로 생성된 요소에 대한 참조를 가지고 있습니다.

 

이 코드를 브라우저에서 로딩하면 p 요소가 생성됩니다.

 

동적으로 생성된 p 요소가 현재 목적 없이 떠 다니기 때문에 실제로 이 요소를 DOM 어딘가에 배치해야 합니다.

 

 

 

목적 없이 떠 다니는 이유는 DOM이 볼 때 p 요소가 존재한다는 인식할 수 없기 때문입니다.

 

요소가 DOM의 일부가 되려면 다음 두 가지를 수행해야 합니다.

 

  1. 부모 역할을 할 요소 찾기
  2. appendChild를 사용하고 요소를 원하는 해당 부모 요소에 추가하십시오.

이 모든 것을 이해하는 가장 좋은 방법은 이 모든 것을 하나로 묶는 예를 보는 것입니다.

 

따라 하려면 새 HTML 문서를 만들고 다음 HTML, CSS 및 JS를 추가하십시오.

 

<!DOCTYPE html>
<html>

<head>
  <title>Creating Elements</title>

  <style>
    body {
      background-color: #0E454C;
      padding: 30px;
    }
    h1 {
      color: #14FFF7;
      font-size: 72px;
      font-family: sans-serif;
      text-decoration: underline;
    }
    p {
      color: #14FFF7;
      font-family: sans-serif;
      font-size: 36px;
      font-weight: bold;
    }
  </style>
</head>

<body>
  <h1>Am I real?</h1>
  <script>
    let newElement = document.createElement("p");
    let bodyElement = document.querySelector("body");

    newElement.textContent = "Or do I exist entirely in your imagination?";

    bodyElement.appendChild(newElement);
  </script>
</body>

</html>

 

이 파일을 저장하고 브라우저에서 로드합니다. 모든 것이 해결되면 다음과 유사한 내용이 표시됩니다.

 

 

 

이제 우리는 한 걸음 물러나서 우리의 예에서 정확히 무슨 일이 일어나는지 살펴볼 것입니다.

 

JavaScript를 살펴보면 요소를 만들고 이를 DOM에 추가하는 데 필요한 모든 것이 스크립트 태그 사이에 있습니다.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");

newElement.textContent = "Or do I exist entirely in your imagination?";

bodyElement.appendChild(newElement);

 

newElement를 사용하여 새로 생성된 p 태그에 대한 참조를 저장합니다.

 

bodyElement를 사용하면 body 요소에 대한 참조를 저장합니다.

 

새로 생성된 요소 (newElement)에서 textContent 속성을 최종적으로 표시할 내용으로 설정합니다.

 

마지막으로 할 일은 목적 없이 떠 다니는 newElement를 가져와서 appendChild를 이용해 body 요소의 자식으로 만드는 것입니다.

 

다음은 간단한 예제의 DOM이 어떻게 생겼는지 시각화한 것입니다.

 

 

이제 appendChild 함수에 대한 세부 사항은 부모가 가질 수 있는 모든 자식의 끝에 요소를 추가한다는 것입니다.

 

우리의 경우 body 요소에는 이미 h1 및 script 요소가 자식으로 있습니다.

 

p 요소는 그 뒤에 막내로 추가됩니다. 즉, 부모 아래에서 특정 요소가 위치할 정확한 순서를 제어할 수 있습니다.

 

h1 태그 바로 뒤에 newElement를 삽입하려면 부모에 대해 insertBefore 함수를 호출하면 됩니다.

 

insertBefore 함수는 두 개의 인수를 사용합니다.

 

첫 번째 인수는 삽입하려는 요소입니다.

 

두 번째 인수는 선행하려는 형제 (부모의 자식)에 대한 참조입니다.

 

다음은 우리의 newElement가 h1 요소 뒤 (그리고 스크립트 요소 앞)에 있도록 수정된 예제입니다.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");
let scriptElement = document.querySelector("script");

newElement.textContent = "I exist entirely in your imagination.";

bodyElement.insertBefore(newElement, scriptElement);

 

bodyElement에 대해 insertBefore를 호출하고 스크립트 요소 앞에 newElement를 삽입하도록 지정합니다. 

 

이 경우 DOM은 다음과 같습니다.

 

insertBefore 메서드가 있으면 insertAfter 메서드도 있어야 한다고 생각할 수 있습니다.

 

사실 insertAfter 메서드는 없습니다. 요소 앞이 아닌 뒤에 삽입하는 방법은 없습니다.

 

우리가 할 수 있는 것은 요소 앞에 추가 요소를 삽입하도록 지시하여 insertBefore 함수를 속이는 것입니다.

 

이건 말도 안 되므로 먼저 코드를 보여주고 나중에 설명하겠습니다.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");
let h1Element = document.querySelector("h1");

newElement.textContent = "I exist entirely in your imagination.";

bodyElement.insertBefore(newElement, h1Element.nextSibling);

 

마지막 줄에 유의하여 코드가 실행되기 전과 실행된 후에 발생하는 상황을 보여주는 다음 다이어그램을 살펴보십시오.

 

 

h1 Element.nextSibling 호출은 스크립트 요소를 찾습니다. 

 

스크립트 요소 앞에 newElement를 삽입하면 h1 요소 뒤에 요소를 삽입한다는 목표가 달성됩니다. 

 

타기팅할 형제 요소가 없으면 어떻게 되나요? 

 

이 경우 insertBefore 함수는 매우 영리하며 원하는 요소를 자동으로 끝에 추가합니다.

 

 

 

 

insertAfter 메서드 직접 만들기

 

insertAfter 메서드가 없으면 직접 만들 수 있습니다.

 

일단 아래 코드를 보십시오.

 

function insertAfter(target, newElement) {
  target.parentNode.insertBefore(newElement, target.nextSibling);
}

 

작동되는 예제를 보겠습니다.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");
let h1Element = document.querySelector("h1");

newElement.textContent = "I exist entirely in your imagination.";

function insertAfter(target, element) {
  target.insertBefore(element, target.nextSibling);
}

insertAfter(bodyElement, newElement);

 

 

요소 제거

이전 섹션에서 createElement 메서드를 사용하여 요소를 만드는 방법을 살펴보았습니다.

 

이 섹션에서는 요소를 제거하는 것에 관한 removeChild를 살펴보겠습니다.

 

우리가 오랫동안 살펴본 예제와 함께 작동하도록 만들 수 있는 다음 코드 스니펫을 살펴보십시오.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");
let h1Element = document.querySelector("h1");

newElement.textContent = "I exist entirely in your imagination.";

bodyElement.appendChild(newElement);

bodyElement.removeChild(newElement);

 

newElement에 의해 저장된 p 요소는 appendChild 메서드에 의해 body 요소에 추가됩니다.

 

이 요소를 제거하려면 body 요소에서 removeChild를 호출하고 제거하려는 요소에 대한 포인터를 전달합니다.

 

위 코드에서는 제거하려는 요소는 newElement입니다.

 

removeChild가 실행되면 DOM이 newElement가 존재한다는 것을 알지 못하는 것과 같습니다.

 

주의할 점은 제거하려는 자식의 부모에서 removeChild를 호출해야 한다는 것입니다.

 

이 메서드는 제거하려는 요소를 찾으려고 DOM을 위아래로 탐색하지 않습니다.

 

이제 요소의 부모에 직접 액세스 할 수없고 이를 찾는 데 시간을 낭비하고 싶지 않다고 가정해 보겠습니다.

 

다음과 같이 parentNode 속성을 사용하여 해당 요소를 매우 쉽게 제거할 수 있습니다.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");
let h1Element = document.querySelector("h1");

newElement.textContent = "I exist entirely in your imagination.";

bodyElement.appendChild(newElement);

newElement.parentNode.removeChild(newElement);

 

이 변형에서는 newElement.parentNode를 지정하여 부모에서 removeChild를 호출하여 newElement를 제거합니다.

 

위 코드는 약간 이상하게 보일 수 있지만 원하는 작업을 완료합니다.

 

이제 요소를 제거하는 더 새롭고 나은 방법이 있습니다.

 

직접 제거하려는 요소에 대해 remove 메서드를 호출만 하면 됩니다. 이 예제는 다음과 같습니다.

 

let newElement = document.createElement("p");
let bodyElement = document.querySelector("body");
let h1Element = document.querySelector("h1");

newElement.textContent = "I exist entirely in your imagination.";

bodyElement.appendChild(newElement);

newElement.remove();

 

그럼 처음부터 remove 메서드로 요소를 제거하는 것만 알려주면 되지 왜 removeChild 메서드를 배웠을까요?

 

그 이유는 브라우저 지원과 관련이 있습니다.

 

remove 메서드 접근 방식은 최근 메서드이기 때문에 Internet Explorer와 같은 구형 브라우저는 이를 지원하지 않습니다.

 

Internet Explorer 지원이 중요한 경우 우리가 살펴본 구식 접근 방식을 이용해야 합니다.

 

일부 사소한 단점에도 불구하고 요소를 제거하기 위해 보편적으로 허용되는 접근 방식을 찾고 있다면 removeChild 함수는 효율성면에서 가장 좋습니다.

 

직접적인 것을 원한다면 remove 메서드가 적격일 겁니다.

 

이 두 가지 방법 모두 원래 마크 업에서 생성된 요소를 포함하여 모든 DOM 요소를 제거할 수 있습니다.

 

동적으로 추가 한 DOM 요소를 제거하는 데 국한되지 않습니다.

 

제거하려는 DOM 요소에 많은 수준의 자식과 손자가 있으면 그들 또한 모두 제거됩니다. 제거할 때 주의를 요해야 합니다.

 

 

요소 복제

 

나머지 DOM 조작 기술은 하나의 요소로 시작하여 동일한 복제본을 생성하는 복제 요소를 중심으로 회전하는 기술입니다.

 

요소를 복제하는 방법은 복제하려는 요소에서 cloneNode 함수를 호출하고 true 또는 false 인수를 제공하여 요소만 복제할지 아니면 요소와 모든 자식을 복제할지 지정하는 것입니다.

 

다음은 요소를 복제하고 DOM에 추가하는 코드 스니펫입니다.

 

let bodyElement = document.querySelector(body);
let item = document.querySelector(".myItem");

let clonedItem = item.cloneNode(false);

// add cloned element to the DOM
bodyElement.appenChild(clonedItem);

 

복제된 요소가 DOM에 추가되면 우리가 배운 DOM 제어방법을 사용하여 수정할 수 있습니다.

 

요소 복제는 우리가 익숙해지기에 시간이 필요한 만큼 매우 중요한 개념입니다. 이 스니펫을 넘어서 더 완전한 예를 살펴보겠습니다.

 

<!DOCTYPE html>
<html>

<head>
  <title>Cloning Elements</title>

  <style>
    body {
      background-color: #60543A;
      padding: 30px;
    }
    h1 {
      color: #F2D492;
      font-size: 72px;
      font-family: sans-serif;
      text-decoration: underline;
    }
    p {
      color: #F2D492;
      font-family: sans-serif;
      font-size: 36px;
      font-weight: bold;
    }
  </style>
</head>

<body>
  <h1>Am I real?</h1>
  <p class="message">I exist entirely in your imagination.</p>

  <script>
    let bodyElement = document.querySelector("body");
    let textElement = document.querySelector(".message");
    
    setInterval(sayWhat, 1000);

    function sayWhat() {
      let clonedText = textElement.cloneNode(true);
      bodyElement.appendChild(clonedText);
    }
  </script>
</body>

</html>

 

이 코드를 브라우저에서 로드해 보면 이전 예제와 유사한 내용이 표시됩니다.

 

 

그러나 몇 초 후에 이 예제가 상당히 다르다는 것을 알 수 있습니다. 메시지가 계속 복제됩니다.

 

 

비밀은 바로 위 코드 내에 있습니다.

 

스크립트 태그 내부의 코드로 돌아가서 무슨 일이 일어나고 있는지 알아보겠습니다.

 

let bodyElement = document.querySelector("body");
let textElement = document.querySelector(".message");

 

맨 위에는 HTML의 body 요소를 참조하는 bodyElement 변수가 있습니다.

 

message 클래스 값으로 우리의 p 요소를 참조하는 textElement 변수를 가지고 있습니다. 여기까지는 특별한 것은 없습니다.

 

자, 여기가 조금 흥미로운 부분입니다. 

 

1000 밀리 초 (1 초)마다 sayWhat 함수를 호출하는 setInterval 타이머 함수가 있습니다.

 

setInterval(sayWhat, 1000);

 

실제 복제가 발생하는 sayWhat 함수 안에 있습니다.

 

function sayWhat() {
  let clonedText = textElement.cloneNode(true);
  bodyElement.appendChild(clonedText);
}

 

textElement에서 cloneNode를 호출합니다. 

 

이렇게 하면 textElement의 복사본이 생성되고 clonedText 변수의 일부로 저장됩니다. 

 

마지막 단계는 새로 복제된 요소를 DOM에 추가하여 표시되도록하는 것입니다. 

 

setInterval 덕분에 sayWhat 아래의 모든 코드가 페이지에 복제 된 요소를 계속 추가하기 위해 반복됩니다.

 

눈치채셨을 수 있는 한 가지는 복제 중인 항목이 다음 단락 요소라는 것입니다.

 

<p class="message">I exist entirely in your imagination.</p>

 

코드에서 지정한 내용은 다음과 같습니다.

 

let clonedText = textElement.cloneNode(true);

 

true 플래그로 cloneNode를 호출하여 모든 자식도 복제할 것임을 나타냅니다.

 

왜? 단락 요소에 자식이 없는 것 같죠?

 

음... 이것이 요소와 노드의 차이가 작용하는 곳입니다.

 

단락 태그에는 자식 요소가 없지만 p 태그로 래핑 된 텍스트는 자식 노드입니다.

 

헷갈리지 않게 주의해야 합니다.

 

 

 

결론

 

여기서 createElement, removeElement, remove 및 cloneNode와 같은 메서드를 사용하여 쉽게 만들 수 있는 변경의 깊이와 폭을 직접 확인했습니다.

 

여기에서 배운 모든 내용으로 완전히 빈 페이지로 시작하고 몇 줄의 JavaScript를 사용하여 그 안에 있는 모든 내용을 채우는 것도 가능합니다.

 

<!DOCTYPE html>
<html>

<head>
  <title>Look what I did, ma!</title>
</head>

<body>
  <script>
    let bodyElement = document.querySelector("body");

    let h1Element = document.createElement("h1");
    h1Element.textContent = "Do they speak English in 'What'?";

    bodyElement.appendChild(h1Element);

    let pElement = document.createElement("p");
    pElement.textContent = "I am adding some text here...like a boss!";

    bodyElement.appendChild(pElement);
  </script>
</body>

</html>

 

이런 일을 할 수 있다고 해서 항상 그래야 한다는 의미는 아닙니다.

 

콘텐츠를 동적으로 생성할 때 발생하는 주요 문제는 검색 엔진이 콘텐츠를 파악하는 데  어려움이 있다는 것입니다.

 

구글 검색엔진은 JavaScript를 사용하여 만든 것보다 마크 업에 저장된 콘텐츠에 더 익숙합니다.

그리드형