λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Dev Note/JavaScript

Intersection Observer 둜 화면에 λ³΄μ΄λŠ” λΆ€λΆ„λ§Œ λ‘œλ”©ν•˜κΈ° (feat. Lazy loading)

by iyos 2023. 5. 21.

 

πŸ–₯️ 졜근 νšŒμ‚¬μ—μ„œ κ°œλ°œν•˜λ˜ κΈ°λŠ₯ 쀑 μš”μ†Œμ˜ κ°€μ‹œμ„±μ— 따라 API λ₯Ό ν˜ΈμΆœν•΄μ•Όν•˜λŠ” μž‘μ—…μ΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 
κΈ°μ‘΄μ—λŠ” νŽ˜μ΄μ§€μ— μ§„μž…ν•˜λŠ” μˆœκ°„ κ΄€λ ¨ APIλ₯Ό λͺ¨λ‘ ν˜ΈμΆœν•˜μ—¬ 화면에 κ·Έλ €μ£ΌλŠ” λ°©μ‹μœΌλ‘œ κ΅¬ν˜„μ΄ λ˜μ–΄μžˆμ—ˆλŠ”λ°μš”,
ν•΄μƒλ„λ‚˜ μœ μ €μ˜ μ„ΈνŒ… μƒνƒœμ— 따라 보이지 μ•ŠλŠ” κΈ°λŠ₯의 APIκΉŒμ§€ μ „λΆ€ ν˜ΈμΆœν•˜λ˜ 뢀뢄이 λΉ„νš¨μœ¨μ μ΄λΌκ³  νŒλ‹¨λ˜μ–΄, μΌλ°˜μ μœΌλ‘œλŠ” μ΄λ―Έμ§€λ₯Ό Lazy loading ν•˜λŠ”데에 주둜 μ“°μ΄λŠ” Intersection Observer API λ₯Ό ν™œμš©ν•˜μ—¬ μ„œλ²„λ‘œ ν˜ΈμΆœν•˜λŠ” API μžμ²΄λ„ Lazy loading μ„ μ‹œλ„ν•΄λ³΄μ•˜κ³ , κ΄€λ ¨ λ‚΄μš©μ„ κ³΅μœ ν•΄λ³΄λ €κ³  ν•©λ‹ˆλ‹€. 

 
 
 

Lazy loading μ΄λž€?

 
Lazy loading 은 '지연 λ‘œλ”©', 즉 λ¦¬μ†ŒμŠ€λ₯Ό μ‹λ³„ν•˜μ—¬ ν•„μš”ν•  λ•Œλ§Œ λ‘œλ“œν•˜λŠ” μ „λž΅μž…λ‹ˆλ‹€. 
 
