코드스피츠 CSS Rendering 5회차 정리 (STEP 46, 47)

Semantic Web & CSS Query (microdata, dataset 활용)

image

STEP 46, 47

코드스피츠76 CSS Rendering - 5회차 강의를 보고 정리한 내용입니다.

✍️ Semantic Web

Semantic Web에서 CSS를 어떻게 쿼리처럼 쓸 수 있을까?

  • HTML 문서는 Semantic하게 구성되어있다.
  • Semantic Web = 의미론적 웹 = HTML 태그를 하나의 문서로서 의미를 갖도록 태깅하자는 철학
  • 웹 표준에서 권장하는 방식: HTML은 의미론적으로 작성, 화면에 보이는 모양은 CSS로 조정
  • Semantic Web을 사용하는 이유: 검색엔진이 HTML을 읽어들였을 때 마치 데이터베이스처럼 문서로부터 유의미한 것들을 추출해 다양한 검색 결과를 나타내기 위해서(기계 친화적, 크롤러 친화적인 웹을 위함)
  • 오늘 배우는 것의 핵심: 이러한 Semantic Web에서 CSS를 어떻게 사용하는 것이 좋을까에 대한 고민

  • DOM -> 의미를 갖도록 태깅

    • 의미상 무엇인가(Semantic)를 기준으로 태그를 만들 것
      • 엘리먼트가 화면에 어떻게 보일지, 위치가 어딘지에 상관없이 태깅하기(요소가 태그상으로는 제일 먼저 작성됐지만 화면에서는 맨 아래 있을 수도 있음)
      • 줄을 나누기 위해 div, p태그를 함부로 쓰거나, 장식을 위해 img 태그를 넣는 등의 행위를 허용하지 않아야 한다.
    • Semantic Web을 잘 사용하면 태그 수가 줄어드는 효과 O
    • Ex) 시간을 표현 -> time 태그 / 제목 -> header 태그 / 글 하나하나는 -> article
  • CSS Selector -> DOM의 구조에 밀접하게 선택

    • 보통 CSS Selector는 Sematic과 무관하게 DOM 구조에 밀접하게 작성된다.
    • Ex) a 태그 안에 p -> 종종 이런식으로 선택함
    • CSS Selector로 Semantic Web을 만드려는 것은 잘못된 시도처럼 보인다. (뒤에서 자세히 설명할 것)
  • CSS 자체 -> 의미와 무관한 스타일

    • 그래픽스 렌더링 엔진과 관련됨(그림을 어떻게 그릴지)
    • Ex) background-color: yellow

CSS SelectorDOMCSS(자체)를 잇는 역할을 한다. 이를 잘 작성하면 그 둘이 유기적으로 연결되고 유지보수가 쉬워진다.

보통은 문서의 모양(DOM 구조)에 따라 CSS Selector를 작성하는데, 이때 중간에 엘리먼트를 넣는 등의 수정이 일어나면 CSS Selector가 모두 깨지는 일이 발생할 수 있다. 결국 CSS Selector로 인해 DOM을 건드리지 못하는 상황까지 생겨버린다.(CSS Selector를 잘 설계해야하는 이유)

  • 문제점

    • 1️⃣ DOM을 스타일에 맞춰 제작하는 것 -> Semantic Web에서 DOM은 의미를 갖도록 태깅해야한다.
    • 2️⃣ (1번의 문제로 인해) 태그의 변화가 스타일을 깨먹음
    • => 결국 HTML, CSS를 유지보수하기 어려워진다.


  • 해결방법
    • Semantic Selector: CSS Selector도 Semantic하게 짜보자
    • DOM의 구조에서 벗어나자: Selector에 오직 class만 써도 반은 해결된다.(p > a > ... 이런건 유지보수가 힘듦)
    • ➕ 오늘 배우는 내용들을 숙지할 것

CSS Attribute Selector

  • CSS Attribute Selector는 해당 엘리먼트의 속성(attribute)을 기반으로 엘리먼트를 선택할 수 있게 해준다.
  • 대괄호([])로 시작한다.

