Frontend 공부하기 - 4일차 CSS Part3
포스트
취소

Frontend 공부하기 - 4일차 CSS Part3

프론트엔드 개발을 배우기 위해 CSS 기초부터 다지려고 합니다. Dave Gray 유튜브 강좌이고, 실습 환경은 SvelteKit + PostCSS 을 사용합니다. (4일차)

참고문서

MDN - CSS 문서

Part ♯3 Chapter 19 ~ 23

Ch19. CSS Pseudo-Classes vs Pseudo-Elements

Pseudo-Classes

  • 상태를 가리키는 선택자, ex) :hover, :active 등..

:is (특이성 10) 와 :where (특이성 0)

  • 둘다 , 로 연결된 선택자를 묶는 같은 역활을 하고 있지만
  • is 의 특이성 점수는 높고, where 는 특이성 점수를 제거하여 우선순위가 낮아진다는 차이점이 있습니다.

의사 선택자들

  • not(), nth-child() 등 …
  • :before 왼쪽 :after 오른쪽
  • odd 홀수번째, even 짝수번
1
2
3
4
5
6
7
8
.card figcaption::after {
  content: '✨';
  display: block;
}

.card figcaption::first-letter {
  font-size: 3rem;
}

Ch20. CSS Custom Variables & Dark Mode

--로 시작하며, 보통 :root 에 global 속성값으로 작성

  • :global 이란 의사코드는 MDN 에 없음
    • CSS Modules 에서 사용한 의사코드라는데, 지금은 안쓰이는듯
    • :global(:root) 라는 것도 없는 셈인데, 오류는 나지 않음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  /* || VARIABLES */
  :root {
    --ch20-bgcolor: #475569;
    --ch20-header-color: #1e293b;
  }

  div {
    background-color: var(--ch20-bgcolor);
    background-image: radial-gradient(whitesmoke, var(--ch20-bgcolor));
  }

  header,
  footer {
    background-color: var(--ch20-header-color);
  }  

Svelte 에서 CSS Variable 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
  let red = 0;
  let green = 0;
  let blue = 0;

  let rootElement;

  $: rootElement && rootElement.style.setProperty('--container-background', `rgb(${red}, ${green}, ${blue})`);
</script>

<div class="container" bind:this={rootElement}>
</div>

<style>
  :root {
    --container-background: inherit;
  }

  .container {
    background-color: var(--container-background);
  }
</style>  

Chrome 에서 prefer-color-scheme 설정하는 법

개발자 도구 > Elements > Styles (하단) > filter 옆의 페인트 아이콘

setup prefer-color-scheme setup prefer-color-scheme

Classes 이름 명명법에서 -- (더블 하이픈) 의미는 무엇인지?

참고 : CSS - BEM 구문

  • 블록, 요소, 수정자를 의미하는 BEM 은 Yandex 직원이 고안한 명명
  • 대규모 프로젝트에서 협업을 위해 사용 (공식적인 방법론은 아님)
1
2
3
.site-search {} /* Block */
.site-search__field {} /* 구성되는 Element */
.site-search--full {} /* Block 의 Modifier, 상태, 또다른 버전 등 */

사용 예제

1
2
3
4
5
6
7
8
9
10
11
<!-- full, field 등의 클래스 이름의 개별적인 의미를 알기 어렵다 -->
<form class="site-search full">
    <input type="text" class="field">
    <input type="Submit" value ="Search" class="button">
</form>

<!-- BEM 명명법 사용한 경우 -->
<form class="site-search  site-search--full">
    <input type="text" class="site-search__field">
    <input type="Submit" value ="Search" class="site-search__button">
</form>

on:click 추가한 svelte 버전

a11y 때문에 div 에 on:click 추가시 on:keyup 등도 필요하다

  • 참고: How To Fix click-events-have-key-events?
    • 번거로운 작업을 피하려면 div 대신에 button 요소를 사용하면 됨
  • squareClick 으로 active 상태를 제어한다
    • true 이면 square--highlight 클래스가 활성화됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<script lang="ts">
  const squaresCount = 12;
  const squares = Array<boolean>(squaresCount).fill(false);

  function squareClick(index: number) {
    squares[index] = !squares[index];  // 토글
    console.log(index, squares[index]);
  }
</script>