웹이 λ°œμ „ν•¨μ— 따라 μ‚¬μš©μžμ—κ²Œ μ „μ†‘λ˜λŠ” assets의 μˆ˜μ™€ ν¬κΈ°λŠ” μ—„μ²­λ‚˜κ²Œ μ¦κ°€ν–ˆμŠ΅λ‹ˆλ‹€. 이 문제λ₯Ό ν•΄κ²°ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆλŠ” 방법 쀑 ν•˜λ‚˜κ°€ λ Œλ”λ§μ΄ λ°œμƒν•˜λŠ” 데 μ€‘μš”ν•˜μ§€ μ•Šμ€ λ¦¬μ†ŒμŠ€λ₯Ό 지연 λ‘œλ“œν•˜μ—¬, λ Œλ”λ§ 경둜 길이λ₯Ό μ€„μ΄λŠ” κ²ƒμž…λ‹ˆλ‹€.
지연 λ‘œλ”©μ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ—¬λŸ¬ μˆœκ°„μ— λ°œμƒν•  수 μžˆμ§€λ§Œ 일반적으둜 슀크둀 및 탐색과 같은 일뢀 μ‚¬μš©μž μƒν˜Έ μž‘μš©μ—μ„œ λ°œμƒν•©λ‹ˆλ‹€.

 
 κ°€μž₯ 일반적으둜 μ“°μ΄λŠ” μ˜ˆμ‹œλŠ” μ‡Όν•‘λͺ°μ΄λ‚˜ λΈ”λ‘œκ·Έ λ“± νƒ€μž„λΌμΈ ν˜•νƒœλ‘œ λ§Žμ€ 이미지와 ν•¨κ»˜ λžœλ”λ§λ˜λŠ” μ„œλΉ„μŠ€μΈ κ²½μš°μ— 이미지에 μ§€μ—°λ‘œλ”© 처리λ₯Ό ν•˜μ—¬  κ²½μš°κ°€ λ§ŽμŠ΅λ‹ˆλ‹€. 기본적으둜 CSSλŠ” λ Œλ”링 차단 λ¦¬μ†ŒμŠ€λ‘œ μ·¨κΈ‰λ˜κΈ° λ•Œλ¬Έμ— λΈŒλΌμš°μ €λŠ” CSSOM이 κ΅¬μ„±λ  λ•ŒκΉŒμ§€ 처리된 μ½˜ν…μΈ λ₯Ό λ Œλ”λ§ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ CSSλŠ” κ°€λŠ₯ν•œ ν•œ 빨리 μ „λ‹¬λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. λ”°λΌμ„œ λΈŒλΌμš°μ €κ°€ DOM 및 CSSOM을 μ™„λ£Œν•˜κ³ , λ Œλ”λ§ 트리λ₯Ό κ΅¬μΆ•ν•˜κ³ , ν‘œμ‹œλ˜λŠ” λͺ¨λ“  μ½˜ν…μΈ μ˜ μŠ€νƒ€μΌμ„ κ³„μ‚°ν•˜κ³ , λͺ¨λ“  λ Œλ” 트리 μš”μ†Œμ˜ μœ„μΉ˜μ™€ 크기λ₯Ό μ •μ˜ν•˜κΈ°κΉŒμ§€μ˜ κ³Όμ •μ—μ„œ λΆˆν•„μš”ν•œ μ»¨ν…μΈ μ˜ λ‘œλ”©μ„ μ΅œμ ν™” ν•˜κΈ° μœ„ν•΄μ„œ 지연 λ‘œλ”©μ˜ κ°œλ…μ΄ νƒ„μƒν•œ 것이죠. 
 

<img src="image.jpg" alt="..." loading="lazy" />
<iframe src="video-player.html" title="..." loading="lazy"></iframe>

 
 μ‚¬μ‹€ μ΄λ²ˆμ— μ €μ˜ κ²½μš°μ—” λΈŒλΌμš°μ € λ‘œλ”©μ„ μ΅œμ ν™”ν•˜κΈ° μœ„ν•œ λͺ©μ μœΌλ‘œ 지연 λ‘œλ”©μ„ μ‚¬μš©ν•œ 것은 μ•„λ‹™λ‹ˆλ‹€. λΈŒλΌμš°μ €μ— κ·Έλ €μ§€λŠ” μ½˜ν…μΈ λ₯Ό μ΅œμ ν™”ν•˜κΈ° μœ„ν•΄ 'ν•„μš”ν•œ λ¦¬μ†ŒμŠ€λ§Œ λ Œλ”λ§ν•œλ‹€' λŠ” μœ„ κ°œλ…μ—μ„œ μΈμ‚¬μ΄νŠΈλ₯Ό μ–»μ–΄, '보이지 μ•ŠλŠ” μˆ˜λ§Žμ€ 값듀을 ν•œλ²ˆμ— λΆˆλŸ¬μ˜€λŠ”κ±΄ λΉ„νš¨μœ¨μ μ΄λ‹ˆ, 화면에 κ·Έλ €μ§€λŠ” λΆ€λΆ„λ§Œ API ν˜ΈμΆœμ„ λ¨Όμ € ν•˜λ©΄ λ˜μ§€ μ•Šμ„κΉŒ?' λΌλŠ” 생각을 ν•˜κ²Œ λ˜μ—ˆκ³ , Lazy loading 을 μœ„ν•΄ μ“°μ΄λŠ” 방법 쀑 μ €μ˜ μΌ€μ΄μŠ€μ— λ„μž…ν•΄λ³΄κΈ°μ— μ ν•©ν•˜λ‹€κ³  νŒλ‹¨λœ Intersection Observer API λ₯Ό ν™œμš©ν•˜κ²Œ 된 경우이죠. 보편적으둜 μ†Œκ°œλ˜λŠ” '이미지λ₯Ό μ§€μ—°λ‘œλ”©ν•˜λŠ” 경우'κ°€ μ•„λ‹ˆλ”λΌλ„ Intersection Observer λ₯Ό λ‹€μ–‘ν•˜κ²Œ ν™œμš©ν•  수 μžˆλ‹€λŠ” 것을 μ†Œκ°œν•˜λŠ” μ •λ„μ˜ κΈ€λ‘œ μ΄ν•΄ν•΄μ£Όμ‹œλ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€. 
 
 
 
 

