[JS]scrollIntoView event handlers, bubbling & capturing, event delegation, DOM traversing


コースソース:完全JavaScript Course 2022 Jonas(Udemy)
Implementing Smooth Scrolling
スクロールに関する良い情報を理解します.
  • getBoundingClientRect() : relative to visible viewport
  • window.screen:
  • ディスプレイサイズ
  • window.outer:page以外のブラウザの合計サイズ(url+tab+page)
  • window.inner:スクロールバー付きビューポート
  • document.documentElement.clientHeight, document.documentElement.ClientWidth:ビューポートの高さと幅.スクロールバー
  • は含む.
  • window.pageXOffset, window.pageYOffset:viewportの末尾で、ページ上部の長さは
  • です.
    scrollIntoView()
    const btnScrollTo = document.querySelector('.btn--scroll-to');
    const section1 = document.querySelector('#section--1');
    
    btnScrollTo.addEventListener('click', e => {
      const s1coords = section1.getBoundingClientRect();
      console.log(s1coords);
    
      console.log(e.target.getBoundingClientRect());
      console.log('Current scroll (X/Y)', window.pageXOffset, window.pageYOffset); 
    
      console.log(
        'height/width viewport',
        document.documentElement.clientHeight,
        document.documentElement.clientWidth 
      );
    
    //scrolling
    window.scrollTo(
      s1coords.left + window.pageXOffset,
      s1coords.top + window.pageYOffset
    );
    
    window.scrollTo({
      left: s1coords.left + window.pageXOffset,
      top: s1coords.top + window.pageYOffset,
      behavior: 'smooth',
    });
    
    // modern way
     section1.scrollIntoView({ behavior: 'smooth' });
    });
    Types of Events and Event Handlers
    addEvenetListener, removeEventListener
    const h1 = document.querySelector('h1');
    
    const alertH1 = e => {
      alert('addEventListener : Great! You are reading the heading :D');
    
      h1.removeEventListener('mouseenter', alertH1); //event 가 한번만 실행되고 제거된다.
    };
    
    h1.addEventListener('mouseenter', alertH1);
    
    setTimeout(() => h1.removeEventListener('mouseenter', alertH1), 3000);
    
    //old way
    h1.onmouseenter = e => {
      alert('addEventListener : Great! You are reading the heading :D');
    };
    Event Propagation : Bubbling and Capturing
    イベント発生原理
    イベントが発生した場合は、要素ではなく一番上のDocumentに渡されます.
  • 取得フェーズ:ドキュメントからターゲットフェーズの親要素からターゲットの部分まで.
  • targetフェーズ:イベントが発生したtarget上でコールバック関数を実行します.
  • バブルフェーズ:targetから親要素に再移行し、最初のイベントからドキュメントの部分に移行します.このセクションでは、親要素にtargetと同じイベントがある場合、一緒に発生します.親コンテナは、イベントがどのサブエレメントで発生しても、すべてのイベントをリスニングできます.
  • Event Propagation in Practice
    rgb(255, 255, 255); ランダムカラー関数の作成
    const randomInt = (min, max) =>
      Math.floor(Math.random() * (max - min + 1) + min);
    const randomColor = () =>
      `rgb(${randomInt(0, 255)},${randomInt(0, 255)},${randomInt(0, 255)})`;
    
    document.querySelector('.nav__link').addEventListener('click', function (e) {
      this.style.backgroundColor = randomColor();
      console.log('LINK', e.target, e.currentTarget);
    
      // Stop propogation 사용하지 않는게 좋음
      //e.stopPropagation(); //bubbling 일어나지 않음
    });
    
    document.querySelector('.nav__links').addEventListener('click', function (e) {
      this.style.backgroundColor = randomColor();
      console.log('CONTAINER', e.target, e.currentTarget);
    });
    
    document.querySelector('.nav').addEventListener(
      'click',
      function (e) {
        this.style.backgroundColor = randomColor();
        console.log('NAV', e.target, e.currentTarget);
      },
      true // event handling이 bubbling을 멈추고 capturing event 시작한다.
      // capturing phase : nav -> link -> container 순서로 event 발생 (nav는 capturing, 나머지 둘은 bubbling phase 이므로)
    );
    
    「.navlink」イベントを実行するときに親要素として使用します.およびnavlinksnavはすべて実行されます.
    => Event propagation
    this==e.currentTarget/e.targetとは、実際にイベントが発生した要素です.泡が立つから.navのイベントが発生しても、e.targetは.nav linkを指します!
    Event Delegation
    Implementing Page Navigation by using Event Delegation
    委任イベントの利点
    同じイベントを複数の要素で実行する必要がある場合は、=>共通の親要素でイベント(委任)を実行することによって一貫した関数を作成することを省略できます.すなわち、繰り返しのイベントを処理する場合、すなわち、親の子供がイベントを共同で処理する必要がある場合、イベントリスナーを毎日サブノードに追加するよりも、親に登録するほうがよい.
    イベント委任ロジック
  • Add event listener to common parent element
  • Determine what element originated the event
  • Matching strategy => if(element.classList.contains('')){}
  • document.querySelector('.nav__links').addEventListener('click', e => {
      e.preventDefault();
      // Matching strategy
      if (e.target.classList.contains('nav__link')) {
        const id = e.target.getAttribute('href');
        if (id === '#') {
          return;
        }
        document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
      }
    });
    注)link href='#id'=>は、対応するIDを使用して自動的にスクロールする基本機能を備えています.
    DOM Traversing
    DOM Traversing : walking through the DOM, we can select an element based on another element
    Going downwards : child
    querySelector
    const h1 = document.querySelector('h1');
    console.log(h1.querySelectorAll('.highlight'));
    console.log(h1.childNodes);
    //NodeList(9) [text, comment, text, span.highlight, text, br, text, span.highlight, text]
    // Nodes can be anything!
    console.log(h1.children); //HTMLCollection(3) [span.highlight, br, span.highlight]
    h1.firstElementChild.style.color = 'white';
    h1.lastElementChild.style.color = 'orangered';
    
    Going upwards : parents
    closest
    console.log(h1.parentNode); //<div class="header__title">...</div>
    console.log(h1.parentElement); //<div class="header__title">...</div>
    
    //closest(selectors) 지정한 선택자와 가장 가깝게 조건에 만족한 부모요소가 반환된다.
    h1.closest('.header').style.background = 'var(--gradient-secondary)'; 
    // header에 가장 가까운 h1의 부모요소에 효과 적용된다.
    h1.closest('h1').style.background = 'var(--gradient-primary)'; 
    // h1 그 자체
    最も近いのはquerySelectorとは全く逆の概念と考えられる.どちらもセレクタが受信されますが、querySelectorはchild elementを選択し、最近のparent elementを選択します.
    Going side ways : siblings
    console.log(h1.previousElementSibling); //바로 전 sibling elememnt
    console.log(h1.nextElementSibling); // 바로 다음 sibling element
    
    console.log(h1.previousSibling); //바로 전 sibling node
    console.log(h1.nextSibling); //바로 다음 sibling node
    
    console.log(h1.parentElement.children); // h1을 포함한 모든 siblings 출력.
    [...h1.parentElement.children].forEach(function (el) {
      if (el !== h1) el.style.transform = 'scale(0.5)';
    });
    
    Tabbed component
    tabsContainer.addEventListener('click', e => {
      const clicked = e.target.closest('.operations__tab'); //span 부분을 눌러도 button 선택된다!
      //Guard clause -modern way (빠른 return)
      if (!clicked) return;
    
      // Remove active classes
      tabs.forEach(t => t.classList.remove('operations__tab--active')); 
      // 흔한 logic! 하나의 element에만 classList를 추가하고 싶다면 추가 전에 모든 요소의 classList clear 해주기!
      tabsContent.forEach(c => c.classList.remove('operations__content--active'));
    
      //Active tab
      clicked.classList.add('operations__tab--active');
    
      //Active content area
      document
        .querySelector(`.operations__content--${clicked.dataset.tab}`)
        .classList.add('operations__content--active');
    });