UI Interaction

Web Tech
Concert

Web Tech
Concert

Web Tech
Concert

Visual
Studio
Code

Visual Studio Code, Microsoft

Code Editor

Live
Server

Extension

Part 1 . CSS Animation

Web Tech
Concert

CSS3 Animation

Fade Slide In from Top

Fade Slide In from Left

Fade In

Fade Slide Up from Bottom

Overview

1

2

3

4

5

6

7

Sequence Animation

CSS3 Animation

Fade In

@keyframes fade-In {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
.app-header {
  opacity: 0;
  animation: 
    fade-in   /* name */
    0.35s     /* duration */
    0.4s      /* delay */
    ease-out  /* timing function */
    forwards; /* fill mode */
}

CSS3 Animation

Fade Out

@keyframes fade-Out {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
.app-header {
  animation: 
    fade-out
    0.35s                  
    0.4s                   
    ease-out               
    forwards;              
}

CSS3 Animation

Fade Slide In From Top

@keyframes fade-slide-in-from-top {
  0% {
    transform: translateY(-4rem);
    opacity: 0;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}
.app-header {
  opacity: 0;
  animation: 
    fade-slide-in-from-top
    0.35s                 
    0.4s                  
    ease-out              
    forwards;             
}

CSS3 Animation

Fade Slide In From Left

@keyframes fade-slide-in-from-left {
  0% {
    transform: translateX(-4rem);
    opacity: 0;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}
.app-header {
  opacity: 0;
  animation: 
    fade-slide-in-from-left
    0.35s                  
    0.4s                   
    ease-out               
    forwards;              
}

CSS3 Animation

Fade Slide In From Bottom

@keyframes fade-slide-in-from-bottom {
  0% {
    transform: translateY(4rem);
    opacity: 0;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}
.app-header {
  opacity: 0;
  animation: 
    fade-slide-in-from-bottom
    0.35s                  
    0.4s                   
    ease-out               
    forwards;              
}

CSS3 Animation

Fade Slide In From Right

@keyframes fade-slide-in-from-right {
  0% {
    transform: translateX(4rem);
    opacity: 0;
  }
  100% {
    transform: none;
    opacity: 1;
  }
}
.app-header {
  opacity: 0;
  animation: 
    fade-slide-in-from-right
    0.35s                  
    0.4s                   
    ease-out               
    forwards;              
}

CSS3 Animation

Timing Function | Cubic Bezier

CSS3 Animation

.app-header {
  opacity: 0;
  animation: 
    fade-slide-in-from-top
    0.35s                 
    0.4s
    /* timing-function */                           
    cubic-bezier(0, 0, 0.06, 1.24)
    forwards;             
}

Timing Function | Chrome Browser DevTools

CSS3 Animation

@keyframes transform-none {
  to {
    transform: none;
    opacity: 1;
  }
}

Effective Management

.app-header {

  /* 0% */
  opacity: 0;
  transform: translateY(-4rem);

  /* 100% */
  animation: transform-none 0.35s 0.4s ease-out forwards;

}

CSS3 Animation

Effective Management

.brand {
  /* 0% */
  opacity: 0;
  transform: translateX(-4rem);
  /* 100% */
  animation: transform-none 0.3s 0.7s cubic-bezier(0, 0, 0.23, 1.43) forwards;
}

button[title="메뉴 열기"] {
  /* 0% */
  opacity: 0;
  transform: translateX(4rem);
  /* 100% */
  animation: transform-none 0.45s 0.65s cubic-bezier(0, 0, 0, 1) forwards;
}

CSS3 Animation

.ediya-menu__item {
  /* 0% */
  opacity: 0;
  transform: translateY(4rem);
  /* 100% */
  animation: transform-none 0.3s 0.85s cubic-bezier(0.6, 0.01, 0.16, 1) forwards;
}

/* 시간 차 애니메이션 */
.ediya-menu__item:nth-child(1) { animation-duration: 0.8s }
.ediya-menu__item:nth-child(2) { animation-duration: 1.2s }
.ediya-menu__item:nth-child(3) { animation-duration: 1.6s }
.ediya-menu__item:nth-child(4) { animation-duration: 2.0s }

CSS3 Animation

CSS3 Animation

깜박이는 애니메이션은 주의력 결핍 과잉 행동 장애 (ADHD)와 같은
인지 적 문제가 있는 사람들에게 문제가 될 수 있습니다. 또한, 특정 종류의
운동은 전정 장애, 간질, 편두통조영제 민감도를 유발할 수 있습니다.

접근성 고려

CSS3 Animation

CSS3 Animation

A11y Class 활용 (코드 재사용을 위한)

UI Interaction

Needs JavaScript Programming

CSS 애니메이션을 사용해 UI 모션을 구현 적용할 수
있으나, 사용자의 요구에 맞는 동작을 작동시키려면
JavaScript 프로그래밍이 필요합니다. 이어서 Java-
Script 프로그래밍 방법에 대해 학습합니다.

Part 2. DOM Script

Web Tech
Concert

DOM Script

Document Object Model + ECMAScript (JavaScript)

<!DOCTYPE html>
<html lang="ko-KR">
  <head>
    <meta charset="UTF-8" />
    <title>이디야(Ediya) UI</title>
  </head>
  <body>
    <header class="app-header">
      <h1 class="brand">
        <a 
          href="https://www.ediya.com/" 
          target="_blank">
          <span 
            class="a11y-hidden" 
            lang="en">
            EDIYA COFFEE
          </span>
        </a>
      </h1>
    </header>
  <body>
</html>

접근 / 조작 (이벤트 핸들링)

JavaScript Behaiver

Overview

Click Event Handing

Show (Open) / Hide (Close)

Click Event Handing

Off Canvas Menu

Slide In from Right
Slide Out from Left

JavaScript Behaiver

Overview

Analysis

<ul class="ediya-menu reset-list">
  <li class="ediya-menu__item">
    <a href="#">
      <figure>
        <img
          src="./images/ICED-벚꽃라떼.png"
          width="323"
          height="323"
          alt
        />
        <figcaption>ICED 벚꽃라떼</figcaption>
      </figure>
    </a>
    <div hidden class="ediya-menu__item--detail"> ... </div>
  </li>
</ul>

HTML Markup & UI

<ul class="ediya-menu reset-list">
  <li class="ediya-menu__item">
    <a href="#" role="button" aria-haspopup="dialog" aria-pressed="false">
      <figure>
        <img
          src="./images/ICED-벚꽃라떼.png"
          width="323"
          height="323"
          alt
        />
        <figcaption>ICED 벚꽃라떼</figcaption>
      </figure>
    </a>
    <div role="dialog" aria-modal="true" hidden class="ediya-menu__item--detail"> 
      ... 
    </div>
  </li>
</ul>

using WAI-ARIA for Accessibility (Dialog)

HTML Markup & UI

버튼 역할, 다이얼로그(팝업)을 가짐, 누르지 않은 상태

다이얼로그(팝업) 역할, 모달(Modal)로 설정

/* DOM API : 문서에서 요소를 찾는 방법
 * const 문서_객체 = 문서.선택자로_요소_찾기('선택자')
 */

// 메뉴 아이템 버튼 찾기
const menuItemButton = document.querySelector('.ediya-menu__item a')

JavaScript Behaiver

Selecting DOM Element : querySelector()

// 유틸리티 함수 el 정의 (ES6 화살표 함수)
const el = (selector) => document.querySelector(selector)

// 유틸리티 함수를 사용해 문서에서 요소 찾기
const menuItem = el('.ediya-menu__item')
const menuItemButton = el('.ediya-menu__item a[role="button"]')
const menuItemModal = el('.ediya-menu__item .ediya-menu__item--detail')
/* DOM API : 문서에서 요소를 찾는 방법
 * const 문서_객체(들) = 문서.선택자로_요소_모두_찾기('선택자')
 */

// 메뉴 아이템(들) 버튼 찾기
const menuItemButtons = document.querySelectorAll('.ediya-menu__item a')

JavaScript Behaiver

Selecting DOM Elements : querySelectorAll()

// 유틸리티 함수 elList 정의
const elList = selector => document.querySelectorAll(selector)

// 유틸리티 함수를 사용해 문서에서 요소(들) 찾기
const menuItems = elList('.ediya-menu__item')
const menuItemButtons = elList('.ediya-menu__item a[role="button"]')
const menuItemModals = elList('.ediya-menu__item .ediya-menu__item--detail')
/* DOM API : 사용자가 문서 요소를 클릭하면 동작하도록 하는 방법
 * 문서_객체.addEventListener('이벤트_유형', 실행될_함수)
 */

// 클릭 시, 실행 될 함수
const onClickAction = (event) => {
  console.log('이벤트 유형:', event.type)
}

// 첫번째 메뉴 아이템 버튼 클릭 시, 연결된 함수 실행
menuItemButtons[0].addEventListener('click', onClickAction)

JavaScript Behaiver

Click Event Handling

/* DOM API : 문서 객체(들)을 순환하여 이벤트에 핸들러 연결
 * 문서_객체(들).forEach(핸들러)
 */

// 메뉴 아이템(들) 버튼 클릭 시, 연결된 함수 실행
menuItemButtons[0].addEventListener('click', onClickAction)
menuItemButtons[1].addEventListener('click', onClickAction)
.
.
.
menuItemButtons[n].addEventListener('click', onClickAction)

JavaScript Behaiver

Loop Event Handling

// 메뉴 아이템(들)을 순환하여 각 아이템 버튼 요소에 클릭 이벤트 핸들러 연결 설정
// NodeList.prototype.forEach() : IE 미지원. IE 고려 시 Fallback 사용. (mzl.la/2Nc2uLP)
menuItemButtons.forEach(button => button.addEventListener('click', onClickAction))

JavaScript Behaiver

Todo 1-1

이디야 메뉴 아이템(.ediya-menu__item)을 문서에서 모두 수집한 후,
메뉴 아이템 내부에서 a 요소를 찾아 클릭 이벤트를 연결한다.

이벤트에 연결된 함수(핸들러)는 음료 상세 설명 패널을 화면에 표시한다.

  - 상세 설명 패널(.ediya-menu__item--detail) hidden 속성 값 제거.

  - 상세 설명 패널에 is-active 클래스 추가.

JavaScript Behaiver

Programming : Show Detail Modal

elList('.ediya-menu__item a[role="button"]').forEach(button =>
  button.addEventListener('click', showDetailModal)
)
const showDetailModal = e => {
  const button = e.currentTarget            // 클릭한 버튼([role="button"] 요소)
  const modal = button.nextElementSibling   // 버튼에 인접한 다음 요소는 '모달'([role="dialog"])
  button.setAttribute('aria-pressed', true) // 버튼 요소를 '누른' 상태로 변경
  modal.removeAttribute('hidden')           // 모달 요소의 hidden 속성 제거
  // 모달 요소에 is-active 클래스 속성 추가
  window.setTimeout(() => modal.classList.add('is-active'), 0)
  // 브라우저 기본 동작 차단
  e.preventDefault()
}

JavaScript Behaiver

Todo 1-2

상세 설명 패널 내부에 위치한 패널 닫기 버튼(.button.is-close-panel)을
클릭하면,
패널을 닫는 이벤트 핸들러를 연결하여 패널을 닫도록 설정한다.

  - 상세 설명 패널(.ediya-menu__item--detail) hidden 속성 값 추가.

  - 상세 설명 패널에 is-active 클래스 제거.

JavaScript Behaiver

Programming : Hide Detail Modal

elList('.ediya-menu__item .button.is-close-panel').forEach(button =>
  button.addEventListener('click', hideDetailModal)
)
const hideDetailModal = e => {
  const closeButton = e.currentTarget  // 닫기 버튼
  const modal = closeButton.parentNode // 부모 탐색 '모달'
  const button = modal.previousElementSibling // 열기 버튼(a)
  button.setAttribute('aria-pressed', false) // 열기 버튼 누르지 않은 상태로 변경
  modal.classList.remove('is-active') // 모달 요소의 is-active 클래스 속성 제거
  window.setTimeout(() => modal.setAttribute('hidden', true), 200) // 모달에 hidden 속성 추가
}

JavaScript Behaiver

Todo 2-1

EDIYA COFFEE 로고 오른쪽 옆에 위치한 토글 버튼(button.is-open-
menu)을 누르면, 오프캔버스 메뉴(.app-navigation)을 화면에 표시한다.

  - hidden 속성 값 제거.

  - is-active 클래스 추가.

JavaScript Behaiver

Programming : Open OffCanvas Menu

const openMenuButton = el('.app-header .button.is-open-menu')
const menu = el('.app-navigation')

openMenuButton.addEventListener('click', openMenu)
const openMenu = e => {
  menu.removeAttribute('hidden')
  window.setTimeout(() => menu.classList.add('is-active'), 100)
}

JavaScript Behaiver

Todo 2-2

오프캔버스 닫기 버튼(.button.is-close-menu)을 누르면,
오프캔버스 메뉴(.app-navigation)을 화면에서 감춘다.

  - hidden 속성 추가.

  - is-active 클래스 제거.

JavaScript Behaiver

Programming : Close OffCanvas Menu

const closeMenuButton = el('.app-header .button.is-close-menu')

closeMenuButton.addEventListener('click', closeMenu)
const closeMenu = e => {
  menu.classList.remove('is-active')
  window.setTimeout(() => menu.setAttribute('hidden', true), 200)
}

JavaScript Behaiver

Todo 2-3

오프캔버스 메뉴(.app-navigation)가 열린 상태에서
Tab 또는 Shift + Tab 키를 눌렀을 때 포커스가 메뉴 안에서
순환되어야 한다.

  - 첫번째 링크 요소에서 Shift + Tab 키를 누르면
    메뉴 닫기 버튼에 포커싱.

  - 메뉴 닫기 버튼에서 Tab 키를 누르면
    첫번째 링크 요소에 포커싱.

JavaScript Behaiver

Programming : Focus Cycle Processing for Accessibility

const firstNavLink = el('.app-navigation a')
const closeMenuButton = el('.app-navigation .button.is-close-menu')

firstNavLink.addEventListener('keydown', focusOnCloseMenuButton)
closeMenuButton.addEventListener('keydown', focusOnFirstNavLink)
const focusOnCloseMenuButton = e => {
  if(e.shiftKey && e.code === 'Tab') { 
    window.setTimeout(() => closeMenuButton.focus(), 0) 
  }
}

const focusOnFirstNavLink = e => {
  if(!e.shiftKey && e.code === 'Tab') { 
    window.setTimeout(() => firstNavLink.focus(), 0) 
  }
}

Event Handling (DOM Script)

Finished Code

// DOM 요소(Elements) 찾기
const menu = el('.app-navigation')
const firstNavLink = el('.app-navigation a')
const openMenuButton = el('.app-header .button.is-open-menu')
const closeMenuButton = el('.app-navigation .button.is-close-menu')
const menuItemButtons = elList('.ediya-menu__item a[role="button"]')
const closeButtons = elList('.ediya-menu__item .button.is-close-panel')

// 인터랙션 기능 구현
const showDetailModal = (e) => {
  const button = e.currentTarget
  const modal = button.nextElementSibling
  button.setAttribute('aria-pressed', true)
  modal.removeAttribute('hidden')
  window.setTimeout(() => modal.classList.add('is-active'), 0)
  e.preventDefault()
}

const hideDetailModal = (e) => {
  const closeButton = e.currentTarget
  const modal = closeButton.parentNode
  const button = modal.previousElementSibling
  button.setAttribute('aria-pressed', false)
  modal.classList.remove('is-active')
  window.setTimeout(() => modal.setAttribute('hidden', true), 200)
}

const openMenu = (e) => {
  menu.removeAttribute('hidden')
  window.setTimeout(() => menu.classList.add('is-active'), 100)
}

const closeMenu = (e) => {
  menu.classList.remove('is-active')
  window.setTimeout(() => menu.setAttribute('hidden', true), 200)
}

const focusOnCloseMenuButton = (e) => {
  if (e.shiftKey && e.code === 'Tab') {
    window.setTimeout(() => closeMenuButton.focus(), 0)
  }
}

const focusOnFirstNavLink = (e) => {
  if (!e.shiftKey && e.code === 'Tab') {
    window.setTimeout(() => firstNavLink.focus(), 0)
  }
}

// 이벤트 핸들링
menuItemButtons.forEach((button) => button.addEventListener('click', showDetailModal))
closeButtons.forEach((button) => button.addEventListener('click', hideDetailModal))
openMenuButton.addEventListener('click', openMenu)
closeMenuButton.addEventListener('click', closeMenu)
firstNavLink.addEventListener('keydown', focusOnCloseMenuButton)
closeMenuButton.addEventListener('keydown', focusOnFirstNavLink)

JavaScript Behaiver

Mission: User Convenience

오프캔버스 메뉴(.app-navigation)가 열린 상태에서 Esc 키를 누르면
열린 메뉴를 닫아 사용자 편의성을 높이고 싶습니다. 어떻게 해야 할까요?
미션을 해결할 수 있는 프로그래밍을 생각하고 실행해보세요.  

Web Tech
Concert

Part 3. Template Engine

feat. Ajax/Modular

HTML

CONTENTS

TAG

+

=

Dynamic
Data Binding

(Client Side)

why using?  JavaScript Template.

SERVER

CLIENT

<script async src="https://yamoo9.github.io/assets/js/dom.utils.js"></script>
<script async src="https://unpkg.com/axios@0.19.0/dist/axios.min.js"></script>
<script async src="https://unpkg.com/hogan.js@3.0.2/dist/hogan-3.0.2.min.js"></script>

JavaScript Libraries

async | defer (비동기 처리)  module (모듈러 프로그래밍)

Scripts : parse / fetch / execution

<script async type="module" src="./js/main.js"></script>

ediya.json  |  JSON Viewer Awesome Extension

JSON (DATA)

// 비동기 통신 후, 실행 될 함수
const app = ({ data }) => {
  // 응답 결과의 data에서 특정 데이터를 상수로 할당 (별칭 사용)
  const { beverage_menu: menu, navigation } = data
}

// axios를 사용해 비동기 데이터 가져오기
// https://api.myjson.com/bins/pdppf 또는 /api/ediya.json
axios.get('/api/ediya.json').then(app)

using axios library

Get DATA

<nav
  class="app-navigation"
  hidden
>
  <h2 class="a11y-hidden">메인 메뉴</h2>
  <ul class="reset-list"></ul>
  <!-- ... -->
</nav>

Clear list item code

Navigation List

Dynamic
Data Binding

<script
  id="ediya-navigation-item-template"
  type="text/x-template"
>
  {{#items}}
    <li><a href="{{link}}">{{text}}</a></li>
  {{/items}}
</script>

using hogan library

JavaScript Template

const template = Hogan.compile(el('#ediya-navigation-item-template').html())
const output = template.render({ items: navigation.items })
el('.app-navigation > ul').html(output)
const bindDataToTemplate = (data, template_selector, target_selector) => {
  const template = Hogan.compile(el(template_selector).html())
  const output = template.render(data)
  el(target_selector).html(output)
}

Make Utility

Binding Function

const app = ({ data }) => {
  const { beverage_menu: menu, navigation } = data
  bindDataToTemplate(
    { items: navigation.items }, 
    '#ediya-navigation-item-template', 
    '.app-navigation > ul'
  )
}
<main class="app-main">
  <h2 class="a11y-hidden">이디야 음료</h2>
  <ul class="ediya-menu reset-list"></ul>
</main>

Clear list item code

Menu List

Dynamic
Data Binding

<script
  id="ediya-menu-item-template"
  type="text/x-template"
>
  {{#menu}}
    <li class="ediya-menu__item">
      <a 
        href="#" 
        role="button"
        aria-haspopup="true"
        aria-pressed="false"
      >
        <figure>
          <img
            src="{{figure.src}}"
            width="{{figure.width}}"
            height="{{figure.height}}"
            alt=""
          />
          <figcaption>{{figure.name}}</figcaption>
        </figure>
      </a>
      <div
        class="ediya-menu__item--detail"
        role="dialog"
        aria-modal="true"
        aria-labelledby="ediya-menu__item-{{id}}"
        hidden
      >
        <h3
          class="ediya-menu__item--name"
          id="ediya-menu__item-{{id}}"
        >{{detail.ko}}<span lang="en">{{detail.en}}</span></h3>
        <p>{{detail.desc}}</p>
        <div class="ediya-menu__item--multi-column is-2">
          <dl>
            {{#detail.display_criteria}}
              <dt>{{0}}</dt>
              <dd>({{1}})</dd>
            {{/detail.display_criteria}}
          </dl>
        </div>
        <button
          class="button is-close-panel"
          type="button"
          title="닫기"
          aria-label="음료 정보 패널 닫기"
        >
          <span aria-hidden="true">×</span>
        </button>
      </div>
    </li>
  {{/menu}}
</script>

using hogan library

JavaScript Template

using Utility

Building Dynamic DOM

const app = ({ data }) => {
  const { beverage_menu: menu, navigation } = data
  bindDataToTemplate(
    { items: navigation.items }, 
    '#ediya-navigation-item-template', 
    '.app-navigation > ul'
  )
  bindDataToTemplate({ menu }, '#ediya-menu-item-template', '.ediya-menu')
}

Ajax (axios) + Templating (Hogan) + Event Handling (DOM Script)

Finished Code

const app = ({ data }) => {
  const { beverage_menu: menu, navigation } = data
  bindDataToTemplate(
    { items: navigation.items }, 
    '#ediya-navigation-item-template', 
    '.app-navigation > ul'
  )
  bindDataToTemplate(
    { menu }, 
    '#ediya-menu-item-template', 
    '.ediya-menu'
  )
  bindEvents()
}

const bindDataToTemplate = (data, template_selector, target_selector) => {
  const template = Hogan.compile(el(template_selector).html())
  const output = template.render(data)
  el(target_selector).html(output)
}

const bindEvents = () => {
  handleNavigation()
  handleNavigationA11y()
  handleMenuItems()
}

const handleNavigation = () => {
  const menuItemButtons = elList('.ediya-menu__item a[role="button"]')
  menuItemButtons.forEach((button) => button.on('click', showDetailModal))
  const menuItemCloseButtons = elList('.ediya-menu__item .button.is-close-panel')
  menuItemCloseButtons.forEach((button) => button.on('click', hideDetailModal))
}

const showDetailModal = (e) => {
  const button = e.currentTarget
  const modal = button.nextElementSibling
  button.setAttribute('aria-pressed', true)
  modal.removeAttribute('hidden')
  window.setTimeout(() => modal.classList.add('is-active'), 0)
  e.preventDefault()
}

const hideDetailModal = (e) => {
  const closeButton = e.currentTarget
  const modal = closeButton.parentNode
  const button = modal.previousElementSibling
  button.setAttribute('aria-pressed', false)
  modal.classList.remove('is-active')
  window.setTimeout(() => modal.setAttribute('hidden', true), 200)
}

const handleNavigationA11y = () => {
  const firstNavLink = el('.app-navigation a')
  const closeMenuButton = el('.app-navigation .button.is-close-menu')
  firstNavLink.on('keydown', (e) => {
    if (e.shiftKey && e.code === 'Tab') {
      window.setTimeout(() => closeMenuButton.focus(), 0)
    }
  })
  closeMenuButton.on('keydown', (e) => {
    if (!e.shiftKey && e.code === 'Tab') {
      window.setTimeout(() => firstNavLink.focus(), 0)
    }
  })
}

const handleMenuItems = () => {
  const openMenuButton = el('.app-header .button.is-open-menu')
  const menu = el('.app-navigation')
  openMenuButton.on('click', (e) => {
    menu.removeAttribute('hidden')
    window.setTimeout(() => menu.classList.add('is-active'), 100)
  })
  const closeMenuButton = el('.app-header .button.is-close-menu')
  closeMenuButton.on('click', (e) => {
    menu.classList.remove('is-active')
    window.setTimeout(() => menu.setAttribute('hidden', true), 200)
  })
}

axios.get('/api/ediya.json').then(app)

Web Tech
Concert

Front-End 디자인 & 개발 공부는 야무와 함께 ( yamoo9.github.io/front-end-master )

Part 4. Framework

Front-End

Web Tech
Concert

프레임워크 학습활용에 앞서

Front-End 프레임 워크 사용에 앞서, 사전 학습 가이드 ( yamoo9.github.io/react-master )

Front-End 개발 로드맵 ( roadmap.sh/frontend )

웹 표준 개발
패러다임 시프트

웹 표준 개발 패러다임 시프트    모듈 프로그래밍

<script async src="//yamoo9.github.io/assets/js/dom.utils.js"></script>
<script async src="//unpkg.com/axios@0.19.0/dist/axios.min.js"></script>
<script async src="//unpkg.com/hogan.js@3.0.2/dist/hogan-3.0.2.min.js"></script>
<script defer src="./js/main.js"></script>
<script async type="module" src="./js/main.js" ></script>
<script nomodule src="./js/module-fallback.js"></script>

caniuse.com/#feat=es6-module

웹 표준 개발 패러다임 시프트    웹 컴포넌트 시스템

bit.ly/WComponent

차세대 웹 표준 기술 ➪ 프레임워크 활용

개발 중인 웹 표준 이슈    제안된 기술 중 확정 채택되지 않고 버려진 기술들

차세대 웹 표준 기술 중 일부는 개발 과정에서 채택되지 않고 버려지므로, 표준 확정 전에 학습할 경우 시간 낭비가 될 수 있습니다.

    Front-End 프레임워크를 사용하면 차후 웹 표준이 될 기술을 미리 사용하여
오늘날 바로 사용할 수 있습니다. (브라우저 호환, 번들, 최적화 자동 수행)

Framework 사용 경험자 채용    차세대 개발 능력을 가진 인재를 찾는 기업들

차세대 웹 표준 기술이 가져다 줄 개발 인프라 스트럭처를 오늘 당장 쓰길 원하는 기업은 프레임워크 경험자를 찾습니다.

React Master 영상 강의는 야무가 진행하는 Front-End Masters League 블렌디드 러닝에 사용되는 동영상 강의

React 프로그래밍 영상 강의 (일부 공개)

Documentation

React

JavaScript Library for UI
Component-Based

Virtual DOM

앞서 다룬 모듈 프로그래밍, 컴포넌트 시스템이 반영된 Front-End 프레임 워크 중 React를 WTC 2019에서 다뤄봅니다.

Front-end Frameworks

Overview (stateofjs.com)

React는 사용률과 만족도가 매우 높고, Vue는 배우고 싶다는 의견이 높았습니다. 상대적으로 Angular는 만족도가 낮았습니다.

React Basic

React 기본기 학습

React를 활용해 개발을 시작하려면 다음의 기본 개념을 학습해야 합니다.

React Library

Quick Overview about Components

빠르게 React 코드를 살펴보면서 컴포넌트 개념을 가볍게 익혀봅니다.

<MenuItem image="cherry-blossoms-latte.png">
  ICED 벚꽃라떼
</MenuItem>

React Workflow

Development  ➪  Production

React 프레임 워크를 활용해 개발에서 배포까지 워크 플로우를 살펴봅니다.

React Programming

Learning Guide (react2019)

React 러닝 가이드를 통해 React를 이해하고, 활용하는 방법을 가볍게 살펴봅니다.

Online Code Editor

세미나 형식의 강의 특성 상
복잡한 개발 환경 구성을 생략하고
온라인 상에서 바로 실습 가능한
코드 에디터를 활용합니다.

온라인 코드 에디트 환경을 제공하는 StackBlitz 서비스를 사용해 실습을 진행합니다.

React Developer Tools

React 개발 도구

React 개발에 도움을 줄 Chrome 브라우저 확장을 설치합니다.

WTC Example

Practice Start: Fork it!

WTC React 예제 프로젝트를 포크(Fork) 합니다.  —  stackblitz.com/edit/react-rm4as1

HTML/CSS ➪ React

CSS 스타일 모듈 관리

Spoqa Han Sans 웹 폰트를 제외한 스타일 모듈을 HTML이 아닌, 엔트리 파일(index.js)에서 로드 합니다.

1.1__ index.html 파일에서 CSS 스타일 모듈 링크 제거

// CSS 스타일 모듈 로드
import './css/normalize.min.css'
import './css/style.css'
import './css/animation.css'
<head>
  <link rel="stylesheet" href="https://spoqa.github.io/spoqa-han-sans/css/SpoqaHanSans-kr.css">
  <!-- style.css, animation.css 제거 -->
</head>

1.2__ index.js 파일에서 스타일 모듈 로드

Component-Based

컴포넌트 기반 프로그래밍

React 컴포넌트는 UI를 구성하는 각 부품에 해당되며, 독립적인 JavaScript 파일로 분리되어 관리됩니다.

앱 헤더 컴포넌트

2.1__ index.html 파일에서 <header> 영역 코드 제거

import React from 'react'

const AppHeader = () => (
  <header className="app-header">
    ...
  </header>
)

export default AppHeader
<header class="app-header"> ... </header>

2.2__ components/AppHeader.js 파일 생성 후, React 컴포넌트 코드 작성 (속성 이름 주의!)

<header> 코드를 분리하여 AppHeader 컴포넌트로 생성합니다.

React Components

// 엔트리 파일(index.js)에 컴포넌트 렌더링 설정

import AppHeader from './components/AppHeader'

const App = () => (
  <div className="container">
    <AppHeader />
  </div>
)

앱 메인 컴포넌트

3.1__ index.html 파일에서 <main> 영역 코드 제거

import React from 'react'

const AppMain = () => (
  <main className="app-main">
    ...
  </main>
)

export default AppMain
<main class="app-main"> ... </main>

3.2__ components/AppMain.js 파일 생성, React 컴포넌트 코드 작성 (<img> → <img /> 주의!)

<main> 코드를 분리하여 AppMain 컴포넌트로 생성합니다.

React Components

import AppMain from './components/AppMain'

const App = () => (
  <div className="container">
    <AppHeader />
    <AppMain />
  </div>
)

데이터를 속성으로 전달

4__ 엔트리 파일에서 JSON 데이터 로드 후, 컴포넌트 속성으로 데이터 값을 전달

// JSON 데이터
// - 모듈 코드 설명 (구조 분해 할당 및 별칭 설정)
import { data as ediyaItems } from './api/data'

const App = () => (
  <div className="container">
    <AppHeader />
    {/* props 개념 설명: items 속성 값으로 ediyaItems 설정하면 컴포넌트에 데이터가 전달 됨 */}
    <AppMain items={ediyaItems} />
  </div>
)

데이터를 컴포넌트의 속성으로 전달하면, 컴포넌트의 props 속성으로 전달받을 수 있습니다.

Passing Props

데이터 리스트 렌더링

5.1__ 컴포넌트에 전달 된 속성(props)을 토대로 데이터 바인딩

const AppMain = ({ items }) => (
  <ul className="ediya-menu reset-list">
    {items.map(({id, figure, detail}) => (
      <li className="ediya-menu__item" key={id}>
        <a href="#" role="button">
          <figure>
            <img src={figure.src} alt="" width={figure.width} height={figure.height} />
            <figcaption>{figure.name}</figcaption>
          </figure>
        </a>
        ...
      </li>
    ))}
  </ul>
)

전달 받은 데이터를 기반으로 데이터 아이템을 순환하여  리스트 렌더링 합니다.

React List Rendering

  • 매개변수 구조 분해 할당
  • 데이터 → 리스트 렌더링
  • key 속성 (빠른 비교, 성능)

중첩 리스트 렌더링

5.2__ 중첩 데이터 바인딩

(
  <div className="ediya-menu__item--detail" aria-labelledby="ediya-menu__item1" hidden>
    <h3 className="ediya-menu__item--name" id="ediya-menu__item1">
      {detail.ko}<span lang="en">{detail.en}</span>
    </h3>
    <p>{detail.desc}</p>
    <div className="ediya-menu__item--multi-column is-2">
      <dl>
        { detail.display_criteria.map(item => (
          <React.Fragment>
            <dt>{item[0]}</dt> 
            <dd>({item[1]})</dd>
          </React.Fragment>
        )) }
      </dl>
    </div>
  </div>
)

리스트 렌더링 안에서 중첩된 리스트 렌더링을 처리할 수 있습니다.

Nested List Rendering

  • 중첩 리스트 렌더링
  • React.Fragment 컴포넌트

컴포넌트 관리

6__ AppMenuList 컴포넌트 생성 (<ul> 코드 분리)

import React from 'react'

const AppMenuList = ({items}) => (
  <ul className="ediya-menu reset-list">
    {items.map(({ id, figure, detail }) => (
      ...
    ))}
  </ul>
)

export default AppMenuList

메뉴 아이템을 관리하기 위한 컨테이너 컴포넌트를 추가합니다.

Manage React Components

import React from 'react'
import AppMenuList from './AppMenuList'

const AppMain = ({ items }) => (
  <main className="app-main">
    <h2 className="a11y-hidden">이디야 음료</h2>
    <AppMenuList items={items} />
  </main>
)

export default AppMain

중첩 컴포넌트 관리

7__ AppMenuListItem 컴포넌트 생성 (<li> 코드 분리)

import React from 'react'
import AppMenuListItem from './AppMenuListItem'

const AppMenuList = ({items}) => (
  <ul className="ediya-menu reset-list">
    {items.map(item => (
      <AppMenuListItem item={item} key={item.id} />
    ))}
  </ul>
)

export default AppMenuList

Nesting React Components

컨테이너 컴포넌트에 중첩되는 메뉴 아이템 컴포넌트를 추가합니다.

import React from 'react'

const AppMenuListItem = ({item}) => {
  const { figure, detail } = item
  return (
    <li className="ediya-menu__item"> 
      ... 
    </li>
  )
}

export default AppMenuListItem

컴포넌트 상태 관리

8__ 함수형 컴포넌트 ➪ 클래스 컴포넌트로 변경

Manage React Component State

컴포넌트 상태(데이터) 관리가 필요한 경우, 클래스 컴포넌트를 사용합니다.

import React, { Component } from 'react'

class AppMenuListItem extends Component {
  state = {
    isPressed: false // 디테일 패널 표시(눌림) 상태
  }
  render() {
    const { id, figure, detail } = this.props.item
    return (
      <li className="ediya-menu__item">
        ...
      </li>
    )
  }
}

export default AppMenuListItem

Class

import React, { Component } from 'react'

class 컴포넌트 extends Component {
  state = {
    // 상태(데이터)
  }
  render() {
    return (
      {/* React 요소(JSX 코드) */}
    )
  }
}

export default 컴포넌트

접근성 반영

9.1__ 버튼 접근성 속성(role, aria-haspopup, aria-pressed) 및 상태 설정

React A11Y

WAI-ARIA 기술을 적용하여 애플리케이션 접근성을 개선합니다.

(
  <li className="ediya-menu__item">
    <a href="#" role="button" aria-haspopup="true" aria-pressed={this.state.isPressed}>
      <figure>
        <img src={figure.src} alt="" width={figure.width} height={figure.height} />
        <figcaption>{figure.name}</figcaption>
      </figure>
    </a>
    ...
  </li>
)

9.2__ 모달 접근성 속성(role, aria-modal, aria-labelledby) 및 상태 설정

(
  <div 
    role="dialog" 
    aria-modal="true" 
    aria-labelledby={`ediya-menu__item-${id}`} 
    className="ediya-menu__item--detail" 
    hidden
  >
  <h3 id={`ediya-menu__item-${id}`} className="ediya-menu__item--name">
    {detail.ko}<span lang="en">{detail.en}</span>
  </h3>
    ...
  </div>
)

접근성 반영

React A11Y

WAI-ARIA 기술을 적용하여 애플리케이션 접근성을 개선합니다.

인터랙션 메서드

10.1__ 버튼 눌림 상태를 변경하는 메서드 정의

React Interaction

메뉴 아이템 버튼 클릭 시, 처리 될 메서드 액션을 정의합니다.

class AppMenuListItem extends Component {
  
  // 메서드 (클래스 필드 문법 사용)
  showModal = (e) => {
    e.preventDefault()
    this.setState({
      isPressed: true
    })
  }

  hideModal = (e) => {
    e.preventDefault()
    this.setState({
      isPressed: false
    })
  }

}

클릭 이벤트 핸들링

10.2__ 버튼 이벤트 핸들링

React Click Event Handling

메뉴 아이템 클릭 이벤트를 처리하는 속성을 설정합니다.

(
  <li className="ediya-menu__item">
    <a 
      onClick={e => this.showModal(e)} 
      href="#" 
      role="button" 
      aria-haspopup="true" 
      aria-pressed={isPressed}
    >
      ...
    </a>
    ...
  </li>
)

상태 변경 시, UI 업데이트 처리

10.3__ 모달 이벤트 핸들링

Change State

메뉴 아이템 컴포넌트 상태(데이터)가 변경되면 모달의 UI가 변경되도록 설정합니다.

(
  <li className="ediya-menu__item">
    ...
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={`ediya-menu__item-${id}`}
      aria-hidden={!isPressed}
      className={`ediya-menu__item--detail ${isPressed ? 'is-active' : ''}`}
      style={{ zIndex: isPressed ? 1 : -1 }}
    >
  </li>
)

이벤트, 포커싱 처리

10.4__ 모달 닫기 버튼 이벤트 핸들링 및 포커싱 컨트롤

Handling Event & Focusing

모달 닫기 버튼에 클릭 이벤트를 설정하고, 닫혀진 상태에서는 키보드 포커싱을 차단합니다.

(
  <div role="dialog">
    ...
    <button 
      onClick={e => this.hideModal(e)} 
      tabIndex={isPressed ? 0 : -1}
      className="button is-close-panel" 
      type="button"  
      title="닫기" 
      aria-label="음료 정보 패널 닫기" 
    > ... </button>
  </div>
)

오프 캔버스 컴포넌트

OffCanvas Component

<nav> 코드를 분리하여 OffCanvasMenu 컴포넌트로 생성합니다.

11.1__ AppHeader 컴포넌트에서 <nav> 영역 코드 제거

import React from 'react'

const OffCanvasMenu = () => (
  <nav className="app-navigation">
    ...
  </nav>
)

export default OffCanvasMenu
<nav className="app-navigation"> ... </nav>

11.2__ components/OffCanvasMenu.js 파일 생성 후, React 컴포넌트 코드 작성

// AppHeader에 컴포넌트 렌더링 설정

import OffCanvasMenu from './OffCanvasMenu'

const AppHeader = () => (
  <header className="app-header">
    ...
    <OffCanvasMenu />
  </header>
)

오프 캔버스 메뉴 데이터

OffCanvas Data

OffCanvasMenu 컴포넌트에 전달 할 데이터를 JSON으로 작성합니다.

11.3__ 내비게이션 메뉴 데이터 화 : navigation.json

{
  "menu": {
    "title": "메인 메뉴",
    "items": [
      { "link": "#menu", "text": "메뉴" },
      { "link": "#members", "text": "이디야멤버스" },
      { "link": "#coffee-lab", "text": "이디야커피랩" },
      { "link": "#culture-lab", "text": "이디야컬쳐랩" },
      { "link": "#notice", "text": "공지사항" },
      { "link": "#find-store", "text": "매장찾기" }
    ]
  }
}
const AppHeader = ({ menu }) => (
  <header className="app-header">
    ...
    <OffCanvasMenu menu={menu} />
  </header>
)

11.4__ 메뉴 데이터 전달(props)

오프 캔버스 메뉴 데이터 바인딩

OffCanvas Component

OffCanvasMenu 컴포넌트에 전달 된 데이터를 바인딩, 렌더링 처리 합니다.

11.5__ OffCanvasMenu 컴포넌트 데이터 바인딩, 리스트 렌더링

const OffCanvasMenu = ({ menu }) => (
  <nav className="app-navigation">
    <h2 className="a11y-hidden">{menu.title}</h2>
    <ul className="reset-list">
      {
        menu.items.map(item => (
          <li key={item.text}><a href={item.link}>{item.text}</a></li>
        ))
      }
    </ul>
    <button className="button is-close-menu" type="button" title="메뉴 닫기" aria-label="메뉴 닫기">
      <span className="close" aria-hidden="true">×</span>
    </button>
  </nav>
)

앱 헤더 컴포넌트 인터랙션

12.1__ AppHeader 컴포넌트 상태 관리 및 메서드 정의

AppHeader Interaction

AppHeader 클래스 컴포넌트로 변경한 후, 메뉴 열림/닫힘 상태와 메서드를 설정합니다.

import React, { Component } from 'react'

class AppHeader extends Component {
  state = { isOpened: false }
  openMenu = () => {
    this.setState({ isOpened: true })
  }
  closeMenu = () => {
    this.setState({ isOpened: false })
  }
  render() {
    const { menu } = this.props
    return (
      <header className="app-header"> ... </header>
    )
  }
}

이벤트 처리 및 상태 전달

12.2__ 메뉴 열기 버튼에 클릭 이벤트 설정 / 오프 캔버스 메뉴 컴포넌트에 상태 전달

Handling Event & Passing Props

모달 닫기 버튼에 클릭 이벤트를 설정하고, 닫혀진 상태에서는 키보드 포커싱을 차단합니다.

(
  <header className="app-header">
    ...
    <button onClick={this.openMenu} 
      className="button is-open-menu" type="button" title="메뉴 열기" aria-label="메뉴 열기">
      <span className="ir"></span>
    </button>
    <OffCanvasMenu menu={menu} isOpen={this.state.isOpened} />
  </header>
)

전달 받은 상태 ➪ UI 업데이트

12.3__ OffCanvasMenu 컴포넌트에 전달 된 상태에 따라 UI 업데이트 설정

OffCanvasMenu Props

OffCanvasMenu 컴포넌트로 전달된 isOpen 속성을 토대로 오프 캔버스 메뉴 UI를 업데이트 합니다.

const OffCanvasMenu = ({ menu, isOpen }) => (
  <nav className={`app-navigation ${isOpen ? 'is-active' : ''}`}>
    ...
  </nav>
)
(
  <OffCanvasMenu menu={menu} isOpen={this.state.isOpened} onClose={this.closeMenu} />
)

12.4__ OffCanvasMenu 컴포넌트에 메서드 closeMenu를 전달

전달 받은 메서드 ➪ 부모 컴포넌트 상태 변경

12.5__ OffCanvasMenu 컴포넌트에 전달 된 메서드를 사용해 UI 업데이트 설정

OffCanvasMenu Close

OffCanvasMenu 컴포넌트로 전달된 onClose 메서드를 사용해 부모 컴포넌트의 상태를 변경할 수 있습니다.

const OffCanvasMenu = ({ menu, isOpen, onClose }) => (
  <nav className={`app-navigation ${isOpen ? 'is-active' : ''}`}>
    ...
    <button onClick={onClose} 
      className="button is-close-menu" type="button" title="메뉴 닫기" aria-label="메뉴 닫기">
      <span className="close" aria-hidden="true">×</span>
    </button>
  </nav>
)

열림/닫힘 상태에 따른 포커스 컨트롤

12.6__ OffCanvasMenu 컴포넌트에 tabIndex 속성을 사용해 포커스 접근/해제 설정

OffCanvasMenu Focusing

오프 캔버스 메뉴의 열림/닫힘 상태에 따라 포커스 접근/해제를 tab-index 속성을 사용해 설정합니다.

const OffCanvasMenu = ({ menu, isOpen, onClose }) => (
  <nav className={`app-navigation ${isOpen ? 'is-active' : ''}`}>
    ...
    <ul className="reset-list">
      {
        menu.items.map(item => (
          <li key={item.text}>
            <a tabIndex={isOpen ? 0 : -1} href={item.link}>{item.text}</a>
          </li>
        ))
      }
    </ul>
    <button onClick={onClose} tabIndex={isOpen ? 0 : -1} 
      className="button is-close-menu" type="button" title="메뉴 닫기" aria-label="메뉴 닫기">
      <span className="close" aria-hidden="true">×</span>
    </button>
  </nav>
)

포커스 가능한 요소들 중 일부 참조

13.1__ 포커스 가능한 요소 중 첫번째, 마지막 요소 참조(reference)

Focusable Elements

오프 캔버스 메뉴의 첫번째, 마지막 포커스 가능한 요소를 참조합니다.

import React, { Component, createRef } from 'react'

class OffCanvasMenu extends Component {
  constructor() {
    this.firstFocusableEl = createRef()
    this.lastFocusableEl = createRef()
  }
  ...
}

포커스 가능한 요소들 중 일부 참조

13.2__ 첫번째, 마지막 포커스 가능한 요소에 참조 설정

Focusable Elements

오프 캔버스 메뉴의 첫번째, 마지막 포커스 가능한 요소를 참조합니다.

첫번째 링크 요소일 경우,
첫번째 포커스 요소로 참조

메뉴 닫기 버튼은 마지막 포커스 요소로 참조

키보드 이벤트 핸들링

13.3__ 키보드 이벤트(keyDown) 핸들링 ➪ 첫번째 또는 마지막 요소 포커싱

Handling Keyboard Events

오프 캔버스 메뉴의 첫번째, 마지막 포커스 가능한 요소에 키보드 이벤트를 설정합니다.

포커싱 메서드

13.4__ 포커싱을 처리하는 메서드 작성 (누른 키 감지: shiftKey, Tab | 비동기 이벤트 속성 참조)

Focusing Method

키보드 이벤트에 따라 오프 캔버스 메뉴의 첫번째, 마지막 포커스 가능한 요소에 포커싱을 처리하는 메서드를 작성합니다.

moveFocus = (type, e) => {
  // React 앱에서 비동기 이벤트 속성 참조할 경우 사용
  e.persist()
  switch(type) {
    case 'first':
      window.setTimeout(
        // shift 키를 누르지 않고, tab 키만 누른 경우
        () => !e.shiftKey && e.key === 'Tab' && this.firstFocusableEl.current.focus(), 
      0)
    break;
    case 'last':
      window.setTimeout(
        // shift 키와 tab 키를 누른 경우
        () => e.shiftKey && e.key === 'Tab' && this.lastFocusableEl.current.focus(), 
      0)
  }
}

사용성 개선

13.5__ Esc 키를 누르면 메뉴를 닫는 메서드 정의 및 라이프 사이클 훅을 사용해 이벤트 연결

Usability improvements

오프 캔버스 메뉴가 열린 상태에서 Esc 키를 누르면 메뉴를 닫히도록 설정하여 사용성을 개선합니다.

componentDidMount() {
  window.addEventListener('keyup', this.escCloseMenu)
}

escCloseMenu = (e) => {
  switch(e.key) {
    case 'Esc': // IE, Edge
    case 'Escape':
      this.props.onClose()
  }
}

완성 예제

Completion Example

실습이 완료된 예제 링크 입니다. (stackblitz.com/edit/react-t5ffdx)

WTC React Programming Example

Finished Code

강의신청