Skip to content

Latest commit

 

History

History
746 lines (652 loc) · 25.6 KB

File metadata and controls

746 lines (652 loc) · 25.6 KB

jQuery plug-in 제작/사용

Card 컴포넌트 정의

1. jQuery 로드 여부 검증

  if (!$) { throw 'jQuery에 의존하는 컴포넌트입니다. jQuery를 로드해주세요.' }

2. 비공개 지역 함수 정의

function bind(card) {
    card._$toggle.on('click', onClickToggle.bind(card._$toggle, card));
    card._$save.on('click', onClickSave.bind(card._$save, card));
    card._$edit.on('click', onClickEdit.bind(card._$edit, card));
    card._$delete.on('click', onClickDelete.bind(card._$delete, card));
  }

  function onClickToggle(card, e) {
    e.preventDefault();
    card.toggleContent();
  }
  function onClickSave(card, e) {
    e.preventDefault()
    card.save();
  }
  function onClickEdit(card, e) {
    e.preventDefault();
    card.edit();
  }
  function onClickDelete(card, e) {
    e.preventDefault();
    card.delete();
  }

3. Constructor

  • 생성자 함수 Card

  • new를 강제화하는 검증을 한다.

  • 초기에 카드 컴포넌트로 사용할 DOM 요소를 jQuery화 해준다.

  • 카드 컴포넌트를 초기화 한다. (init 함수 실행)

function Card(o) {
    if (!this) { throw 'new Card() 문법으로 사용해주세요.'; }
    this._$card = $(o);
    this.init();
  }

4. Prototype

  • 생성자 함수 Card의 프로토타입의 속성을 지정 (constructor, version)

  • 생성자 함수 Card의 프로토타입의 속성으로 메서드 지정 (init, isOpenedContent, openContent, closeContent, toggleContent, save, edit, delete)

  • init 메서드

    Card {} 내부의 버튼 컴포넌트를 참조시킨다. bind 함수를 통해 this, 즉 Card {} 를 전달하는 이벤트 바인딩을 실행시킨다.

  • isOpenedContent 메서드

  • openContent 메서드

    aria-label 속성값이 open인 토글 버튼의 속성값을 close로 바꿔준다. fa-angle-up 클래스를 가진 토글 버튼의 클래스를 지우고 fa-angle-down 클래스를 추가해준다. card-content 클래스를 찾아 slideDown 메서드를 실행시킨다.

    * 참고링크 slideDown 메서드 - jQuery.com

  • closeContent 메서드

    aria-label 속성값이 close인 토글 버튼의 속성값을 open으로 바꿔준다. fa-angle-down 클래스를 가진 토글 버튼의 클래스를 지우고 fa-angle-up 클래스를 추가해준다. card-content 클래스를 찾아 slideUp 메서드를 실행시킨다.

    * 참고링크 slideDown 메서드 - jQuery.com

  • toggleContent 메서드

  • save 메서드

    content 클래스를 가진 객체의 <br>, <hr> 요소를 제외한 모든 요소를 찾아 contenteditable 속성을 지워준다.

    * 참고링크 contenteditable 속성 - MDN

  • edit 메서드

  • delete 메서드

  Card.fn = Card.prototype = {
    constructor: Card,
    version: '1.0.0',
    init: function(){
      this._$toggle = this._$card.find('.card-toggle-btn');
      this._$save   = this._$card.find('.card-save-btn');
      this._$edit   = this._$card.find('.card-edit-btn');
      this._$delete = this._$card.find('.card-delete-btn');
      bind(this);
    },
    isOpenedContent: function() {
      return !this._$toggle.attr('aria-label').includes('open');
    },
    openContent: function(){
      var change_open_text = this._$toggle.attr('aria-label').replace('open', 'close');
      this._$toggle.attr('aria-label', change_open_text);
      this._$toggle.find('.fa-angle-up').addClass('fa-angle-down').removeClass('fa-angle-up');
      this._$card.find('.card-content').slideDown(100);
    },

    closeContent: function(){
      var $toggle = this._$toggle;
      var change_open_text = $toggle.attr('aria-label').replace('close', 'open');
      $toggle.attr('aria-label', change_open_text);
      $toggle.find('.fa-angle-down').addClass('fa-angle-up').removeClass('fa-angle-down');
      this._$card.find('.card-content').slideUp(100);
    },
    toggleContent: function(){
      this.isOpenedContent() ? this.closeContent() : this.openContent();
    },
    save: function(){
      this._$card.find('.content *:not(br,hr)').removeAttr('contenteditable');
    },
    edit: function(){
      this._$card.find('.content *:not(br,hr)').attr('contenteditable', true).eq(0).focus();
    },
    delete: function(){
      this._$card.remove();
    }
  };