Intersection Observer API λž€?

 
Intersection Observer APIλŠ” νƒ€κ²Ÿ μš”μ†Œμ™€ μƒμœ„ μš”μ†Œ λ˜λŠ” μ΅œμƒμœ„ document 의 viewport μ‚¬μ΄μ˜ intersection λ‚΄μ˜ λ³€ν™”λ₯Ό λΉ„λ™κΈ°μ μœΌλ‘œ κ΄€μ°°ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€. κ³΅μ‹λ¬Έμ„œμ—μ„œ μ†Œκ°œν•˜λŠ” ν™œμš© μΌ€μ΄μŠ€λŠ” μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€. μ €μ˜ κ²½μš°λŠ” λ„€λ²ˆμ§Έμ— 해당이 λ˜κ² λ„€μš”.
 

  • νŽ˜μ΄μ§€κ°€ 슀크둀 λ˜λŠ” 도쀑에 λ°œμƒν•˜λŠ” μ΄λ―Έμ§€λ‚˜ λ‹€λ₯Έ μ»¨ν…μΈ μ˜ 지연 λ‘œλ”©.
  • 슀크둀 μ‹œμ—, 더 λ§Žμ€ 컨텐츠가 λ‘œλ“œ 및 λ Œλ”λ§λ˜μ–΄ μ‚¬μš©μžκ°€ νŽ˜μ΄μ§€λ₯Ό μ΄λ™ν•˜μ§€ μ•Šμ•„λ„ 되게 ν•˜λŠ” infinite-scroll 을 κ΅¬ν˜„.
  • κ΄‘κ³  μˆ˜μ΅μ„ κ³„μ‚°ν•˜κΈ° μœ„ν•œ μš©λ„λ‘œ κ΄‘κ³ μ˜ κ°€μ‹œμ„± 보고.
  • μ‚¬μš©μžμ—κ²Œ κ²°κ³Όκ°€ ν‘œμ‹œλ˜λŠ” 여뢀에 따라 μž‘μ—…μ΄λ‚˜ μ• λ‹ˆλ©”μ΄μ…˜μ„ μˆ˜ν–‰ν•  지 μ—¬λΆ€λ₯Ό κ²°μ •.

과거에 intersection 감지λ₯Ό κ΅¬ν˜„ν•˜λ©΄ 영ν–₯을 λ°›λŠ” λͺ¨λ“  μš”μ†Œλ₯Ό μ•ŒκΈ° μœ„ν•΄μ„œ Element.getBoundingClientRect()와 같은 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” μ—¬λŸ¬ 이벀트 ν•Έλ“€λŸ¬μ™€ 루프가 μ–½ν˜€μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ν™”λ©΄μ˜ 크기λ₯Ό λ¨Όμ € κ³„μ‚°ν•˜κ³ , μš”μ†Œμ˜ μœ„μΉ˜λ₯Ό κ³„μ‚°ν•˜λŠ” λ“±μ˜ λͺ¨λ“  μ½”λ“œκ°€ 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜κΈ° λ•Œλ¬Έμ—, 이 쀑 ν•˜λ‚˜λΌλ„ μ„±λŠ₯ 문제λ₯Ό μΌμœΌν‚¬ 수 있고 μ΄λŸ¬ν•œ 계산 λ‘œμ§λ“€κ³Ό ν•¨κ»˜ νŽ˜μ΄μ§€κ°€ λ‘œλ“œλ˜λ©΄ 상황이 λ”μš± λ‚˜λΉ μ§ˆ 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
 

