코드를 최대한 캡슐화하여 재사용 가능한 커스텀 엘리먼트를 만들게 해주는 기술들의 모음
개념 : 재사용을 원하는 어느 곳이든, 코드 충돌에 대한 걱정이 없는 캡슐화 된 기능을 갖춘 다용도의 커스텀 엘리먼트를 생성하기 위해 사용됨
커스텀 엘리먼트란 ?
커스텀 엘리먼트와 그 동작을 정의할 수 있도록 해주는 Javascript API의 집합.
세 가지 주요 기술로 구성된다
- Custom Element : Custom element에 대한 정의는 DOM element의 새로운 타입을 사용하고 디자인하기 위한 토대를 마련한다. browser API 중에 customElements.define("HTML name", 정의될이름) 등을 사용하여 돔에 해당 엘리먼트를 붙이고 커스텀 할 수 있도록 해준다. 내부에는 라이프 사이클이 존재 해당 API내부에는 사용할 수 있는 메서드가 몇 가지 정의되어 있고 이를 이용하여 컴포넌트를 만들 수 있다.
- What is a Custom Element / Web Component?
- Shadow DOM : shadow DOM에 대한 정의는 웹 컴포넌트 내에서 어떻게 캡슐화된 마크업과 스타일을 사용할 것인가에 대한 것이다.
- ES Modules : Js 문서가 현대적이고 기초적인 부분에 기반을 두고 재사용 및 내장될 수 있도록 해준다.
- HTML Template : HTML Template 은 마크업 조각들이 언제 로딩되고 정의될지에 대해 정의함.
커스텀 엘리먼트를 만들 수 있는 방법은 너무 많다.
polymer라는 패키지를 이용할 수도 있다.
브라우저의 api를 이용할 경우에는, customElements.define()이라는 메서드를 이용할 수 있다.
Custom element에는 두 종류가 있다
Autonomous custom elements
비동기 적으로 로딩 됨 / 독립적인 단위로 움직일 수 있다. 우리는 HTML element 그냥 쓰기만 하면 페이지에 이를 사용할 수 있다.
예 - <popup-info>, or document.createElement("popup-info").
Customized built-in ekements
basic HTML elements에서 상속된다. 만들기 위해선 어떤 element를 상속받을 건지 정의하고, 기본 적인 element를 작성하지만 is 속성에 사용자 정의 요소의 이름을 지정하여 사용한다.
예- <p is="word-count">, or document.createElement("p", { is: "word-count" }).
예제 1
```jsx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
hellosss
<app-drawer></app-drawer>
<h1>Word count rating widget</h1>
<article contenteditable="">
<h2>Sample heading</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum. Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.</p>
<p>Pellentesque ornare tellus sit amet massa tincidunt congue. Morbi cursus, tellus vitae pulvinar dictum, dui turpis faucibus ipsum, nec hendrerit augue nisi et enim. Curabitur felis metus, euismod et augue et, luctus dignissim metus. Mauris placerat tellus id efficitur ornare. Cras enim urna, vestibulum vel molestie vitae, mollis vitae eros. Sed lacinia scelerisque diam, a varius urna iaculis ut. Nam lacinia, velit consequat venenatis pellentesque, leo tortor porttitor est, sit amet accumsan ex lectus eget ipsum. Quisque luctus, ex ac fringilla tincidunt, risus mauris sagittis mauris, at iaculis mauris purus eget neque. Donec viverra in ex sed ullamcorper. In ac nisi vel enim accumsan feugiat et sed augue. Donec nisl metus, sollicitudin eu tempus a, scelerisque sed diam.</p>
<p is="word-count"></p>
</article>
<script src="main.js"></script>
<script>
class AppDrawer extends HTMLElement {
// ======================= life cycles!! =======================
connectedCallback(){ :
const name = "duke";
console.log('Custom square element added to page.');
this.innerHTML = `
<h2>hey ${name}</h2>
`
}
disconnectedCallback() {
console.log('Custom square element removed from page.');
}
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
attributeChangedCallback(name, oldValue, newValue) {
console.log('Custom square element attributes changed.');
}
}
customElements.define('app-drawer', AppDrawer); // 엘리먼트를 등록하는 과정
// 정의할 엘리먼트 이름, 기능을 명시하고 있는 클래스, 상속받은 엘리먼트 (옵션)
// Create a class for the element
class WordCount extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
///=========== parent Node도 이런 식으로 가져올 수 있음
// count words in element's parent element
const wcParent = this.parentNode;
function countWords(node){
const text = node.innerText || node.textContent;
return text.split(/\s+/g).length;
}
const count = `Words: ${countWords(wcParent)}`;
// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});
// Create text node and add word count to it
const text = document.createElement('span');
text.textContent = count;
// Append it to the shadow root
shadow.appendChild(text);
// Update count when element content changes
setInterval(function() {
const count = `Words: ${countWords(wcParent)}`;
text.textContent = count;
}, 200);
}
}
// Define the new element
customElements.define('word-count', WordCount, { extends: 'p' });
</script>
</body>
</html>
```
하단의 깃헙에 들어가게 되면 만들어 놓은 웹 컴포넌트들의 예시들을 잘 모아놨다.
web component를 만들어 내는 기술들의 라이브러리 깃헙
Shadow DOM
돔을 만들고 다른 요소의 자식으로 추가하고 붙이는 개념이 아니라 (appendChild API) Shadow DOMTree 를 만들 루트를 만들어 주고 그 밑으로 자식 돔 트리가 들어갈 수 있도록 해줌 실제 자식으로 부터는 분리된 개념이다.
const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().
// header.shadowRoot === shadowRoot
// shadowRoot.host === header
구성요소
- Shadow Host : shadow DOM이 attach 될 regular DOM node
- Shadow tree : shadow DOM 내부의 DOM tree
- Shadow boundary : shadow DOM이 시작되고 끝나는 지점
- Shadow root : shadow tree 의 root Node
shadow DOM tree내부에 css 파일을 입히면서 스타일링이 가능하고, 기타 어트리뷰트도 넣을 수 있음
shadow DOM은 그 자체로 캡슐화된 엘리먼트를 넣는다는 것을 의미한다.
Shadow DOM을 사용해보자.
element.attachShadow 를 사용하여 엘리먼트를 부착할 수 있다.
파라미터로 옵션 오브젝트를 요구한다. (mode - open or closed)
open : 메인 페이지 레벨에 쓰여진 자바스킙트로 shadow DOM 에 접근할 수 있다는 것을 의미. 예를 들면 element.shadowRoot property를 사용함으로서 접근 가능,
closed : 섀도우 돔에 외부에서 접근불가.
class 생성자의 한 부분으로서 shadow DOM을 넣고 싶다면,
let shadow = this.attachShadow({mode: 'open'});
shadow DOM을 붙였을 때, 평소 돔 조작을 하듯이 DOM API 를 이용할 수 있다.
예제 2
```jsx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
hellosss
<app-drawer></app-drawer>
<h1>Word count rating widget</h1>
<article contenteditable="">
<h2>Sample heading</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum. Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.</p>
<p>Pellentesque ornare tellus sit amet massa tincidunt congue. Morbi cursus, tellus vitae pulvinar dictum, dui turpis faucibus ipsum, nec hendrerit augue nisi et enim. Curabitur felis metus, euismod et augue et, luctus dignissim metus. Mauris placerat tellus id efficitur ornare. Cras enim urna, vestibulum vel molestie vitae, mollis vitae eros. Sed lacinia scelerisque diam, a varius urna iaculis ut. Nam lacinia, velit consequat venenatis pellentesque, leo tortor porttitor est, sit amet accumsan ex lectus eget ipsum. Quisque luctus, ex ac fringilla tincidunt, risus mauris sagittis mauris, at iaculis mauris purus eget neque. Donec viverra in ex sed ullamcorper. In ac nisi vel enim accumsan feugiat et sed augue. Donec nisl metus, sollicitudin eu tempus a, scelerisque sed diam.</p>
<p is="word-count"></p>
<h1>Pop-up info widget - web components</h1>
<form>
<div>
<label for="cvc">Enter your CVC
<popup-info
img="https://images.unsplash.com/photo-1629486997190-dcfbabe0ab8b?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwzfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60"
text="Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.">
</popup-info>
</label>
<input type="text" id="cvc">
</div>
</form>
</article>
<script>
class PopupInfo extends HTMLElement{
constructor() {
super();
var shadow = this.attachShadow({mode: 'open'});
// Create spans
var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');
var icon = document.createElement('span');
icon.setAttribute('class','icon');
icon.setAttribute('tabindex', 0);
var info = document.createElement('span');
info.setAttribute('class','info');
// Take attribute content and put it inside the info span
var text = this.getAttribute('text');
info.textContent = text;
// Insert icon
var imgUrl;
console.log(this)
if(this.hasAttribute('img')) {
imgUrl = this.getAttribute('img');
} else {
imgUrl = 'img/default.png';
}
var img = document.createElement('img');
img.src = imgUrl;
icon.appendChild(img);
const style = document.createElement("style");
console.log("style.isConnected",style.isConnected);
style.textContent = `
.wrapper {
position: relative;
}
.info {
font-size: 0.8rem;
width: 200px;
display: inline-block;
border: 1px solid black;
padding: 10px;
background: white;
border-radius: 10px;
opacity: 0;
transition: 0.6s all;
position: absolute;
bottom: 20px;
left: 10px;
z-index: 3;
}
img {
width: 1.2rem;
}
.icon:hover + .info, .icon:focus + .info {
opacity: 1;
}
`;
// Attach the created elements to the shadow dom
shadow.appendChild(style);
console.log("style.isConnected",style.isConnected);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
}
}
customElements.define("popup-info",PopupInfo);
</script>
</body>
</html>
```
아래의예제는 커스텀으로 웹 컴포넌트를 만들 수 있는 방식에 관한 것이다.
Web Components(2): Custom Elements : NHN Cloud Meetup
웹 컴포넌트 사람들이 만든 것 여기서 많이 볼 수 있음