사용 가능한 여러 문법 맛보기

  • [attr] - 속성이 존재함
    • Ex) input[type] input 태그 중에 type이 존재하는 엘리먼트들 모두를 선택함
  • [attr=val] - 값(val)과 일치
  • [attr~=val] - 공백으로 구분된 단어로 포함되면 일치
    • Ex) HTML에 속성을 줄 때 공백(Space)로 여러개를 줄 수 있음을 기억하자. 그렇게 준 속성안에 일치하는 값이 있는지 찾는다.
  • [attr:=val] - 일치하거나 뒤에 -가 붙을 때
  • [attr*=val] - 값이 포함될 때
  • [attr^=val] - 값으로 시작될 때 # 정규식에서 온 표현(^=시작, $=끝)
  • [attr$=val] - 값으로 끝날 때

출처: https://drafts.csswg.org/selectors-3/#attribute-selectors


Query Style Selector

CSS Query는 SQL Query와 굉장히 비슷하게 작성한다.

SQL Query: select * from table where field conditions..

CSS Query: base selector[field conditions][..][..]..

  • base selector: table과 같은 역할. 얻고자하는 DOM 그룹을 의미
  • field conditions: 조건 (나열해서 작성한다)

내가 원하는 DOM의 그룹에서 원하는 엘리먼트들만 고르는 작업이다.

Query Style Selector 예제

// 📁scss 파일
#join {
  [name="userid"] {
    border: 1px solid #f00;
  }
  [name="pw"] {
    border: 1px solid #0f0;
  }
  [name="nick"] {
    border: 1px solid #00f;
  }
}
<!-- 📁html 파일 -->
<form id="join">
  <input type="text" name="userid" />
  <input type="password" name="pw" />
  <input type="text" name="nick" />
</form>
  • 기존 방법: DOM 구조와 관련된 방법들 ex) 선택하고싶은 태그에 id나 class를 모두 부여하거나 혹은 form 태그 안에 몇번째 태그인지 식별하는 방식으로 셀렉터를 작성
  • 제안 방법: CSS Query Selector 사용
    • (위 html 코드 참고)join을 하나의 table로 보고 질의(query)를 하자
    • name과 같이 DOM 구조나 디자인에 영향받지 않는 서버와 합의된 내용을 선택하도록 한다. 이렇게 데이터 구조에 근거를 둔 셀렉팅이 적절하다.

💡 Sass에선 selector를 중첩하여 쓸 수 있다. CSS였다면 다음과 같이 작성해야한다. Sass를 사용하면 위 예제처럼 코드의 중복을 없애 셀렉터 수정시 실수를 방지할 수 있다.

#join [name="userid"] {
  border: 1px solid #f00;
}
#join [name="pw"] {
  border: 1px solid #0f0;
}
#join [name="nick"] {
  border: 1px solid #00f;
}

결국, 태그에서 진짜 의미를 가진 것을 추출해 거기에 스타일을 주는 것이 핵심이다.


1️⃣ HTML5 Microdata

HTML Semantic 스펙인 microdata를 본격적으로 알아보자. 아래 속성들을 사용하자는 것이 microdata의 핵심이다.

  • itemscope - 적용범위 설정
    • 어떤 div에 scope를 지정하면 그 안에만 적용이 된다.
  • itemtype - 스키마 설정
    • 스코프에 type을 지정
  • itemid - 특정 id 부여
    • 스코프 내부 여러 아이템들에 특정 id 부여
  • itemprop - 속성명
  • content - 비가시적일 때 값을 설정
  • value - 가시적인 값이 원하는 값이 아닐 때
  • itemref - scope 계층구조 안에 없을

출처: https://www.w3.org/TR/microdata


Schema.org

Schema.org 사이트에서는 microdata 포맷으로 된 스키마(데이터 구조)를 공유하고 있다. 공식적인 표준은 아니지만 많은 사람들이 따르고 개선하고 있는 문서이다.

우리가 사용해볼 스키마들

💡 우리가 임의로 스키마를 정의해도 되지만, 이 경우 정의 후에 웹에 url로 스키마 포맷을 공유해야한다.


microdata 예제

  • 일단 먼저 html 틀을 작성해보자
<!-- 화면에 보이는 디자인 문서에 가까움 -->
<body>
  <h1>CodeSpitz76 - 5</h1>
  <nav>Home</nav>
  <p>코드스피츠76 5회차 수업은 css를 쿼리화하여 사용하는 방법을 다룹니다.</p>
  <ul>
    <li>
      <h2>HTML5 MicroData</h2>
      <p>마이크로데이터에 대한 개념과 예제</p>
    </li>
    <li>
      <h2>HTML5 DataSet</h2>
      <p>데이터셋에 대한 개념과 예제</p>
    </li>
  </ul>
  <footer>
    <div>MIT</div>
    <div>Bsidesoft co.</div>
  </footer>