5. Export

  $.Card = Card;

6. Card 컴포넌트 사용

  global.t_card = new $.Card('.twitter-card');
  global.f_card = new $.Card('.facebook-card');

  global.t_card.toggleContent();
  global.f_card.toggleContent();

  $('.demo-radio').find('[role="tab"]').on('click', function(){
    $(this).parent().radioClass('is-active');
  });

jQuery 객체 속성(메서드) 확장 - 인스턴스 메서드

  • jQuery 인스턴스 메서드(플러그인) 작성 패턴
(function($){
  'use strict';

  if ( !$.fn.radioClass ) {
    $.fn.radioClass = function(name='active'){
      // this === jQuery {}
      // jQuery의 능력을 사용할 수 있다.
      // 플러그인 내에서는....
      this.siblings(`.${name}`).removeClass(name);
      this.addClass(name);
      // 메서드 체이닝을 위해서
      return this;
    };
  }

})(window.jQuery);

jQuery().swipe Event

  • 모바일 환경에서 사용 가능한 touch event와 데스크탑 환경에서 사용 가능한 mouse event를 사용한 swipe 메서드

사용자 접속 환경에 따른 이벤트 분기

// 사용자 접속 환경이 mobile 인지 desktop 인지 확인
 if ('ontouchstart' in window || navigator.msMaxTouchPoints)...

Mouse Event

jQuery.event 객체의 type, client 좌표 값 사용

  1. jQuery.event.type

    • mousedown
    • mousemove
    • mouseup
  2. client 좌표

    • jQuery.event.clientX
    • jQuery.event.clientY
  • mousedown 이벤트 발생 시점의 client X,Y 좌표값과 mousemove, mouseup 이벤트 발생 시점의 client X,Y 좌표값을 비교하여 swipe direction을 판별할 수 있다.

  • 명확한 mousemove 이벤트 감지를 위해 document context의 mousedown 상태 값을 확인하는 부분이 필요

$(document).on({
    'mousedown': function(){
    global.is_global_mousedown_state = true;
    },
    'mouseup': function(){
    global.is_global_mousedown_state = false;
    }
});

Touch Event

jQuery.event 객체의 type, changedTouches의 좌표 값 사용

  1. jQuery.event.type

    • touchstart
    • touchmove
    • touchend
  2. jQuery.event.changedTouches[0]

    • jQuery.event.changedTouches[0].clientX
    • jQuery.event.changedTouches[0].clientY
  • touchstart 이벤트 발생 시점의 client X,Y 좌표값과 touchmove, touchend 이벤트 발생 시점의 client X,Y 좌표값을 비교하여 swipe direction을 판별할 수 있다.

참고링크1. jQuery Mobile에서 제공하고 있는 swipe 참고링크2. jQuery의 Plug-in

jQuery에 Carousel Component plug-in 붙이기

carousel 구조

carousel의 구조는 아래와 같다. 구조를 설명하기 위해 구체적인 아이템 숫자 등은 제외했다. 이어지는 예제에서 아이템 갯수는 8개로 가정한다.

<article id="bs3-headphone" class="ui-carousel">
    <h1 class="ui-carousel-headline">Beats Solo3 Wireless On-Ear Headphones – Rose Gold</h1>
    <div role="tablist" class="ui-carousel-tablist">
      <ul role="presentation">
        <li class="active"><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View Front"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 1"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 2"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 3"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 4"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 5"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 6"></a></li>
        <li><a class="ui-carousel-tab" href="" role="tab" aria-label="Headphone View 7"></a></li>
      </ul>
    </div>
    <div class="ui-carousel-button-group" role="group">
      <button type="button" class="ui-carousel-prev-button" aria-label="previous content"></button>
      <button type="button" class="ui-carousel-next-button" aria-label="next content"></button>
    </div>
    <div role="group" class="ui-carousel-tabpanel-wrapper">
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2.jpeg" alt="MNET2">
      </figure>
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2_AV1.jpeg" alt="MNET2_AV1">
      </figure>
      <figure class="ui-carousel-tabpanel active" role="tabpanel">
        <img src="images/MNET2_AV2.jpeg" alt="MNET2_AV2">
      </figure>
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2_AV3.jpeg" alt="MNET2_AV3">
      </figure>
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2_AV4.jpeg" alt="MNET2_AV4">
      </figure>
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2_AV5.jpeg" alt="MNET2_AV5">
      </figure>
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2_AV6.jpeg" alt="MNET2_AV6">
      </figure>
      <figure class="ui-carousel-tabpanel" role="tabpanel">
        <img src="images/MNET2_AV7.jpeg" alt="MNET2_AV7">
      </figure>
    </div>
  </article>

