Logo

웹 컴포넌트의 속성(attribute) 활용법

지난 포스팅는 HTML, CSS, JavaScript 만으로 UI 컴포넌트를 만들 수 있는 웹 표준 기술인 웹 컴포넌트(Web Components)에 대해서 기본 개념을 잡아보았습니다. 이번 포스팅에서는 웹 컴포넌트를 좀 더 제대로 쓰기 위해서 사용자 정의 요소에 속성(attribute)으로 주어진 값을 활용하는 방법에 대해서 배우도록 하겠습니다.

HTML 템플릿 작성

우선 웹 컴포넌트에서 사용할 HTML 코드를 보관하기 위한 HTML 템플릿을 작성해보겠습니다. HTML 템플릿이란 <template> 요소를 사용해서 작성하는 HTML 마크업을 의미하는데요. 브라우저는 HTML 템플릿을 화면에 그려주지 않기 때문에, 실제 웹 페이지에는 아무것도 나타나지 않습니다.

밑에서 웹 컴포넌트를 구현할 때, 이 HTML 템플릿을 참조해야 하기 때문에 <template> 요소의 id 속성을 our-button로 설정하겠습니다.

<template> 요소 안에는 스타일링을 위한 <style> 요소와 <button> 요소를 추가합니다. 그리고 <button> 요소 아래에는 웹 컴포넌트의 내부 텍스트가 그대로 표시될 수 있도록 <slot /> 요소를 둡니다.

<template id="our-button">
  <style>
    <!-- 스타일 추가 예정 -->
  </style>
  <button>
    <slot />
  </button>
</template>

이제 <style> 요소 아래에 버튼에 대한 스타일링 규칙을 추가하겠습니다. 여백, 폰트, 테두리, 레이아웃에 대해 기본적인 스타일을 하고, 버튼이 상태별로도 약간씩 달라 보일 수 있도록 스타일하겠습니다.

버튼에 대한 상세한 스타일링 가이드는 별도 포스팅에서 다루고 있으니 참고하세요.

<template id="our-button">
  <style>
    :host {
      --button-color: #ffffff;
      --button-bg-color: #0d6efd;
      --button-hover-bg-color: #025ce2;
    }

    button {
      -webkit-appearance: none;
      -moz-appearance: none;
      appearance: none;

      margin: 0;
      padding: 0.5rem 1rem;

      font-family: "Noto Sans KR", sans-serif;
      font-size: 1rem;
      font-weight: 400;
      text-align: center;
      text-decoration: none;

      display: inline-block;
      width: auto;

      border: none;
      border-radius: 4px;

      box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
      cursor: pointer;
      transition: 0.5s;

      background: var(--button-bg-color);
      color: var(--button-color);

      &:active,
      &:hover,
      &:focus {
        background: var(--button-hover-bg-color);
        outline: 0;
      }
      &:disabled {
        opacity: 0.5;
      }
    }
  </style>
  <button>
    <slot />
  </button>
</template>

색상과 관련된 속성 값들은 최신 스타일링 관행에 따라서 CSS 변수를 뽑아내었습니다. 밑에서 웹 컴포넌트를 구현할 때 이 CSS 변수를 활용할 예정입니다.

웹 컴포넌트 구현

지금부터 본격적으로 웹 컴포넌트를 구현해보겠습니다. 웹 컴포넌트는 HTMLElement라는 인터페이스를 확장해서 만드는데요. 클래스의 이름은 OurButton으로 하겠습니다.

웹 컴포넌트의 HTML과 CSS, JavaScript를 전체 웹 페이지의 DOM으로 부터 격리시키기 위해서 Shadow DOM을 붙여주겠습니다. 그리고 위에서 작성한 HTML 템플릿을 읽어와 복제를 한 후, Shadow DOM의 자식으로 추가합니다.

class OurButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    const template = document.getElementById("our-button");
    const clone = template.content.cloneNode(true);
    this.shadowRoot.appendChild(clone);
  }
}