</body>


  • microdata 적용을 위해 위의 html 파일 아래와 같이 수정
<!-- 위 HTML에 microdata 적용 -->
<!-- microdata를 적용하기 전보다 문서화, 데이터베이스화 됨 -->
<body itemscope itemtype="http://schema.org/WebPage">
  <h1 itemprop="name">CodeSpitz76 - 5</h1>
  <nav itemprop="breadcrumb">Home</nav>
  <p itemprop="description">
    코드스피츠76 5회차 수업은 css를 쿼리화하여 사용하는 방법을 다룹니다.
  </p>
  <ul>
    <li
      itemprop="mainEntity"
      itemscope
      itemtype="http://schema.org/CollegeOrUniversity"
    >
      <h2 itemprop="name">HTML5 MicroData</h2>
      <p itemprop="description">마이크로데이터에 대한 개념과 예제</p>
    </li>
    <li
      itemprop="mainEntity"
      itemscope
      itemtype="http://schema.org/CollegeOrUniversity"
    >
      <h2 itemprop="name">HTML5 DataSet</h2>
      <p itemprop="description">데이터셋에 대한 개념과 예제</p>
    </li>
  </ul>
  <footer>
    <div itemprop="license">MIT</div>
    <div itemprop="publisher">Bsidesoft co.</div>
  </footer>
</body>
  • 코드 설명

    • body에 itemscope를 줌으로써 하나의 스코프로 지정
    • 스코프를 지정하면서 type(itemtype)을 같이 준다. type은 uri로 준다.(고유하게) 이로써 type이 무엇인지 알 수 있다.
    • li에 itemprop 속성값으로 mainEntity(여러개 지정 가능)를 주고, 새로운 스코프를 지정했다. 그럼 li안이 또 다른 스코프가 된다.
    • <h1 itemprop="name">CodeSpitz76 - 5</h1>의 name은 WebPage 스코프의 name이지만, li 내부 <h2 itemprop="name">HTML5 MicroData</h2>의 name은 CollegeOrUniversity의 name으로 전자와 완전히 다른 name이다.
    • license, publisher는 WebPage 스코프에 소속된 속성이다.
    • 이 문서는 Website(WebPage 타입)를 나타내는 문서이고 각각은 논문 아이템들(CollegeOrUniversity 타입)인 것이다.

이처럼 microdate포맷은 HTML의 구조와 일치하면서 문서에 의미(Semantic)를 부여할 수 있게 한다.


위 그림을 보면 스코프의 의미가 무엇인지 명확하게 이해할 수 있을 것이다.


위 예제를 기반으로 Semantic Query를 짜면 아래와 같다. 쿼리를 보면 DOM 구조가 아예 나오지 않는다.(DOM 구조에서 벗어남!)

microdata 정리

  • microdata를 적용하면 유지보수가 용이해진다. 위에서 본 예제 코드에서 디자인이 개편되더라도 작성자를 고치고 싶다면 WebPage의 publisher로 가서 고치면 된다.
  • 또한 예를 들어 다크 테마로 바꾸고 싶은 경우에도 셀렉터가 아닌 내부 코드만 고치면 테마를 쉽게 바꿀 수 있다.
  • Semantic화가 잘 되어있는 문서는 JSON으로 번역하지 않고 HTML 채로 서버로 보내도 된다. 왜냐하면 microdata DOM parser도 사용할 수 있게 표준화돼있기 때문이다.
  • 아까 봤던 코드에서 name으로 짰던 쿼리보다 microdata를 활용한 이 방법이 더 Semantic한 코드이다.
  • DOM 의존성을 벗어나는 방법은 attribute selector를 쓰는게 아니라 이렇게 DOM 구조에 영향을 받지 않는 셀렉팅 방법을 쓰는 것이다.

2️⃣ HTML5 dataset

  • HTML5에서는 microdata 외에도 커스텀 속성을 사용할 수 있는 dataset가 있다.

  • W3C HTML5 Validator를 통과한다는 특징이 있다.

    • 예를 들어 일본에 웹사이트 납품하는 경우를 생각해보자. 일본의 경우 HTML, CSS가 W3C Validator 기준을 통과해야 납품이 가능하다. 태그에 마음대로 커스텀 속성을 쓰면 납품할 수 없다. 하지만 그래도 태그에 커스텀 속성을 넣고 싶다면? 표준적인 커스텀 속성을 위한 속성 확장자인 dataset을 쓰면 된다.
  • dataset을 사용해도 micro-data 처럼 DOM구조, 변해야 할 요소와의 의존성을 낮추면서 Semantic하게 코드를 작성할 수 있다.