script load

이 예제에서 의존하는 script는 jQuery library 외에 아래 세가지다

<!-- jQuery용 cache plug-in -->
<script src="../jQuery.Extension/utilities/jquery.cache.js"></script>

<!-- jQuery용 radioClass plug-in -->
<script src="../jQuery.Extension/plugins/jquery.radioClass.js"></script>

<!-- jQuery 캐러셀 플러그인 모듈 로드 -->
<script src="js/jquery.ui.carousel.js"></script>

jQuery cache plug-in

cache plug-in은 jQuery의 .data() static method를 이용하여 인자값으로 받은 DOM element, dom_el의 jQuery화 객체를 그 자신에게 bind한다. 이때 bind 된 jQuery화 객체의 member name은 .$this이다.

(function(global, $){
  'use strict';

  var cache = function(dom_el) {
    // 검증
    // document.ELEMENT_NODE
    if ( dom_el.nodeType !== 1 ) { throw new Error('문서객체여야 합니다.') }
    // 메모리 대상
    // jQuery() 팩토리 함수를 사용(1회)한 결과를 저장
    // jQuery.data() 유틸리티 메소드 활용
    // <설정할 때> jQuery.data(dom_el, key, value)
    // <가져올 때> jQuery.data(dom_el, key)
    var $this = $.data(dom_el, '$this');
    // console.log('$this:', $this);
    return $this ? $this : $.data(dom_el, '$this', $(dom_el));
  };

  // jQuery에 본 module을 추가하는 loutine
  if ( !$.cache ) {
    $.cache = cache;
    $.$ = $.cache; // Alias
  }

})(this, this.jQuery);

jQuery radio class plug-in

같은 부모를 가진 형제 요소들의 class_name을 제거하고 그 자신(요소)에게만 class_name을 할당한다.

(function(global, $){
  'use strict';

  var radioClass = function(class_name) {
    // this === jQuery {}
    // console.log('this:', this);
    // console.log('this.jquery:', this.jquery);

    // radioClass 가 적용된 자신(jQuery {})의 형제 중에
    // class_name 값을 가진 형제에게서 해당 class_name을 제거
    this.siblings('.'+class_name).removeClass(class_name);
    // radioClass 적용된 jQuery {} 객체는
    // class_name 이름의 class 속성이 추가
    this.addClass(class_name);
  };

  // $.fn === jQuery.prototype
  if ( !$.fn.radioClass ) {
    $.fn.radioClass = radioClass;
  }

})(this, this.jQuery);

carousel module

setting 객체가 이 컴포넌트 조작의 중심이다. active 동작은 activeTabPanel() 함수 호출을 통해 이루어 진다. 인디케이터나 <버튼, >버튼을 누르면 인디케이터의 경우 눌려진 인디케이터의 index를 인자로 activeTabPanel()을 호출하고 버튼들은 prevContent(), nextContent()를 통해 현재 인덱스에서 +1 또는 -1된 값을 인자로 activeTabPanel()을 호출한다. 이후 주역은 settings 객체다. settings 객체의 정보가 업데이트 된 후에 moveWrapperPosX()에 업드이트된 settings의 .active_index 값을 인자로 주며 호출하고 실질적인 이동이 일어난다. 또한 버튼을 이용한 이동시 인디케이터가 바뀌어야 함을 알려줄 때도 이 settings 객체가 활용된다. activeTabPanel() 내부에서 updateIndicators()에 settings의 .active_index 값을 인자로 주며 호출함으로써 이 동작이 일어난다. passive 동작인 animation auto play는 settings의 using_autoPlay값에 따라 기능 전체를 끄고 킬수 있다. 1주기에 걸리는 시간 역시 settings의 rolling_time이 관여한다. 실행중에 이 둘을 바꾸는 내부함수는 이 모듈에 없다. 자동재생을 일시정지/재생 하는 일은 전역객체의 setInterval과 clearInterval로 구현했다.