ν•˜μ§€λ§Œ Intersection Observer API λŠ” κ°μ‹œν•˜κ³ μž ν•˜λŠ” μš”μ†Œκ°€ λ‹€λ₯Έ μš”μ†Œ(viewport)에 λ“€μ–΄κ°€κ±°λ‚˜ λ‚˜κ°ˆλ•Œ λ˜λŠ” μš”μ²­ν•œ λΆ€λΆ„λ§ŒνΌ 두 μš”μ†Œμ˜ ꡐ차뢀뢄이 변경될 λ•Œ λ§ˆλ‹€ 싀행될 콜백 ν•¨μˆ˜λ₯Ό 등둝할 수 μžˆμŠ΅λ‹ˆλ‹€. 즉, μš”μ†Œμ˜ ꡐ차λ₯Ό μ§€μΌœλ³΄κΈ° μœ„ν•΄ 메인 μŠ€λ ˆλ“œλ₯Ό μ‚¬μš©ν•  ν•„μš”κ°€ 없어지고, λΈŒλΌμš°μ €λŠ” μ›ν•˜λŠ” λŒ€λ‘œ ꡐ차 μ˜μ—­ 관리λ₯Ό μ΅œμ ν™” ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„μ§κΉŒμ§€λŠ” μ •ν™•νžˆ λͺ‡ 픽셀이 겹쳐쑌고 μ–΄λ– ν•œ 픽셀이 κ²Ήμ³μ‘ŒλŠ”μ§€λŠ” μ•Œ μˆ˜κ°€ μ—†μ§€λ§Œ, "N% 정도 κ΅μ°¨ν•˜λŠ” 경우 μƒν˜Έμž‘μš©μ΄ μ΄λ£¨μ–΄μ Έμ•Όν•œλ‹€." 와 같은 κ²½μš°μ—” μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

 
 
 

ν™œμš© 방법

 

1. Observer 생성

let options = {
  root: document.querySelector('#κ΄€μ°°μš”μ†Œλ₯Όν¬ν•¨ν•œμ˜μ—­'),
  rootMargin: '0px',
  threshold: 0.7
}

let observer = new IntersectionObserver(callback, options);

 

1) root

λŒ€μƒ 객체의 κ°€μ‹œμ„±μ„ 확인할 λ•Œ μ‚¬μš©λ˜λŠ” 뷰포트 μš”μ†Œμž…λ‹ˆλ‹€. μ΄λŠ” κ΄€μ°°ν•˜κ³ μžν•˜λŠ” λŒ€μƒ 객체의 쑰상 μš”μ†Œμ—¬μ•Ό ν•©λ‹ˆλ‹€. 기본값은 λΈŒλΌμš°μ € 뷰포트이며, root κ°’이 null μ΄κ±°λ‚˜ μ§€μ •λ˜μ§€ μ•Šμ„ λ•Œ κΈ°λ³Έκ°’μœΌλ‘œ μ„€μ •λ©λ‹ˆλ‹€.
 

2) rootMargin

root κ°€ 가진 μ—¬λ°±μœΌλ‘œ, CSS의 margin 속성과 μœ μ‚¬ν•©λ‹ˆλ‹€. 기본값은 0μž…λ‹ˆλ‹€.
 

3) threshold