dataset 사용법

  • data- 뒤에 원하는 속성 이름을 붙인다.
  • 단어가 더 필요하면 -로 잇는다. ex) <div data-memberId></div>가 아니라 <div data-member-id></div> (html은 모두 소문자로 적용됨)
  • 자바스크립트에서는 element.dataset.속성명 으로 사용 가능 ex) console.log(div.dataset.memberId)

dataset 예제

<!-- 📁 index.html -->
<body data-type="http://schema.org/WebPage">
  <h1 data-name>CodeSpitz76 - 5</h1>
  <nav data-breadcrumb>Home</nav>
  <p data-description>
    코드스피츠76 5회차 수업은 css를 쿼리화하여 사용하는 방법을 다룹니다.
  </p>
  <ul>
    <li data-mainEntity data-type="http://schema.org/CollegeOrUniversity">
      <h2 data-name>HTML5 MicroData</h2>
      <p data-description>마이크로데이터에 대한 개념과 예제</p>
    </li>
    <li data-mainEntity data-type="http://schema.org/CollegeOrUniversity">
      <h2 data-name>HTML5 DataSet</h2>
      <p data-description>데이터셋에 대한 개념과 예제</p>
    </li>
  </ul>
  <footer>
    <div data-license>MIT</div>
    <div data-publisher>Bsidesoft co.</div>
  </footer>
</body>
  • 이렇게 data-name만 쓴 부분처럼 값이 없는 선언 상태도 허용하는게 dataset의 장점이다. (=로 데이터값을 주지 않아도 괜찮다는 것)
  • microdata를 쓸 때처럼 일일이 type을 알 필요가 없다.


/* 📁 style.scss */
[data-type$="WebPage"] {
  color: #900;
  [data-name] {
    font-size: 20px;
    padding: 0;
    margin: 10px 0;
  }
  [data-breadcrumb] {
    font-size: 11px;
    margin: 0 10px;
    color: #234a7f;
  }
  [data-description] {
    font-size: 14px;
    line-height: 20px;
    color: #444;
  }
  ul {
    list-style: none;
    padding: 0;
  }
  [data-mainEntity] {
    padding: 0 20px;
    margin: 10px;
    border: 1px solid #999;
    border-radius: 20px;
  }
  [data-license] {
    font-size: 11px;
    color: #999;
  }
  [data-publisher] {
    font-size: 12px;
    font-weight: bold;
    font-family: sans-serif;
  }
}
[data-type$="CollegeOrUniversity"] {
  [data-name] {
    font-size: 15px;
  }
  [data-description] {
    font-size: 12px;
    line-height: 15px;
  }
}

scss를 이용한 다차원 쿼리 예제

정적인 사이트의 구조를 포맷팅하는 다차원 쿼리

  • 코드에서 구현하고자하는 것

    • 계속 개근인 사람 -> 초록색 표시
    • 계속 결석인 사람 -> 빨간색 표시
    • 한번이라도 출석한 사람 -> 노란색 표시
  • 코드 설명

    • CSS module 3,4 는 본문 인식을 할 수 없다. (아래 코드에서 ‘출석’, ‘결석’ 인식 불가능하다는 말) -> 하지만 CSS module 5에서 기능이 추가될 예정이다.
    • CSS에서 ,(콤마): 여러개에 동시적용
    • selector +: 옆에 있는 태그를 인식. nextSibling과 연결한다. (sibling은 형제자매란 뜻. previousSibling, nextSibling 등의 개념이 있음)

💡 1회차에서 언급했듯, CSS는 모듈별로 스펙이 존재한다. CSS3 이런건 없다! CSS는 공식 스펙은 2.1에서 멈췄고 그 이후론 CSS 모듈 스펙으로 바뀌어서 개별 모듈마다 버전이 따로 존재한다.

