본문 바로가기
창고(2021년 이전)

[CSS Rendering] CSSOM & Vendor Prefix1

by 측면삼각근 2019. 11. 3.
728x90
반응형

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부분은 몇번 복습 후에 이어가는게 좋을것같다!

반응형