(function(global, $){
  'use strict';

  // 문서객체 참조
  var document = global.document;

  // 전역에 공개하여 사용하는 공통 함수
  global.playAnimation = function(callback, time) {
    time = time || 3000;
    return global.setInterval(callback, time);
  };
  global.stopAnimation = function(stop_id) {
    global.clearInterval(stop_id);
  };

  // 플러그인 모듈 내부 어디에서든 참조 가능하도록 객체 참조 변수 선언
  var $widget,
      $wrapper,
      $panels,
      $tablist,
      $tabs,
      $button_group,
      panel_width,
      // 초기 변수
      stop_id         = 0,
      // 옵션 설정
      settings,
      defaults = {
        'active_index'   : 0,
        'using_animation': true,
        'using_autoPlay' : true,
        'rolling_time'   : 3000,
      };

  // 플러그인: 인스턴스 메소드
  var carousel = function(options) {
    settings = $.extend({}, defaults, options);
    $widget = this;
    // 컴포넌트 구현 초기화
    init();
    // 이벤트 연결
    bindEvents();
  };
  // 초기화 함수
  var init = function() {
    // 캐러셀 컴포넌트 객체 참조
    $tablist      = $widget.find('.ui-carousel-tablist');
    $tabs         = $widget.find('.ui-carousel-tab');
    $button_group = $widget.find('.ui-carousel-button-group');
    $wrapper      = $widget.find('.ui-carousel-tabpanel-wrapper');
    $panels       = $wrapper.find('.ui-carousel-tabpanel');
    // 초기 실행
    settingWrapperSize();
    resizeCarouselHeight();
    settings.using_autoPlay && autoPlay();
  };
  // 이벤트 연결 함수
  var bindEvents = function() {
    // 리사이즈 이벤트 핸들링
    $(global).on({
      'resize.change_carousel': resizeCarouselHeight,
      'resize.change_wrapper': settingWrapperSize
    });
    // 탭 이벤트 핸들링
    $.each($tabs, function(index) {
      var $tab = $tabs.eq(index);
      $tab.on({
        'click': $.proxy(activeTabPanel, $tab, index),
        'focus': stopPlay
      });
    });
    // 버튼 이벤트 핸들링
    $button_group.on('click', 'button', function(e){
      $.$(this).index() ? nextContent() : prevContent();
    });
    // 자동재생 이벤트 핸들링
    $widget.on({
      'mouseenter': stopPlay,
      'mouseleave': autoPlay
    });
  };
  // 래퍼 객체의 너비 설정 함수
  var settingWrapperSize = function() {
    // 컴포넌트 너비 구하기
    panel_width = $widget.width();
    // 패널 너비를 컴포넌트 너비로 설정
    $panels.width( panel_width );
    // 래퍼 너비를 컴포넌트 너비 x 패널 개수로 설정
    var wrapper_width = panel_width * $panels.length;
    $wrapper.width(wrapper_width);
    // 현재 활성화된 페이지를 재정렬
    activeTabPanel(settings.active_index);
  };
  // 창 크기 조정에 따른 캐러셀 높이 조정 함수
  var resizeCarouselHeight = function () {
    $widget.height( $panels.height() );
  };
  // 탭패널 활성화하는 함수
  var activeTabPanel = function(index, e) {
    e && e.preventDefault();
    // index 값으로 활성화 인덱스 업데이트
    settings.active_index = index;
    moveWrapperPosX( settings.active_index );
    updateIndicators( settings.active_index );
  };
  // 래퍼 객체를 이동시키는 함수
  var moveWrapperPosX = function(active_index) {
    var distance_x = -panel_width * active_index + 'px';
    settings.using_animation ?
      $wrapper.stop().animate({'left': distance_x}, 600) :
      $wrapper.css('left', distance_x);
  };
  // 업데이트 인디케이터 함수
  var updateIndicators = function(active_index) {
    $tabs.eq(active_index).parent().radioClass('active');
  };
  // 다음 콘텐츠 보기 함수
  var nextContent = function() {
    settings.active_index = ++settings.active_index % $panels.length;
    activeTabPanel(settings.active_index);
  };
  // 이전 콘텐츠 보기 함수
  var prevContent = function() {
    settings.active_index = --settings.active_index < 0 ? ($panels.length - 1) : settings.active_index;
    activeTabPanel(settings.active_index);
  };
  // 자동 재생 함수
  var autoPlay = function() {
    stop_id = global.playAnimation(nextContent, settings.rolling_time);
  };
  // 멈춤 함수
  var stopPlay = function() {
    global.stopAnimation(stop_id);
  };

  // 플러그인으로 함수 연결
  if (!$.fn.carousel) {
    $.fn.carousel = carousel;
    // 초기 옵션 설정(외부에서 접근 가능)
    $.fn.carousel.defaults = defaults;
  }

})(this, this.jQuery);