observer의 콜백이 싀행될 λŒ€μƒ μš”μ†Œμ˜ κ°€μ‹œμ„± 퍼센티지λ₯Ό λ‚˜νƒ€λ‚΄λŠ” 단일 숫자 ν˜Ήμ€ 숫자 λ°°μ—΄μž…λ‹ˆλ‹€. 만일 50%만큼 μš”μ†Œκ°€ λ³΄μ—¬μ‘Œμ„ λ•Œλ₯Ό νƒμ§€ν•˜κ³  μ‹Άλ‹€λ©΄, 값을 0.5둜 μ„€μ •ν•˜λ©΄ λ©λ‹ˆλ‹€. ν˜Ήμ€ 25% λ‹¨μœ„λ‘œ μš”μ†Œμ˜ κ°€μ‹œμ„±μ΄ 변경될 λ•Œλ§ˆλ‹€ 콜백이 μ‹€ν–‰λ˜κ²Œ ν•˜κ³  μ‹Άλ‹€λ©΄ [0, 0.25, 0.5, 0.75, 1] κ³Ό 같은 배열을 μ„€μ • ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 기본값은 0이고, 0으둜 μ„€μ •ν•˜λŠ” κ²½μš°λŠ” λŒ€μƒ μš”μ†Œκ°€ 1픽셀이라도 보이면 λ°”λ‘œ 콜백이 싀행됨을 μ˜λ―Έν•©λ‹ˆλ‹€. 그리고 λ°˜λŒ€λ‘œ 1.0은 μš”μ†Œμ˜ λͺ¨λ“  픽셀이 화면에 λ…ΈμΆœλ˜κΈ° μ „μ—λŠ” μ½œλ°±μ„ μ‹€ν–‰μ‹œν‚€μ§€ μ•ŠμŒμ„ μ˜λ―Έν•©λ‹ˆλ‹€.

 
 
 
 

2. κ΄€μ°°ν•  μš”μ†Œ νƒ€κ²ŸνŒ…

let target = document.querySelector('#κ΄€μ°°ν• μš”μ†Œ');
observer.observe(target);

 
 

3. callback 

μ΄μ œλŠ” κ΄€μ°°ν•  μš”μ†Œκ°€ 1λ²ˆμ—μ„œ μ§€μ •ν•œ μ˜΅μ…˜μ˜ μž„κ³„κ°’μ„ μΆ©μ‘±ν•  λ•Œλ§ˆλ‹€ μ½œλ°±μ΄ ν˜ΈμΆœλ©λ‹ˆλ‹€.

callback(entries, observer) {
  entries.forEach(entry => {
  
    // κ΄€μ°°λ˜λŠ” μš”μ†Œλ“€μ΄ entries 둜 λ“€μ–΄μ˜€κ³ , entry 둜 각각의 μš”μ†Œμ˜ 값을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. 
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
    
    
    // 그리고 μš°λ¦¬λŠ” μ•„λž˜μ˜ ν˜•νƒœλ‘œ μ½œλ°±μ„ ν™œμš©ν•  수 μžˆλŠ” κ²ƒμž…λ‹ˆλ‹€.
    if (entry.intersectionRatio >= 0.75) { // κ΄€μ°° λŒ€μƒμ΄ viewport μ•ˆμ— λ“€μ–΄μ˜¨ 경우
        this.isIntersecting = true;
        console.log('이제 75% λˆˆμ— λ³΄μ΄λ‹ˆκΉŒ 할일을 해라.')
        this.action();
    } else if (!entry.isIntersecting) {  // κ΄€μ°° λŒ€μƒμ˜ ꡐ차 μƒνƒœκ°€ false 일 (보이지 μ•ŠλŠ”) 경우
        this.isIntersecting = false;
    }
    
  });
};

νšŒμ‚¬μ˜ μ½”λ“œλ₯Ό κ°€μ Έμ˜¬ μˆ˜λŠ” μ—†μ–΄ 콜백의 λΆ€λΆ„μ˜ ꡬ쑰λ₯Ό κ°€μ Έμ™€λ³΄μ•˜μŠ΅λ‹ˆλ‹€.
 