그 다음, customElements 전역 객체의 define() 함수를 통해서 <our-button> 사용자 정의 요소와 OurButton 클래스를 연결해주겠습니다.

customElements.define("our-button", OurButton);

이제 작성한 웹 컴포넌트가 잘 작동하는지 테스트해볼까요?

<main>
  <our-button>버튼 1</our-button>
  <our-button>버튼 2</our-button>
  <our-button>버튼 3</our-button>
</main>

브라우저에 3개의 버튼이 나타나는 것을 볼 수 있습니다.

사용자 정의 요소의 속성 활용

React 컴포넌트가 주어진 prop에 따라서 모습을 바꿀 수 있는 것처럼, 웹 컴포넌트도 속성 값에 따라서 다양한 모습으로 나타나게 만들 수 있습니다.

예를 들어, <our-button> 요소가 기본 버튼 외에 성공, 오류, 경고, 이렇게 3가지 변형된 종류의 버튼을 추가로 지원하고, 소, 중, 대, 이렇게 3가지 버튼 크기를 지원하도록 구현해보겠습니다. 즉, 다음과 같이 HTML 마크업을 했을 때, 각각 다른 모습의 버튼이 나타날 수 있도록 웹 컴포넌트를 개선하려고 합니다.

<main>
  <our-button variant="success">성공</our-button>
  <our-button variant="error">오류</our-button>
  <our-button variant="warning">경고</our-button>

  <our-button size="sm">소형</our-button>
  <our-button size="md">중형</our-button>
  <our-button size="lg">대형</our-button>
</main>

우선 웹 컴포넌트에 넘어온 속성 variantsizegetAttribute() 메서드를 통해서 읽어서, 그 속상 값을 그대로 버튼 요소의 클래스 목록에 추가해줍니다. size 속성에 아무 값도 설정되지 않은 경우, md가 기본값으로 사용되도록 하였습니다.

class OurButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    const template = document.getElementById("our-button");
    const clone = template.content.cloneNode(true);
    this.shadowRoot.appendChild(clone);

    const variant = this.getAttribute("variant");    this.shadowRoot.querySelector("button").classList.add(variant);
    const size = this.getAttribute("size") ?? "md";    this.shadowRoot.querySelector("button").classList.add(size);  }
}

customElements.define('our-button', OurButton);

방금 웹 컴포넌트에서 추가한 클래스에 대한 스타일링 규칙을 HTML 템플릿에 추가해야 합니다. 버튼 종류에 대한 클래스(success, error, warning)를 스타일할 때는 CSS 변수를 활용하겠습니다. 그리고 버튼 크기에 대한 클래스(sm, lg)를 스타일할 때는 관련 CSS 속성값을 직접 변경하겠습니다.

<template id="our-button">
  <style>
    :host {
      --button-color: #ffffff;
      --button-bg-color: #0d6efd;
      --button-hover-bg-color: #025ce2;
    }

    button {
      /* 생략 */

      &.success {
        --button-bg-color: #28a745;
        --button-hover-bg-color: #218838;
      }

      &.error {
        --button-bg-color: #dc3545;
        --button-hover-bg-color: #c82333;
      }

      &.warning {
        --button-color: #212529;
        --button-bg-color: #ffc107;
        --button-hover-bg-color: #e0a800;
      }

      &.sm {
        font-size: 0.6rem;
        padding: 0.25rem 0.5rem;
      }

      &.lg {
        font-size: 1.5rem;
        padding: 0.7rem 1.4rem;
        border-radius: 8px;
      }
    }
  </style>
  <button>
    <slot />
  </button>
</template>

이제 브라우저에서 테스트를 해보면, 바라던 대로 다양한 종류와 크기의 버튼이 나타날 것입니다.

마치면서

지금까지 사용자 정의 버튼을 구현하는 실습을 통해서 웹 컴포넌트에서 어떻게 속성 값을 활용할 수 있는지 살펴보았습니다. 본 포스팅을 통해서 웹 컴포넌트의 무궁무진한 가능성을 느끼실 수 있으셨으면 좋겠습니다.