<!-- 📁 index.html -->
<h1 data-name>CodeSpitz76</h1>
<table>
  <thead>
    <th>번호</th>
    <th>이름</th>
    <th>1강</th>
    <th>2강</th>
    <th>3강</th>
    <th>개근</th>
  </thead>
  <tr data-id="1">
    <td>1</td>
    <td data-name="이수박">이수박</td>
    <td data-1="on">출석</td>
    <td data-2="on">출석</td>
    <td data-3="on">출석</td>
    <td></td>
  </tr>
  <tr data-id="2">
    <td>2</td>
    <td data-name="김레몬">김레몬</td>
    <td data-1="on">출석</td>
    <td data-2="on">출석</td>
    <td data-3="off">결석</td>
    <td></td>
  </tr>
  <tr data-id="3">
    <td>3</td>
    <td data-name="박오렌">박오렌</td>
    <td data-1="on">출석</td>
    <td data-2="off">결석</td>
    <td data-3="off">결석</td>
    <td></td>
  </tr>
  <tr data-id="4">
    <td>4</td>
    <td data-name="서라임">서라임</td>
    <td data-1="off">결석</td>
    <td data-2="off">결석</td>
    <td data-3="off">결석</td>
    <td></td>
  </tr>
</table>
// 📁 style.scss
[data-1="on"],
[data-2="on"],
[data-3="on"] {
  color: green;
}
[data-1="off"],
[data-2="off"],
[data-3="off"] {
  color: red;
}

/* 초록색을 표시하는 코드 */
// 출석,출석,출석인 경우 그 옆에 있는 td에 스타일을 주겠다는 것
[data-1="on"] + [data-2="on"] + [data-3="on"] + td {
  background: green;
}

/* 빨간색을 표시하는 코드 */
[data-1="off"] + [data-2="off"] + [data-3="off"] + td {
  background: red;
}

/*노란색 표시 로직 (scss 문법을 떠올려보기)*/

// 1) on/off/off, off/on/off와 같은 경우
// i와 j를 돌면서 i==j인 경우는 on, 아닌 경우는 off를 ret 빈 문자열인 ret 변수에 더해가면서 결과적으로 ret 문자열에 모든 경우의 수가 담김.
@for $i from 1 through 3 {
  $ret: "";
  @for $j from 1 through 3 {
    @if $j == $i {
      $ret: $ret + "[data-" + $j + '="on"]';
    } @else {
      $ret: $ret + "[data-" + $j + '="off"]';
    }
    $ret: $ret + "+";
  }
  $ret: $ret + "td";
  #{$ret} {
    background: yellow;
  }
}

// 2) on/on/off 같은 경우
@for $i from 1 through 3 {
  $ret: "";
  @for $j from 1 through 3 {
    @if $j == $i {
      $ret: $ret + "[data-" + $j + '="off"]';
    } @else {
      $ret: $ret + "[data-" + $j + '="on"]';
    }
    $ret: $ret + "+";
  }
  $ret: $ret + "td";
  #{$ret} {
    background: yellow;
  }
}

위 scss파일을 css파일로 컴파일해서 실행결과를 확인해보자. 변환된 css파일을 보면 일일이 css코드를 짜는 것보다 scss 문법을 사용하여 쿼리를 짜는 것이 훨씬 빠르고 좋은 것을 알 수 있다.

  • 고민🤔
    • 위 코드에서 2)on/on/off 같은 경우를 직접 작성했는데 만약 가짓수가 3개가 아닌 4, 5개로 늘어나면 코드가 제대로 동작하지 않는다. 다르게 짜야할 것 같다.

✍️ SCSS > CSS 변환 방법

1️⃣ scss 설치

# global로 설치
$ npm install -g node-sass

2️⃣ scss > css 변환

$ node-sass style.scss > style.css

# or
# scss 파일이 변경되는 것을 감지하여 자동으로 css 파일을 수정하는 명령
$ node-sass style.css --watch style.css


컴파일 도중 마주친 에러

Case 1) VSCode 터미널에서 scss 관련 명령어가 실행되지 않음


마무리

  • 생명력이 길고, 유지보수가 용이해지는 CSS Selector는 어떻게 작성해야할까에 대한 고민이 필요하다.
  • Semantic 웹 기반으로 Selector를 작성하는 것은 그 많은 요령들 중 하나일 뿐이다.
  • 강조했듯 HTML5에서 id, class를 의미론으로 사용하지 말고 디자인을 위해 비워두자. 변하지 않는 데이터에 대해서는 micro-data나 dataset을 사용하자.

Comment

References

팀원들 결과물‍