css

@font-face {
    font-family: "Nova Thin";
    src: url("../fonts/proximanova-thin-webfont.woff") format("woff")
}
@font-face {
    font-family: "Nova Regular";
    src: url("../fonts/proximanova-regular-webfont.woff") format("woff")
}
@font-face {
    font-family: "Nova Bold";
    src: url("../fonts/proximanova-bold-webfont.woff") format("woff")
}
.ui-carousel-tablist .ui-carousel-tab:focus,
.ui-carousel-button-group button:focus {
    outline: none;
    outline-offset: 10px;
    box-shadow: 0 0 0 4px rgba(90, 206, 255, 0.45);
    border-radius: 2px
}
.ui-carousel-tablist ul {
    list-style: none;
    margin-top: 0;
    margin-bottom: 0;
    padding-left: 0
}
.ui-carousel-button-group button {
    border: none;
    padding: 0;
    background: transparent
}
.readable-hidden {
    overflow: hidden;
    position: absolute;
    clip: rect(0, 0, 0, 0);
    width: 1px;
    height: 1px;
    margin: -1px;
    border: 0;
    padding: 0
}
.readable-hidden.focusable:focus {
    overflow: visible;
    position: static;
    clip: auto;
    width: auto;
    height: auto;
    margin: 0
}
body {
    margin: 0;
    font-family: "Nova Regular";
    color: #2b2b2b;
    background: #fff;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center
}
::selection {
    background: #ebc9c7;
    color: #4f1e19
}
.ui-carousel {
    overflow: hidden;
    position: relative;
    flex: 0 1 1200px;
    height: 700px;
    background: #fff;
    margin-left: auto;
    margin-right: auto
}
@media screen and (min-width: 660px) {
    .ui-carousel {
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
        border: 1px solid rgba(43, 43, 43, 0.2)
    }
}
.ui-carousel-headline {
    position: absolute;
    z-index: 1000;
    left: 30px;
    top: -3px;
    width: 10em;
    margin: 0;
    font-family: "Nova Thin";
    font-size: 14px;
    line-height: 1.34;
    color: #ac807a
}
@media screen and (min-width: 660px) {
    .ui-carousel-headline {
        top: 30px
    }
}
.ui-carousel-tablist {
    position: absolute;
    z-index: 1000;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    display: none
}
@media screen and (min-width: 660px) {
    .ui-carousel-tablist {
        display: block
    }
}
.ui-carousel-tablist ul {
    display: flex;
    justify-content: center
}
.ui-carousel-tablist li {
    margin-left: 8px;
    margin-right: 8px
}
.ui-carousel-tablist li.active .ui-carousel-tab {
    position: relative;
    top: -1px;
    background: #fff;
    border: 1px solid #5aceff;
    box-shadow: inset 0 0 10px rgba(90, 206, 255, 0.3)
}
.ui-carousel-tablist .ui-carousel-tab {
    color: #999;
    text-decoration: none;
    display: block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: currentColor
}
.ui-carousel-tablist .ui-carousel-tab:focus {
    border-radius: 50%
}
.ui-carousel-tablist .ui-carousel-tab:hover {
    color: #44ace2
}
.ui-carousel-button-group button {
    cursor: pointer;
    opacity: 0.45;
    position: absolute;
    z-index: 1000;
    top: 50%;
    width: 30px;
    height: 60px;
    transform: translateY(-50%)
}
.ui-carousel-button-group button:hover {
    opacity: 1
}
.ui-carousel-button-group button::before {
    content: '';
    position: absolute;
    width: 40px;
    height: 40px
}
.ui-carousel-button-group .ui-carousel-prev-button {
    left: 20px
}
.ui-carousel-button-group .ui-carousel-prev-button::before {
    top: 10px;
    left: 10px;
    transform: rotate(-45deg);
    border-top: 1px solid rgba(43, 43, 43, 0.7);
    border-left: 1px solid rgba(43, 43, 43, 0.7)
}
.ui-carousel-button-group .ui-carousel-next-button {
    right: 20px
}
.ui-carousel-button-group .ui-carousel-next-button::before {
    top: 10px;
    right: 10px;
    transform: rotate(45deg);
    border-top: 1px solid rgba(43, 43, 43, 0.7);
    border-right: 1px solid rgba(43, 43, 43, 0.7)
}
.ui-carousel-tabpanel-wrapper {
    overflow: hidden;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    text-align: center
}
.ui-carousel-tabpanel-wrapper img {
    margin-top: 30px;
    width: 50%;
    height: auto
}
.ui-carousel-tabpanel {
    margin: 0;
    float: left;
    width: 100%
}

