CSS Rendering - 3_Box Model & Absolute Position
CSS는 브라우저마다 지원 사양이 매번 다르다.
따라서 그것에 대해 vendor prefix 가 붙는 것이 대부분이다.
새로운 기능들은 vendor prefix 가 붙었다가, 안정화, 표준화가 이루어지면 vendor prefix 를 떼는 식으로 진행되는것이 관행이라 vendor perfix 처리가 까다롭다.
ex) border-radius 에 vendor prefix 를 붙일 시 작동하지 않는다.
CSSOM이 무엇일까?
이에대해서 깊이 이해하고자 한다면 브라우저 랜더링(정독)에 대한 이해가 반드시 따라야 할것같다.
DOM(Document(HTML) Object Model) - HTML을 객체화시키어 프로그래밍 가능하게 바꾸어 놓은것이 DOM이다.따라서 js를 통해서 DOM API를 이용하면 직접 HTML을 수정하지 않고 해당 element 를 고치는 일이 가능하다.
CSS도 마찬가지로 js를 통해 CSS를 수정할 수있다.
DOM에 inline style을 넣는방식이 아니라, css 그 자체를 수정 할 수 있다. CSS를 object화 하고 modeling 한것이 CSSOM이다.
CSS Object Model
<!-- style DOM Element -->
<style id="s">
.test{ background : #ff0}
</style>
<script>
const el = document.querySelecotr("#s");
// style 엘리먼트 안에 sheet라는 객체가 있다.
// CSS Style sheet 라는 객체!
// element가 아니라 그 안의 sheet가 '실체'라고 할 수 있다.
// 아래 본문 참고
// sheet 속성을 끌어내는 방법
const sheet = el.sheet;
// sheet : CSS 의 실제 sheet!
// CSS 안에 여러 rule들이 들어있다.
// 이는 cssRules 로 속성을 끌어 낼 수 있다.
const rules = sheet.cssRules;
//cssRules 는 유사배열의 형태로 반환한다. 따라서 아래와같이 낱개로 표기도 가능하다.
const rule = rules[0];
// 0번에 들어있는 것이 바로 .test{ background : #ff0}이다.
// sytle sheet안에 여러개를 정의해 놓았다면 순서대로 0, 1, 2, 3, 4 ...카운팅된다.
console.log(rule);//객체 반환
console.log(rule.selectorText);//.test
console.log(rule.style.background); // rgb(255, 255, 0)
//CSS는 rules에 추가하는것이 아니라, sheet 에 직접추가 해야함
sheet.insertRule('.red{background:red}',rules.length);
// 2번째 인자는 몇번째로 넣을지 옵션이다.
// rules.length 로 지정함으로서 가장 마지막에 넣겠다는 옵션을 준것이다.
sheet.insertRule('.blue{background:blue}',rules.length);
console.log(Array.from(rules).map(v=>v.cssText).join('\n'));
</script>
HTML라는 큰 container 을 위해서 container box(DOM) 처럼 포장되어 있다고 보면 된다.
style sheet 가 실체이지만 그 실체가 DOM안에 감춰져있고 이를 DOM으로 포장하여 HTML문서에 넣은것이다.
-> tag는 그 자체가 실체가 아니라, 일종의 container box 같은 기능이다. 실체는 그 태그 안에 들어있음.
DOM el의 style 객체가 들어있다.(inline style)
그것과 같은 객체가 rule 안에도 존재한다. CSSStyleDeclaration
rule 객체를 출력하면 아래와 같다.
type : 1이라 출력되었는데, 1번 type 의 rule 은 일반적으로 css 정의에 대한 rule이다.
Sheet는 수많은 종류의 rule 이 있다.
0 | - | 내부 객체 |
1 | STYLE_RULE | CSSOM |
2 | CHARSET_RULE | CSSOM |
3 | IMPORT_RULE | CSSOM |
4 | MEDIA_RULE | CSSOM |
5 | FONT_FACE_RULE | CSSOM |
6 | PAGE_RULE | CSSOM |
7 | KEYFRMAES_RULE | css3_animation |
8 | KEYFRMAE_RULE | css3_animation |
9 | MARGIN_RULE | CSSOM |
10 | NAMESPACE_RULE | CSSOM |
11 | COUNTER_STYLE_RULE | css3-lists |
12 | SUPPORTS_RULE | css3-conditional |
13 | DOCUMENT_RULE | css3-conditional |
14 | FONT_FEATURE_VALUES_RULE | css-fonts |
15 | VIEWPORT_RULE | css-device-adapt |
16 | REGION_STYLE_RULE | proposed for css3-regions |
17 | CUSTOM_MEDIA_RULE | meiaqueries |
_999 | - | reserved for future status |
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>CSSOM</title>
</head>
<style id="s">
.test{
background:#ff0;
}
</style>
<body>
<div class="red">red</div>
<div class="blue">blue</div>
</body>
<script>
const el = document.querySelector('#s');
// style 엘리먼트 안에 sheet라는 객체가 있다.
// CSS Style sheet 라는 객체!
// element가 아니라 그 안의 sheet가 '실체'라고 할 수 있다.
// sheet 속성을 끌어내는 방법
const sheet = el.sheet;
// sheet : CSS 의 실제 sheet!
// CSS 안에 여러 rule들이 들어있다.
// 이는 cssRules 로 속성을 끌어 낼 수 있다.
const rules = sheet.cssRules;
//cssRules 는 유사배열의 형태로 반환한다. 따라서 아래와같이 낱개로 표기도 가능하다.
const rule = rules[0];
// 0번에 들어있는 것이 바로 .test{ background : #ff0}이다.
// sytle sheet안에 여러개를 정의해 놓았다면 순서대로 0, 1, 2, 3, 4 ...카운팅된다.
console.log(rule);//객체 반환
console.log(rule.selectorText);//.test
console.log(rule.style.background);// rgb(255, 255, 0)
//()매개변수가 없으면 _ 로대신할 수 있다.
document.querySelector('.red').onclick=_=>{
//CSS는 rules에 추가하는것이 아니라, sheet 에 직접추가 해야함
sheet.insertRule('.red{background:red}',rules.length);
// 2번째 인자는 몇번째로 넣을지 옵션이다.
// rules.length 로 지정함으로서 가장 마지막에 넣겠다는 옵션을 준것이다.
sheet.insertRule('.blue{background:blue}',rules.length);
}
// 클릭시 색상이 나타난다.
// document 에 등록되어있는 sheet를 건들면 rerendering 을 한다.
// document에 등록되어있는 태그인건 어떻게 아는거지?
// 현재 -> document 바로 밑에 style태그임
// document.styleSheets[0].disabled = true
// 와 같은 형태로 사용중인 style sheet를 꺼버릴수도 있다.
//sheet.deleteRule(rules.length-1);
//마지막 속성 제거
document.querySelector('.blue').onclick=_=>{
sheet.deleteRule(rules.length-1);
sheet.deleteRule(rules.length-1);
}
// js로 style 을 변화시키는데도, inline style 이 아니라, css를 조작하는 경우 성능상의 저하가 없다.
// 비용도 낮다! tag에는 미리 style 을 적용해 놓아도 아무 문제가 없다.
// DOM을 조작하는 것 보다, CSS Object를 조작하는 방향을 지향해야함.
console.log(Array.from(rules).map(v=>v.cssText).join('\n'));
</script>
</html>
Compatibility Library
CSSOM을 이용하여 CSS를 안정적으로 통제하는 Framework를 만들것이다.
vendor prefix
- Runtime Fetch
- vendor prefix 는 실행중에 그 속성을 확인해보는 수 밖에 없다. 공식을 미리 만들어 놓을 수 없음 - Unsupported property
- graceful fail -> 지원하지 않는 속성에 대하여 스무스하게 처리하는것
- 지원하지 않는 속성이나 값이라는것이 존재한다.
ex) I7에 rgba 를 주면 브라우저에 다운이된다. 유사한 경우가 매우 많다. - Hierarchy optimaze(계층구조 최적화)
- sheet.disabled = true;
Style 클래스를 만들기 전에 필요한 utility 들이 몇개 있다.
const Style =(_=>{
//vendering prefix 문자열
//브라우저마다 다름 webkit, moz(fireFox), ms, chrome(chrome전용속성), o(오페라), khtml(리눅스)
const prop = new Map, prefix = 'webkit,moz,ms,chrome,o,khtml'.split(',');
//Map의 키는 일반적으로 쓰는 속성이 될것이고,
// 값은 브라우져의 venderprefix 에서 포함하는 진짜 이름으로 바뀔것이다.
//이러한 속성은 브라우져에서 지원하지 않는다는 것을 표현하기 위한 장치 NONE
const NONE = Symbol();
// 실행 도중에 물어보는수밖에 없음.(runtime fetch) -> 누구한테 물어볼것인가?
// document.body의 style이 그 속성을 갖고있다면 속성이 존재한다고 본다.
// 이를 위한 장치 BASE
const BASE = document.body.style;
//표준이름을 넣으면 브라우저에서 지원하는 진짜 이름이 반환되는 함수 getkey
const getkey = key =>{
//한번 계산하여 cash를 잡아 놓을것임.(prop)
//prop(캐시)안에 key가 들어있다면 캐시 안에 진짜 이름으로 줄것
if(prop.has(key)) return prop.get(key);
//key를 BASE(body.style)로 보냈더니 key가 존재하는 경우 -> 캐시 생성
if(key in BASE) prop.set(key, key);
else if(!preifx.some(v=>{
//프리픽스를 붙인 속성은 존재하는가?
// some메소드를 사용하여 배열을 돌음
const newKey = v + key[0].toUpperCase() + key.substr(1);
// newKey의 의도 : background -> webkitBackground
if(newKey in BASE){
prop.set(key, newKey);
key = newKey;
return true;
}else return false;
})){
//false일때
prop.set(key, NONE);
key = NONE; // 프리픽스로도 안되면 없는키!
}
return key;
};
//사용자들의 브라우저의 환경이 모두 다르고 속성마다도 모두 다르기때문에
//일일히 확인하는 코드 말고는 거의 효과가 없다고 봐도 된다.
return class{
//style 객체를 안고 태어남
// console.log(rule.style.background);를 고려하여.
constructor(style){this._style = style;}
get(key){
key = getkey(key);
//속성을 엊고자하면 예외사황때문에 그저 key가아니라 getKey를 통하여 엊어야한다.
if( key === NONE ) return null;
//이름이 없다면 아무것도 하지 않고 null을 return
// Unsupported property - graceful fail
return this._style[key];
// 이름이 있다면 _style 객체로 return
}
set(key, val){
key = getKey(key);
if( key !== NONE) this._style[key] = val;
//NONE일때 아무것도 하지않음. -> 건들이는 순간 브라우져 다운
return this;
}
}
})();
주석 빼고
const Style =(_=>{
const prop = new Map;
const prefix = 'webkit,moz,ms,chrome,o,khtml'.split(',');
const NONE = Symbol();
const BASE = document.body.style;
const getKey = key=>{
if(prop.has(key)) return prop.get(key);
if( key in BASE) prop.set(key, key);
else if(!prefix.som(v=>{
const newKey = v + key[0].toUpperCase() + key.substr(1);
if(newKey in BASE){
prop.set(key, newKey);
key = newKey;
return true;
}else return false;
})){
prop.set(key, NONE);
key = NONE;
}
return key;
};
return class{
constructor(style){this._style = style;}
get(key){
key = getKey(key);
if( key === NONE) return null;
return this._style[key];
}
set(key, val){
key = getKey(key);
if( key !== NONE) this._style[key] = val;
return this;
}
}
})();
실제 코드에 적용시
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>CSSOM</title>
</head>
<style id="s">
.test{
background:#ff0;
}
</style>
<body>
<div class="red">red</div>
<div class="blue">blue</div>
</body>
<script>
const Style =(_=>{
const prop = new Map;
const prefix = 'webkit,moz,ms,chrome,o,khtml'.split(',');
const NONE = Symbol();
const BASE = document.body.style;
const getKey = key=>{
if(prop.has(key)) return prop.get(key);
if( key in BASE) prop.set(key, key);
else if(!prefix.som(v=>{
const newKey = v + key[0].toUpperCase() + key.substr(1);
if(newKey in BASE){
prop.set(key, newKey);
key = newKey;
return true;
}else return false;
})){
prop.set(key, NONE);
key = NONE;
}
return key;
};
return class{
constructor(style){this._style = style;}
get(key){
key = getKey(key);
if( key === NONE) return null;
return this._style[key];
}
set(key, val){
key = getKey(key);
if( key !== NONE) this._style[key] = val;
return this;
}
}
})();
const el = document.querySelector('#s');
const sheet = el.sheet;
const rules = sheet.cssRules;
const rule = rules[0];
console.log(rule);//객체 반환
console.log(rule.selectorText);//.test
console.log(rule.style.background);// rgb(255, 255, 0)
document.querySelector('.red').onclick=_=>{
sheet.insertRule('.red{background:red}',rules.length);
sheet.insertRule('.blue{background:blue}',rules.length);
}
document.querySelector('.blue').onclick=_=>{
sheet.deleteRule(rules.length-1);
sheet.deleteRule(rules.length-1);
}
const style = new Style(rule.style);
style.set('borderRadius','20px')
.set('boxShadow','0 0 0 10px red');
console.log(Array.from(rules).map(v=>v.cssText).join('\n'));
</script>
</html>
콘솔은 아래와 같다.
이로서 style 부분이 완성되었다. Rule과 CSS부분은 몇번 복습 후에 이어가는게 좋을것같다!
'창고(2021년 이전)' 카테고리의 다른 글
[Git] pair programming work flow (0) | 2019.11.11 |
---|---|
[JS] Symbol : ES6 type (0) | 2019.11.04 |
[JS] 2-2_reduce,_pipe,_go (0) | 2019.11.01 |
[JS] 함수형 프로그래밍 - 2-1_함수형으로 전환하기 (0) | 2019.10.30 |
JS시계 API - flip clock을 이용한 출퇴근 관리 (0) | 2019.10.29 |