μ €μ˜ κ²½μš°μ—” ν΄λž˜μŠ€ν˜•μœΌλ‘œ κ΅¬ν˜„λœ μ»΄ν¬λ„ŒνŠΈμ— μœ„ 콜백 ν•¨μˆ˜λ₯Ό 넣어놓고, μ»΄ν¬λ„ŒνŠΈκ°€ λ§Œλ“€μ–΄μ§€λŠ” μˆœκ°„ ν•΄λ‹Ή ν•¨μˆ˜λ₯Ό 콜백으둜 λ“±λ‘ν•œ μ˜΅μ €λ²„λ₯Ό μƒμ„±μ‹œμΌ°μŠ΅λ‹ˆλ‹€. 그리고 ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈκ°€ 75νΌμ„ΌνŠΈ 이상 λ·°ν¬νŠΈμ— λ“€μ–΄μ˜€λ©΄ action ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λŠ” ν˜•νƒœλ‘œ κ΅¬ν˜„μ„ ν•˜κ³ , 쀑볡싀행을 λ°©μ§€ν•˜κΈ° μœ„ν•œ ν”Œλž˜κ·Έ κ°’μœΌλ‘œ isIntersecting λ₯Ό μ„€μ •ν•˜μ—¬ action ν•¨μˆ˜κ°€ μ€‘λ³΅μ‹€ν–‰λ˜μ§€ μ•Šλ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. μ°Έ 쉽죠!!?
 
 
 

마치며

ν”„λ‘ νŠΈμ—”λ“œ μ„±λŠ₯ μ΅œμ ν™”λ₯Ό μœ„ν•΄ Intersection Observer λ₯Ό ν™œμš©ν•˜λŠ” 방법을 μ†Œκ°œν•΄ λ³΄μ•˜μŠ΅λ‹ˆλ‹€.
μ‹€μ œλ‘œ 해상도에 따라 λ³΄μ΄λŠ” μš”μ†Œλ§Œ 골라 API λ₯Ό ν˜ΈμΆœν•˜λ„λ‘ μ΅œμ’… κ΅¬ν˜„λœ λͺ¨μŠ΅μ„ 이해λ₯Ό μœ„ν•΄ μ˜ˆμ‹œλ‘œ λ„£κ³  μ‹Άμ—ˆμ§€λ§Œ, 아직 μ„œλΉ„μŠ€μ—μ„œ 전체 μœ μ €μ—κ²Œ μ˜€ν”ˆλœ κΈ°λŠ₯이 μ•„λ‹ˆλΌ μ•„μ‰½κ²Œλ„ 넣지 λͺ»ν–ˆμŠ΅λ‹ˆλ‹€. μΆ”ν›„ ν•΄λ‹Ή κΈ°λŠ₯이 μ„±κ³΅μ μœΌλ‘œ μ˜€ν”ˆλ˜λ©΄ μ–΄λ–€ ν˜•νƒœλ‘œ μš΄μ˜ν™˜κ²½μ—μ„œ λ™μž‘μ€‘μΈμ§€, ν˜Ήμ‹œλΌλ„ μœ„ API λ₯Ό ν˜„μ—…μ—μ„œ ν™œμš©ν• λ•Œ 더 κ³ λ €ν•  수 μžˆλŠ” 뢀뢄은 μ—†λŠ”μ§€μ— λŒ€ν•œ λ‚΄μš©μœΌλ‘œ λ³΄μ™„ν•΄μ„œ λŒμ•„μ˜€κ² μŠ΅λ‹ˆλ‹€. κ°μ‚¬ν•©λ‹ˆλ‹€.
 
 
 
(μœ„ λ‚΄μš©μ—μ„œ λΆ€μ‘±ν•œ 뢀뢄을 λ°œκ²¬ν–ˆκ±°λ‚˜ μΆ”κ°€ 의견 ν˜Ήμ€ κ²½ν—˜μ΄ μžˆμœΌμ‹œλ‹€λ©΄, μ–Έμ œλ“ μ§€ λŒ“κΈ€ λŒ€ν™˜μ˜μž…λ‹ˆλ‹€ πŸ™ŒπŸ»)
 
 
 
 

λ°˜μ‘ν˜•

'Dev Note > JavaScript' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

크둬 κ°œλ°œμžλ„κ΅¬ λœ―μ–΄λ³΄κΈ°  (0) 2023.04.23