<main>
  <!-- each 블럭의 고유 id(key) 는 괄호를 이용하여 지정한다 -->
  {#each squares as active, i (i)}
    <div tabindex={i} role="button" class="square"
      on:click={() => {
        squareClick(i);
      }}
      on:keyup={() => {
        squareClick(i);
      }}
      class:square--highlight={active}
    >
      {#if i == 0}
        <p>Hey</p>
      {:else}
        💻
      {/if}
    </div>
  {/each}
</main>

<style>
  /* || FEATURES */
  .square--highlight {
    --SQUARE-BGCOLOR: cornflowerblue;
  }
</style>  

css_20_variables-svelte

Ch21. CSS Functions

clamp 함수 : 최소값과 최대값 사이의 값을 선택

  • 사용법: clamp(최소값, 비율값, 최대값)
    • 최소값과 최대값 사이에서 자동으로 비율값이 계산됨
    • font-size 에서는 백분율을 사용하지 않는다 → (vh 권장)
1
2
3
4
5
6
7
8
  :root {
    --FS: clamp(1.75rem, 3vh, 2.25rem);  /* 1.75rem ~ 2.25rem 사이에서 */
    --FS-SM: clamp(1.25rem, 2vh, 1.5rem);  /* 1.25rem ~ 1.5rem 사이에서 */
  }

  .element {
    width: clamp(200px, 50%, 1000px);  /* 400 이상 2000 이하에서 작동 */
  }

data-tooltip

data-tooltip 의 문자열이 (클릭시) 툴팁으로 표시된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<span class="tooltip" data-tooltip="This is Latin">Lorem</span>

<style>
  .tooltip {
    border-bottom: 1px dashed orange;
    position: relative;

    &:hover::before {
      content: attr(data-tooltip);
      position: absolute;
      top: -20px;
      white-space: nowrap;
      background-color: var(--DARK-COLOR);
      padding: var(--PADDING);
      border-radius: 15px;
    }
  }
</style>style>  

그외 함수들

  • min(2vw, 20px) 최소값, max(150px, 20vw) 최대값
  • calc(70% - 5px) 계산식
  • brightness(150%) 밝기, hue-rotate(180deg) hue 변경
  • repeat(4, minmax(100px, 300px)) : 반복

Ch22. CSS Animated Responsive NavBar

video 태그로 mp4 파일을 직접 플레이 할 수 있다. (HTML)

box 굴리기

3번째 박스에 대해 animation 이 2회 반복된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  .animate {
    /* animation-name: slide;
    animation-duration: 2s;
    animation-timing-function: ease-in-out;
    animation-delay: 0.1s;
    animation-iteration-count: 2;
    animation-direction: alternate;
    animation-fill-mode: forwards; */
    animation: 3s ease-in-out 0.9s 2 alternate forwards slide;
  }

  @keyframes slide {
    0%   { transform: translateX(0); }
    33%  { transform: translateX(300px) rotate(180deg); }
    66%  { transform: translateX(-300px) rotate(-180deg); }
    100% { transform: translateX(0); background-color: rebeccapurple; }
  }

pull-down 메뉴의 햄버거 아이콘

Header 에 hover 시, 메뉴 icon 이 회전하여 ‘X’ 표시가 된다.

  • ‘–’ 형태의 div 를 before 과 after 로 위아래로 쌓아 햄버거 아이콘 생성
  • 회전 rotate(180deg)
  • 햄버거 쌓기를 위한 위치 이동 translate(-20px, +12px)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  header {
    background-color: var(--HEADER-BGCOLOR);
    color: var(--HEADER-COLOR);
    position: sticky; /* 시작 위치에 고정 */

    :is(&:hover, &:focus-within) {
      .menu-icon {
        background-color: transparent;
        transform: rotate(180deg);

        &::before {
          transform: translateX(-20px) rotate(45deg);
        }
        &::after {
          transform: translateX(-20px) rotate(-45deg);
        }
      }

      nav {
        display: block; /* visibility: visible; */
      }
    }
  }

  .menu-icon,
  .menu-icon::before,
  .menu-icon::after {
    background-color: var(--HEADER-COLOR);
    width: 40px;
    height: 5px;
    border-radius: 5px;
    position: absolute;
    transition: all 0.2s ease-in-out;
  }

  .menu-icon::before,
  .menu-icon::after {
    content: '';
  }

  .menu-icon::before {
    transform: translate(-20px, -12px);
  }
  .menu-icon::after {
    transform: translate(-20px, +12px);
  }    

pull-down 메뉴의 바운스 효과

  • hover 시에만 nav 가 display: block 되며 나타난다
  • Y축 방향으로 스케일을 0 에서 1.2배 살짝 늘렸다가 원래 길이로 복원시킨다
    • 애니메이션 showMenu 을 0.5초 동안 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  nav {
    background-color: var(--HEADER-BGCOLOR);
    display: none; /* visibility: hidden; */
    transform-origin: top center;
    animation: showMenu 0.5s ease-in-out forwards;

    /* pull-down 메뉴 고정하기: position, top, left */
    position: relative; /* 상대좌표 기준 */

    a {
      display: block;

      &:hover,
      &:focus {
        transform: scale(1.2);
        transition: all 0.3s;
      }
    }
  }

  @keyframes showMenu {
    0%   { transform: scaleY(0); }
    80%  { transform: scaleY(1.2); }
    100% { transform: scaleY(1); }
  }  

메뉴 숨기기 애니메이션 (추가)

‘X’ 로 변한 햄버거 메뉴를 다시 눌렀을 때, 풀다운 메뉴가 접혀지도록 한다

  • .closeMenuBtn:focus + nav 일 때, 닫기 애니케이션이 실행된다
    • ”+” 선택자는 sibling (이웃한) 관계일 때 유효 <참고>
    • closeMenuBtn 클래스 요소가 선택되고, nav 가 block 상태인 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  <header>
    <section class="header-title-line">
      <h1>Acme Co.</h1>
      <button class="menu-button" title="Open Nav Menu">
        <div class="menu-icon" />
      </button>
    </section>

    <!-- 닫기 버튼 추가 (절대위치로 햄버거 메뉴를 덮도록 설정) -->
    <button class="closeMenuBtn" title="Close Nav Menu" tabindex="-1" />

    <nav> <!-- ... 생략 ... --> </nav>
  </header>


<style lang="postcss">

  header:focus-within nav {
    display: block; /* visibility: visible; */
    transform-origin: top center;
    animation: showMenu 0.5s ease-in-out forwards;
  }

  /* 열기 버튼을 누른 후에는 닫기 버튼이 그 자리를 차지한다 */
  header:focus-within .closeMenuBtn {
    display: block;
  }

  /* 닫기 버튼을 누른 후에는 열기 버튼을 위해 자리를 피해준다 */
  header:focus-within .closeMenuBtn:focus {
    transform: translateX(-50px);
  }

  .closeMenuBtn {
    display: none;
    background-color: transparent;
    outline: none;
    border: 1px solid red;
    position: absolute;
    top: 0.25rem;
    right: 0.5rem;
    width: 48px;
    height: 48px;
  }

  .closeMenuBtn:focus + nav {
    animation: hideMenu 0.5s ease-in-out forwards;
  }

  @keyframes hideMenu {
    0%   { transform: scaleY(1); }
    20%  { transform: scaleY(1.2); }
    100% { transform: scaleY(0); }
  }  
</style>

pull-down menu - close button animation pull-down menu - close button animation

Ch23. How to Organize CSS

컨벤션 제안

  1. 팀 규칙에 따를 것
  2. 상단에 항상 주석을 달 것
  3. 속성은 ABC 순서로 정렬할 것 (vscode 명령어 sort line ascending 활용)
  4. 대규모 프로젝트라면 BEM (Block, Element, Modifier) 명명법을 활용할 것
1
2
3
4
5
6
7
8
<header class="header">
  <h1 class="header__title">BEM</h1>
  <nav class="header__nav">
    <button class="header__btn">🚀</button>
    <button class="header__btn header__btn--secondary">🔥</button>
    <button class="header__btn header__btn--lean header__btn--border-lg header__btn--secondary">🌮</button>
  </nav>
</header>

9. Review

  • CSS Grid 는 레이아웃용 (2차원), Flexbox 는 정렬용 (1차원)
  • 자바스크립트 없이 css 만으로 동작을 제어하는 것은 어렵네 (되긴 하는데)
    • 가상의 selector 를 만들고 (보이지 않게)
    • focus 등의 상태를 구분하기 위해 위치를 이동시키고
    • 이건 뭐 객체를 이용한 프로그래밍인데, 변수가 아니라서 헷갈린다

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.