• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    미해결

add.js파일 적용 후 pin 오류

21.02.22 18:48 작성 조회수 210

0

안녕하세요 선생님 강의 잘 듣고 있습니다 ㅎㅎ

다름이 아니라 main_add.js 파일 적용 후 pin부분때문에

Uncaught TypeError: Cannot read property 'style' of null 

오류가 뜹니다... css파일과 html파일도 찾아보고 있는데 

혼자서 해결해 보려고 했지만 시간만가고 해결책이 없는거 같아 

질문드립니다 ㅎㅎ

사진은 오류가 나는 제 페이지입니다 ㅠ

js코드

(() => { //이거슨 즉시 호출 함수 즉, (function(){      }()); 익명함수를  한번싼고 호출한거랑 같음. 이렇게하는 이유는 전역변수를 피하려고

    let yOffset = 0; // window.pageYoffset 대신 쓸 변수
    let prevScrollHeight = 0; //현재 스크롤 위치(yOffeset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이값의 합
    let currentScene = 0; //현재 활성화된 (눈 앞에 보고있는) 씬(scroll-section)
    let enterNewScene = false; //새로운 scene이 시작되는 순간 true

    const sceneInfo = [
        {
            //0
            type: 'sticky',
            heighNum: 5, //브라우저 높이의 5배로 scrollHeight 세팅
            scrollHeight: 0,
            objs:{ // html DOM 객체 요소들
                container: document.querySelector('#scroll-section-0'),
                messageA: document.querySelector('#scroll-section-0 .main-message.a'),
                messageB: document.querySelector('#scroll-section-0 .main-message.b'),
                messageC: document.querySelector('#scroll-section-0 .main-message.c'),
                messageD: document.querySelector('#scroll-section-0 .main-message.d'),
            },
            values: { //값에 해당하는 요소들
                messageA_opacity_in: [0, 1, { start: 0.1, end: 0.2 }], //{}안의 값은 애니메이션이 재생되는 구간 소수점인 이유는 비율로 했기때문에, 전체 스크롤에서 10% ~ 20%만 애니메이션으로 쓰겠다
                messageB_opacity_in: [0, 1, { start: 0.3, end: 0.4 }],
                messageC_opacity_in: [0, 1, { start: 0.5, end: 0.6 }],
                messageD_opacity_in: [0, 1, { start: 0.7, end: 0.8 }],

                messageA_translateY_in: [20, 0, { start: 0.1, end: 0.2 }],
                messageB_translateY_in: [20, 0, { start: 0.3, end: 0.4 }],
                messageC_translateY_in: [20, 0, { start: 0.5, end: 0.6 }],
                messageD_translateY_in: [20, 0, { start: 0.7, end: 0.8 }],

                messageA_opacity_out: [1, 0, { start: 0.25, end: 0.3 }],
                messageB_opacity_out: [1, 0, { start: 0.45, end: 0.5 }],
                messageC_opacity_out: [1, 0, { start: 0.65, end: 0.7 }],
                messageD_opacity_out: [1, 0, { start: 0.85, end: 0.9 }],

                messageA_translateY_out: [0, -20, { start: 0.25, end: 0.3 }],
                messageB_translateY_out: [0, -20, { start: 0.45, end: 0.5 }],
                messageC_translateY_out: [0, -20, { start: 0.65, end: 0.7 }],
                messageD_translateY_out: [0, -20, { start: 0.85, end: 0.9 }]
            }
        },
        {
            //1
            type: 'normal', //type normal에서는 height를 기본 default로 잡기때문에 필요가 없다
            //heighNum: 5, //브라우저 높이의 5배로 scrollHeight 세팅
            scrollHeight: 0,
            objs:{
                container: document.querySelector('#scroll-section-1')
            }
        },
        {
            //2
            type: 'sticky',
            heighNum: 5, //브라우저 높이의 5배로 scrollHeight 세팅
            scrollHeight: 0,
            objs:{
                container: document.querySelector('#scroll-section-2'),
                messageA: document.querySelector('#scroll-section-2 .a'),
                messageB: document.querySelector('#scroll-section-2 .b'),
                messageC: document.querySelector('#scroll-section-2 .c'),
                pinB: document.querySelector('#scroll-section-2 .b .pin'),
                pinC: document.querySelector('#scroll-section-2 .c .pin')
            },
            values: {
                messageA_translateY_in: [20, 0, { start: 0.15, end: 0.2 }],
                messageB_translateY_in: [30, 0, { start: 0.6, end: 0.65 }],
                messageC_translateY_in: [30, 0, { start: 0.87, end: 0.92 }],

                messageA_opacity_in: [0, 1, { start: 0.25, end: 0.3 }],
                messageB_opacity_in: [0, 1, { start: 0.6, end: 0.65 }],
                messageC_opacity_in: [0, 1, { start: 0.87, end: 0.92 }],

                messageA_translateY_out: [0, -20, { start: 0.4, end: 0.45 }],
                messageB_translateY_out: [0, -20, { start: 0.68, end: 0.73 }],
                messageC_translateY_out: [0, -20, { start: 0.95, end: 1 }],

                messageA_opacity_out: [1, 0, { start: 0.4, end: 0.45 }],
                messageB_opacity_out: [1, 0, { start: 0.68, end: 0.73 }],
                messageC_opacity_out: [1, 0, { start: 0.95, end: 1 }],

                pinB_scaleY: [0.5, 1, { start: 0.6, end: 0.65 }],
                pinC_scaleY: [0.5, 1, { start: 0.87, end: 0.92 }]
            }
        },
        {
            //3
            type: 'sticky',
            heighNum: 5, //브라우저 높이의 5배로 scrollHeight 세팅
            scrollHeight: 0,
            objs:{
                container: document.querySelector('#scroll-section-3'),
                canvasCaption: document.querySelector('.canvas-caption')
            },
            values:{

            }
        }
    ];

    function setLayout(){ //각 스크롤 색션의 높이 세팅
        for(let i = 0; i < sceneInfo.length; i++){
            if(sceneInfo[i].type === 'sticky'){
                sceneInfo[i].scrollHeight = sceneInfo[i].heighNum * window.innerHeight;
                sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; // ``는 ''가 아니라 ~와 같이 있는것인데, 백틱이라고 한다
            }else if(sceneInfo[i].type === 'normal'){
                sceneInfo[i].scrollHeight = sceneInfo[i].objs.container.offsetHeight;
            }
            sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}`;
           
        }

        yOffset = window.pageYOffset;

        let totalScrollHeight = 0;
        for(let i =0; i < sceneInfo.length; i++){ 
            totalScrollHeight += sceneInfo[i].scrollHeight; //각 씬에 scrollHeight를 더해준다
            if(totalScrollHeight >= yOffset){ //totalScrollHeight에 들어가는 값이랑 현재 스크롤위치값이랑 비교해서 현재 스크롤위치가 total보다 커지면 현재 위치를 currentScene으로 세팅하고 for문을 빠져나옴 
                currentScene = i;
                break;
            }
        }
        document.body.setAttribute('id', `show-scene-${currentScene}`);
    }

    function calcValues(values, currentYOffset){ //여기서 values는 opacity 0, 1 넣은 그 배열 , offset은 현재씬에서 얼마나 스크롤 됐는지
        let rv;
        //현재 씬(스크롤섹션)에서 스크롤된 범위를 비율로 구하기
        const scrollHeight = sceneInfo[currentScene].scrollHeight;
        const scrollRatio = currentYOffset / scrollHeight; 
        
        if(values.length === 3){ 
            //start ~ end 사이에 애니메이션 실행
            const partScrollStart = values[2].start * scrollHeight;
            const partScrollEnd = values[2].end * scrollHeight;
            const partScrollHeight = partScrollEnd - partScrollStart;

            if(currentYOffset >= partScrollStart && currentYOffset <= partScrollEnd){
                rv = (currentYOffset - partScrollStart) / partScrollHeight * (values[1] - values[0]) + values[0]; //여기는 부분 스크롤 영역의 비율이 반영되야함
            }else if(currentYOffset < partScrollStart){
                rv = values[0];
            }else if(currentYOffset > partScrollEnd){
                rv = values[1];
            }
        }else{
            rv = scrollRatio * (values[1] - values[0]) + values[0]; //여기는 현재 씬 전체 범위에서 지금 스크롤된 비율을 곱해준것

        }

        return rv;
    }

    function playAnimation(){
        const objs = sceneInfo[currentScene].objs;
        const values = sceneInfo[currentScene].values;
        const currentYOffset = yOffset - prevScrollHeight; //현재 씬에서 스크롤된 높이를 나타냄
        const scrollHeight = sceneInfo[currentScene].scrollHeight;
        const scrollRatio = currentYOffset / scrollHeight; // yOffset(전체에서 현재 스크롤값) / 현재 씬의 scrollHeight

        switch(currentScene){
            case 0:
              
                if(scrollRatio <= 0.22){
                        // in
                    objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset);
                    objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`;
                } else {
                    // out
                    objs.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset);
                    objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`;
                }

                if (scrollRatio <= 0.42) {
                    // in
                    objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset);
                    objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`;
                } else {
                    // out
                    objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset);
                    objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`;
                }

                if (scrollRatio <= 0.62) {
                    // in
                    objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset);
                    objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`;
                } else {
                    // out
                    objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset);
                    objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`;
                }

                if (scrollRatio <= 0.82) {
                    // in
                    objs.messageD.style.opacity = calcValues(values.messageD_opacity_in, currentYOffset);
                    objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_in, currentYOffset)}%, 0)`;
                } else {
                    // out
                    objs.messageD.style.opacity = calcValues(values.messageD_opacity_out, currentYOffset);
                    objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_out, currentYOffset)}%, 0)`;
                }
            
                break;
            // case 1:

            //     break;    !!!case1은 2번째 섹션인데 그거슨 normal이라서 뺏음
            case 2:
                if (scrollRatio <= 0.32) {
                    // in
                    objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset);
                    objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`;
                } else {
                    // out
                    objs.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset);
                    objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`;
                }
    
                if (scrollRatio <= 0.67) {
                    // in
                    objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`;
                    objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset);
                    objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`;
                } else {
                    // out
                    objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`;
                    objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset);
                    objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`;
                }
    
                if (scrollRatio <= 0.93) {
                    // in
                    objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`;
                    objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset);
                    objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`;
                } else {
                    // out
                    objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`;
                    objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset);
                    objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`;
                }
                //translateY를 translate3d로 바꿨는데 translate3d(x값, y값, z값)이다. 이것을 쓴 이유는 브라우저 업데이트에 따라 달라질 수 있는데
                //transform속성중 3d가 붙은 애들은 하드웨어 가속이 보장이 된다 즉, 퍼포먼스가 좋다. 그래서 애플에서도 3d이동이 아니더라도 3d를 쓴다
                break;
            case 3:

                break;
        }
    }
  
    function scrollLoop(){
        enterNewScene = false;
        prevScrollHeight = 0;
        for(let i = 0; i < currentScene; i++){
            prevScrollHeight += sceneInfo[i].scrollHeight;
        }

        if(yOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight){
            enterNewScene = true;
            currentScene++;
            document.body.setAttribute('id', `show-scene-${currentScene}`); //currentScene을 바뀔때만 설정한것
        }

        if(yOffset < prevScrollHeight){
            enterNewScene = true;
            if(currentScene === 0) return; //브라우저 바운스 효과로 인해 마이너스가 되는 것을 방지 (주로 모바일에서)
            currentScene--;
            document.body.setAttribute('id', `show-scene-${currentScene}`); //currentScene을 바뀔대만 설정한것
        }

        if(enterNewScene) return;

        playAnimation();
    }
 
    window.addEventListener('resize', setLayout);
    window.addEventListener('scroll', () => {
        yOffset = window.pageYOffset;
        scrollLoop();
    });
    //window.addEventListener('DOMContentLoad', setLayout); //DOMContent와 load의 차이는 load는 웹페이지의 이미지, 리소스들이 싹다 로딩이 되고 나서 실행
    window.addEventListener('load', setLayout);                    //DOMC는 html객체들, DOM구조만 로드가 끝나면 바로 실행한다 이미지 같은것들이 로딩 안되더라도..
    window.addEventListener('resize', setLayout);

})();

html코드

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AirMug Pro</title>
    <link rel="stylesheet" href="css/default.css">
    <link rel="stylesheet" href="css/main.css">
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;900&display=swap" rel="stylesheet">
</head>
<body>  
    <div class="container">
        <nav class="global-nav">
            <div class="global-nav-links">
                <a  class="global-nav-item" href="#">About</a>
                <a class="global-nav-item" href="#">Ideas</a>
                <a class="global-nav-item" href="#">Shop</a>
                <a class="global-nav-item" href="#">Contact</a>
            </div>
        </nav>
        <nav class="local-nav">
            <div class="local-nav-links">
                <a href="#" class="product-name">AirMug Pro</a>
                <a href="#" class="">개요</a>
                <a href="#" class="">제품사양</a>
                <a href="#" class="">구입하기</a>
            </div>
        </nav>
        <section class="scroll-section" id="scroll-section-0">
            <h1>AirMug Pro</h1>
            <div class="sticky-elem main-message a">
                <p>온전히 빠져들게 하는<br>최고급 세라믹</p>
            </div>
            <div class="sticky-elem main-message b">
                <p>주변 맛을 느끼게 해주는<br>주변 맛 허용 모드</p>
            </div>
            <div class="sticky-elem main-message c">
                <p>온종일 편안한<br>맞춤형 손잡이</p>
            </div>
            <div class="sticky-elem main-message d">
                <p>새롭게 입가를<br>찾아온 매혹</p>
            </div>
        </section>
        <section class="scroll-section" id="scroll-section-1">
            <p class="description">
                <strong>보통 스트롤 영역</strong>
                Lorem ipsum dolor sit amet consectetur adipisicing elit. Officia, commodi! Perspiciatis ex sapiente nihil architecto, sequi non impedit odit eos at magni dolorum aliquid quis nemo autem illo provident ipsa, corrupti recusandae asperiores aspernatur dignissimos! Aut iste neque iusto quaerat architecto quam ullam suscipit culpa, accusantium nesciunt porro adipisci cum magnam corrupti quia. Commodi iste ipsum, molestiae animi velit soluta error exercitationem id natus, architecto alias dolorem porro quia repellendus! Voluptatibus animi enim natus, id laboriosam nihil ducimus odio vel officiis commodi facere exercitationem aut laudantium quos repellendus molestiae doloremque modi ullam beatae quae ratione architecto iure maxime quam? Obcaecati impedit laborum nam optio sit, temporibus harum, expedita nihil incidunt officia tempora, iusto odio reprehenderit? Asperiores, ipsum ullam voluptatibus, totam veritatis odit, tempora molestiae impedit animi error hic molestias? Totam corrupti fuga rem minima cumque accusamus dolore modi explicabo corporis eos dicta exercitationem enim eius eum dolores laboriosam fugit nobis, aspernatur id ipsum maiores labore architecto consequuntur consectetur! Quaerat nisi officiis autem quod in ut officia. Error doloremque consequuntur ullam ipsa doloribus beatae deleniti culpa, cupiditate, perspiciatis ea odio voluptates repudiandae numquam facilis quae, dolorem consequatur provident impedit accusamus nesciunt placeat magnam! Sapiente quod aperiam eaque tempore omnis autem tenetur eum dicta totam quibusdam aliquam, vero, unde doloribus sint exercitationem dolore dolorum illo! Deleniti, recusandae voluptatibus? Ducimus ex atque doloribus soluta? Rem porro velit praesentium nesciunt, alias veniam amet perspiciatis natus veritatis, nisi vel culpa repudiandae impedit! Animi molestiae ipsam natus! Tempora consequatur quod aut impedit, dolores odio asperiores in! Quidem quisquam necessitatibus illo dolorem nihil reiciendis eum repudiandae ratione facere. Enim nemo tenetur rem, eos, veniam, incidunt beatae sapiente inventore necessitatibus illum aliquid aliquam provident ipsam! Magni id inventore debitis at odio dicta architecto quos dolorem ea dolores, nulla numquam ipsam voluptates delectus similique obcaecati ducimus. Expedita, impedit? Dolorum corporis in officiis deserunt ipsa dolores, distinctio similique nihil placeat illo ut reiciendis, ea libero, earum nesciunt omnis ratione id? Nostrum optio fugit voluptate reprehenderit eos voluptatum quaerat, molestias itaque accusantium enim dolores distinctio odit vel esse cum eaque voluptas vitae asperiores provident doloremque ipsa fuga cumque! Commodi sint odio recusandae, temporibus accusantium quis magni accusamus porro ducimus nam numquam harum placeat eveniet? Voluptatum officia ad provident expedita, amet nam ratione praesentium possimus! Sint ipsum vitae ipsam hic, nisi earum minima dicta minus. Mollitia similique nulla exercitationem consequatur praesentium quibusdam maxime velit quo iusto, saepe deserunt distinctio voluptas itaque veniam asperiores voluptatem! Hic, ratione, laudantium, suscipit optio nemo laboriosam sint ullam impedit repudiandae cumque amet. Repellendus ut sint ipsa! Exercitationem, dolorum libero pariatur repudiandae iusto eum minima tenetur dolore placeat nulla! Ut deleniti veritatis voluptate, quia quidem incidunt numquam nulla, tenetur at optio nostrum temporibus beatae eveniet, facilis animi maxime rem doloremque expedita minus porro architecto modi sapiente quisquam. Ipsa illo unde numquam tempore ipsum at delectus! Quam alias beatae tempora atque, ratione incidunt accusamus placeat saepe voluptatem? Blanditiis, obcaecati, exercitationem odit consequuntur rerum facilis debitis quasi hic modi doloremque id, dolore doloribus illo atque mollitia. Tenetur nostrum harum ab?
            </p>
        </section>
        <section class="scroll-section" id="scroll-section-2">
            <div class="sticky-elem main-message a">
                <p>
                    <small>편안한 촉감</small>
                    입과 하나 되다
                </p>
            </div>
            <div class="sticky-elem desc-message b">
                <p>편안한 목넘김을 완성하는 디테일한 여러 구성 요소들, 우리는 이들 하나하나 새롭게 살피고 재구성하는 과정을 거쳐 새로운 수준의 머그, AirMug Pro를 만들었습니다. 입에 뭔가 댔다는 감각은 어느새 사라지고 오롯이 당신과 음료만 남게되죠.</p>
            </div>
            <div class="pin"></div>
            <div class="sticky-elem desc-message c">
                <p>
                    디자인 앤 퀄리티 오브 스웨덴,<br>메이드 인 차이나
                </p>
            </div>
            <div class="pin"></div>
        </section>
        <section class="scroll-section" id="scroll-section-3">
            <p class="mid-message">
                <strong>Retina 머그</strong><br>
                아이디어를 광활하게 펼칠<br>
                아름답고 부드러운 음료 공간
            </p>
            <p class="canvas-caption">
                Lorem ipsum dolor sit amet consectetur adipisicing elit. Inventore sit expedita at quibusdam ducimus illo nostrum atque porro totam veniam, omnis quaerat alias? Facilis quod obcaecati harum deserunt eos quas, repudiandae et odit nisi assumenda officiis beatae necessitatibus odio, voluptatem at blanditiis veniam ducimus eum aliquid tempore? Commodi dolores ut eaque illum vel dolor ducimus dolore distinctio repudiandae quisquam expedita libero nisi tempore consequuntur minima, sed saepe. Totam mollitia eaque corporis obcaecati dicta, modi nihil impedit soluta aperiam consequatur, necessitatibus voluptate ad! Sequi, assumenda molestiae exercitationem at ut fugit sapiente saepe cupiditate perferendis nulla nisi nesciunt ullam accusamus mollitia numquam doloremque? Praesentium assumenda architecto quae cupiditate recusandae, velit, ex nemo debitis sapiente neque aliquam delectus? Quam expedita nesciunt perferendis, vero officia ratione velit? Necessitatibus animi nobis quos fuga natus dolorum voluptatum, aliquid nemo quaerat perferendis explicabo repudiandae, reprehenderit ex delectus adipisci perspiciatis? Eum animi praesentium tempora exercitationem blanditiis, officia vitae voluptate sed. Fugit, maiores cumque! Nam reiciendis impedit corporis eaque, numquam ipsa dignissimos ex fugit. Ullam, reprehenderit incidunt praesentium voluptas qui tenetur exercitationem, officiis reiciendis consequuntur impedit explicabo aspernatur inventore! Quis exercitationem vel et voluptatum, sapiente dolore debitis doloribus, quas blanditiis neque vitae laboriosam deleniti! Dolore impedit facere repellat praesentium?
            </p>
        </section>
        <footer class="footer">
            2021, Made by ZZANGZZONG
        </footer>
    </div>
</body>

<script src="js/main.js"></script>
</html>

답변 2

·

답변을 작성해보세요.

0

never6500님의 프로필

never6500

질문자

2021.03.01

답변 너무 감사드립니다 강사님...

하지만 너무 창피하네요...ㅠㅠㅠㅠㅠ 더 열심히 해보겠습니당 

0

html 구조를 아래처럼,
.desc-message.b와 .desc-message.c에 .pin 이 포함되도록 바꾸어주세요~

<div class="sticky-elem desc-message b">
<p>편안한 목넘김을 완성하는 디테일한 여러 구성 요소들, 우리는 이들 하나하나 새롭게 살피고 재구성하는 과정을 거쳐 새로운 수준의 머그, AirMug Pro를 만들었습니다. 입에 뭔가 댔다는 감각은 어느새 사라지고 오롯이 당신과 음료만 남게되죠.</p>
<div class="pin"></div>
</div>