<input type="range">
이런거 쓰면 되게 복잡한 레이아웃이 나온다.
구현원리를 생각해보면, <div>
박스를 여러개 중첩해서 만들어놓은 것같지 않나??
실제로도, <div>
박스를 2-3개 겹쳐서 만든 거다.
왜 이런 것들이 가능한 거냐면, shadowDOM
이라는 기술 덕분이다.
실제로도 이게 <div>
로 구성되있는지 증명하기 위해서, 이거를 개발자 도구에서 확인해보면, 진짜로 <div>
가 숨겨져 있는 것을 확인할 수 있다.
근데, 지금은 안 보일 거다. 숨겨져 있거든요.
이걸 진짜 확인하고 싶으면 크롬 개발자도구
- 설정
- show user agent shadow DOM
켜기 이런거 체크해놓으면 됩니다.
▲ <input type="range">
안에 진짜로 <div>
3개가 숨어있다.
이거를 전문용어로 shadow DOM 이라고 하는데, 이런 것들은 개발자 편하라고 만들어 놓은 거다.
shadow DOM : "일반적으로는 볼 수 없는 숨겨진 HTML"을 의미한다.
여러분들이 직접 <div>
를 3개 만들 필요없이, 그냥 <input>
만 하나 딸랑쓰면, <div>
가 3개 생성이 되도록 코드가 짜여져있다.
그래서, 평소에는 shadowDOM 몰라도 HTML 코드 잘 짤 수있다.
여러분도 이런거 만들어서 HTML 숨길 수 있다.
이제 shadow DOM을 직접 만들어보자.
shadow DOM을 이용해서, HTML 태그 안에다가 몰래 HTML 태그를 숨겨놔보도록 하자.
이걸 왜 하냐고?? 간지나서!! 실제로 쓸 데는 없다.
예를 들어서,
<div>
안에다가 shadow DOM을 부착하고 싶다면??
shadowRoot라는 걸 하나 생성해줘야한다. 거기다가 HTML을 숨길 수가 있다.
shadowRoot를 부착하는 방법은 attachShadow();
를 써주면 된다.
attachShadow();
는 JS의 기본기능이다.
그 다음에 거기 안에다가 "커스텀 HTML 엘리먼트들을 집어넣고 싶다 " 그러면??
우리가 만들어놓은 shadowRoot라는 공간을 우선 선택해야 한다.
그 다음에 .
점을 찍어서 innerHTML
을 바꾸든 자유롭게 하면 된다.
<div class="mordor"></div>
<script>
document.querySelector('mordor').attachShadow({mode : 'open'});
document.querySelector('mordor').shadowRoot.innerHTML = '<p>심연에서왔도다</p>'
</script>
이러면 <div>
안에 몰래 <p>
를 숨길 수 있다.
attachShadow()
라는걸 써서 shadowRoot 라는 공간을 하나 만들어준다.거의 모든 <태그>
는 shadowRoot를 오픈할 수 있고
shadowRoot안에 넣은걸 전부 shadow DOM 이라고 칭하는데
이걸 굳이 써야할 이유를 모르겠고 별 장점은 없어보이지만, web component 문법과 합해야 진가를 발휘한다.
=> web component 문법과 조합을 하면, 완벽한 모듈화가 가능하다.
지난 시간에 했던 예제를 소환해보자!!
label, input을 한 단어로 축약해서 쓰고 싶다면 web components라는 문법을 갖다쓰면 된다.
그냥 정해진 class와 함수 형식에 맞춰주고, 그 안에다가 HTML을 자유롭게 작성하면 된다.
이 커스텀 HTML이 커스텀 단어 하나로 축약이 된다고 했다.
그런데, 이렇게 쓰다가 태그들에게 style을 입히고 싶어진거다. 그러면, HTML 안에다가 직접 style을 집어넣을 수도 있다.
이런 식으로 작성하면, label, input, style 태그를 한 단어로 축약했기 때문에, 이 커스텀 태그를 갖다쓸 때마다 label에 빨간색이 씌워져서 나오겠죠??
근데, 이 방법은 약간 위험하다.
왜냐면, label 태그를 다른 데에서 갖다 쓸 데에도 빨간 색이 적용되서 나온다.
=> 다른 label 태그까지 오염시키고 있다.
이러면, 컴포넌트를 만들어서 쓰는 의미가 없다. 또한, 컴포넌트가 많아지면, style이 겹치기도 한다.
이런 현상을 피하고 싶다면, class명을 태그마다 유니크하게 집어넣던지해야 되는데, 그건 또 귀찮다.
그리고 다른 컴포넌트들끼리 class 중복이 생기면 어떻하죠??
=> 이런 많은 어려움들이 존재합니다.
정확히 이렇게 해줘야 편리하게 쓸 수있다.
shadowDOM에다가 이걸 다 집어넣는 거다.
커스텀 태그들을 shadowDOM이라는 공간에 집어넣게 되면,
<style>
같은 경우, shadowRoot 안에서만 존재한다. 즉, CSS를 막짜도 된다.
- 정리하면,
- CSS로 class를 만들어서 해결한다고 해도, 다른 곳과 class가 겹치면 문제가 생기고 아무튼 귀찮은 문제들이 발생한다.
그럴 땐 스타일을 shadow DOM 열어서 거기 집어넣으면 된다.
왜냐면 shadow DOM에 있는 스타일은 밖에 영향을 끼치지 않아서 그렇다.
class 클래스 extends HTMLElement {
connectedCallback() {
this.attachShadow({mode : 'open'});
this.shadowRoot.innerHTML = `<label>이름을 입력하쇼</label><input>
<style> label { color : red } </style>`
}
}
customElements.define("custom-input", 클래스);
<custom-input></custom-input>
<label>오 이제 바깥건 안빨개짐</label>
스타일과 태그들을 전부 shadow DOM으로 집어넣어놨더니, 진짜 다른 태그들 스타일을 오염시키지 않는다.
그래서 대부분 Web Component 만들 때 shadow DOM을 활용한다.
이래야 진정한 의미의 html 모듈화 개발이 가능하다.
다른 모듈들이 서로 영향을 끼치는걸 막을 수 있으니까...
그런데, 지금처럼 이렇게 코드를 짜면, HTML 코드가 되게 드럽게 않나요??
그래서, 이 HTML을 다른 곳으로 뺼 수가 있다.
HTML을 이런 태그에다가 임시로 보관했다가 그걸 this.shadowRoot.innerHTML
에 붙여넣기하는 식으로 개발이 가능하다.
이런 기능을 실현시켜주는 HTML 태그를 <template>
라고 한다.
<template>
태그컴포넌트 만들 때 html이 너무 길어지면, <template>
태그에 잠깐 보관해두고 집어넣을 수도 있다.
<template>
에 작성한 HTML 태그들은 HTML 페이지에 렌더링되지 않는다.
그래서, shadowDOM이라던지, web components의 커스텀 엘리먼트 안에다가 집어넣고 싶은 긴 HTML 태그들이 있다고 하면, 일단 <template>
로 감싼다.
그 다음 <template>
에 id를 지정하자!!
<custom-input></custom-input>
<template id="template1">
<label>이메일을 입력하쇼</label><input>
<style>label { color : red }</style>
</template>
<script>
class 클래스 extends HTMLElement {
connectedCallback() {
this.attachShadow({mode : 'open'});
this.shadowRoot.append(template1.content.cloneNode(true));
}
}
customElements.define("custom-input", 클래스);
</script>
<template>
은 특수한 태그인데 여기에 적은 html은 렌더링되지 않는다.
그래서 거기에 html들 잠깐 보관하고
this.shadowRoot.append(template1.content.cloneNode(true))
이런 식으로 집어넣으면 된다.
그냥 <template>
태그 사용법이다.
이러면 html이 길어져도 이쁘게 코드짤 수 있겠다.
근데, 실제로 웹 개발을 이렇게 생으로 하는 것보다 라이브러리 갖다 쓰는게 더 좋다.
나는 더 나아가서 웹 앱을 만들고 싶다??
그러면, React나 Vue를 쓰면 된다.
그냥 addEventListener
아무데나 코드짜서 부착하면 된다.
심지어 shadow DOM에도 이벤트리스너 부착 가능하다.
알아서 하자.
<custom-input></custom-input>
<template id="template1">
<label>이메일을 입력하쇼</label><input>
<style>label { color : red }</style>
</template>
<script>
class 클래스 extends HTMLElement {
connectedCallback() {
this.attachShadow({mode : 'open'});
this.shadowRoot.append(template1.content.cloneNode(true));
let el = this.shadowRoot.querySelector('label');
el.addEventListener('click', function(){
console.log('클릭함')
})
}
}
customElements.define("custom-input", 클래스);
</script>
shadow DOM 안의 label 태그를 누르면 콘솔창에 '클릭함' 출력하는
이벤트리스너를 부착해봤다.
=> 이것도 복잡해보이면?? 함수도 빼면 된다.
이런 식으로 개발하면 자바스크립트도 컴포넌트안에 담아서 보관할 수도 있다.
이제 원하는 곳에서 class 만 export 해서 가져다 쓰면 컴포넌트로 모듈식 개발이 가능하다.