ICO MOON

ICO MOON은 여러가지 폰트 아이콘을 한곳에 모아놓고, 원하는 아이콘만 뽑아 쓸 수 있는 사이트입니다. 이 사이트의 웹 아이콘을 이용하기 위해서는 유료 아이콘 팩을 구입하거나, 폰트 아이콘을 웹 폰트 형식으로 링크하여 이용할 수 있습니다. ICOMOON 사이트 우 상단 IcoMoon App 버튼 클릭 후 Add Icons From Library를 들어가면 다양한 폰트 아이콘을 이용할 수 있습니다. 원하는 아이콘을 선택 후, 필요에 따라 Cenerate SVG & More 버튼 또는 Generate Font 버튼을 클릭합니다.

ARIA 레이어 팝업 role="dialog"

https://youtu.be/_xCTEEacOpI?list=PLJJh8Uu1TAKGR7lr7aLa1K3cgkY6sEbcl

위의 영상 왼쪽은 WAI-ARIA를 적용하지 않은 경우이고 오른쪽은 WAI-ARIA를 적용한 경우입니다. 이때 왼쪽의 경우에는 <h1> 요소에 있는 내용을 음성 출력하기 때문에 팝업인지 정확히 알 수 없지만, 오른쪽 영상의 경우는 <h1> 요소에 있는 내용과 역할(Role)에 부여된 dialog로 인해 다이얼로그라는 음성을 추가적으로 출력해 주어 레이어 팝업임을 알 수 있습니다.

스크린 리더의 지원은 데스크톱에서도 괜찮은 편이지만, 모바일 OS 및 브라우저 (iOS / Safari / VoiceOver, Android / Firefox / Talkback)에서는 여전히 화면이 다릅니다.

  • Win 7, IE 9, JAWS 14 : 지원됨
  • Win 7, FF 18, JAWS 14 : 지원되지 않음
  • Win 7, IE9, NVDA 2012.2.1 지원
  • Win 7, FF 18, NVDA 2012.2.1 : 지원됨
  • Mac OS 10.8.X, Safari, VoiceOver : 지원됨
  • iOS 6.1, Safari, VoiceOver (iPad mini) : 부분 지원 (역할 대화 상자는 사용하지 않지만 aria-labelledby를 통해 연결된 대화 제목)
  • Android 4.2, Firefox, Talkback (Nexus 7) : 부분 지원 (대화 상자가 표시되지만 대화 상자를 닫은 후에 만 가능)

role=dialog 모달 대화 상자를 구현하기 위해 ARIA 를 사용하는 경우 다음을 따르세요

  1. 사용자 정의 대화 상자로 사용되는 role=dialog컨테이너 (예 : a div) 의 속성인지 확인하세요.
  2. 사용자 상호 작용이나 다른 이벤트에 따라 자바 스크립트를 통해 컨테이너가 삽입되었는지 확인하세요
  3. 대화 상자가 활성화되면 컨테이너의 요소에 포커스가 설정되어 있는지 확인하세요.
  4. 대화 상자가 활성화되면 컨테이너에없는 요소에 포커스가 설정되지 않았는지 확인하세요.
  5. 대화 상자가 비활성화되면 원래 대화 상자를 활성화 한 컨트롤에 포커스가 설정되어 있는지 확인하십시오.

출처: https://www.w3.org/WAI/GL/wiki/Using_ARIA_role%3Ddialog_to_implement_a_modal_dialog_box

HTML 문서를 PUG로 쉽게 바꿔주는 사이트

http://html2jade.org/ 변환과정에서 Space & Tab 간격을 설정할 수 있습니다.