애플 웹사이트 인터랙션 클론!
애플 웹사이트 인터랙션 클론!
수강정보
(28개의 수강평)
585명의 수강생
3개월 할부시
월 25,666원77,000원
지식공유자 : 1분코딩
56회 수업 · 총 10시간 51분 수업
기간 : 평생 무제한 시청
수료증 : 발급 강의
수강 난이도 : 중급이상
Y K 프로필

css 관련 Y K 1일 전
안녕하세요. 좋은 강의 잘 보고 있습니다.  많이 배우고 있어요 ! :) 그런데 .desc-message가 제대로 적용되려면 sticky-elem 뒤로 순서를 바꾸거나 클래스 추가 수정이 필요할 것 같습니다. 감사합니다.   

1
Koo Paul 프로필

pin이 들어있는 DIV가 이상하게 작동해서 질문드립니다. Koo Paul 6일 전
https://zealous-tesla-834d28.netlify.app/ 세시간 정도 들여다봤는데... 제가 어디가 잘못된 건지 잘 못찾겠어서 답답한 마음에 질문을 드립니다. pin이 들어간 영역에서 스크롤을 내리면 desc의 height가 같이 줄어들었다가 늘어나야하는데 PIN만 줄어들었다가 늘어나네요 ㅜㅜ 그리고 스크롤을 내렸을 때도 움직임이 부자연스러워서... 어디를 손봐야할지 모르겠네요. 

2
pos7271 프로필

푸터 문제 pos7271 7일 전
7:45까지 똑같이 따라했는데 footer인 주황색 박스가 상하좌우로 공간이 생겨요. 선생님껀 푸터 양옆이 꽉차있는데 제껀 왜이러죠??

2
HyunJu Park 프로필

innerWidth에 대해 / 캔버스 드로우 애니메이션 1 강의부분 HyunJu Park 9일 전
늘 강의 감사히 듣고있습니다. 캔버스 드로우 애니메이션1 에 21:33 부분에서 innerWidth가 스크롤바를 포함한다고 하신부분이 검색해보니 innerWidth는 스크롤바를 포함하지않는크기라고 나오더라구요 https://webclub.tistory.com/105 제가 잘못알고있는걸까요?; 중복질문이면 죄송합니다.

2
조장환 프로필

playanimation 관련 질문드립니다. 조장환 9일 전
안녕하세요. 강사님 강의 잘 듣고 있습니다. 현재 쇼핑몰을 제작 중인데 에어팟 프로 상세페이지를 보고 잘 만들었다는 생각을 많이 했습니다. 그러다 강사님 강의를 알게 되었고 이렇게 배울 수 있는 기회를 주셔서 감사합니다. 강의는 전체적으로 한번 듣고 다시 한 강의씩 들으면서 페이지 제작 중에 있는데 문제가 있어 이렇게 질문을 남깁니다. 일단 저는 아임웹을 이용해서 홈페이지를 만드는 중이고 '현재 활성씬  반영하기'까지 완료하고 콘솔창을 통해 제대로 작동하는 것까지 확인했습니다. 헌데 playanimation 함수를 통해 case들을 작성하고 잘 작동하는지 console.log콘솔창에 띄어보려고 해도 아무런 반응이 없습니다. 빨간색으로 어떤 에러도 표시가 안되어서 일단 무시하고 다음 내용들을 작성하여도 콘솔창에 모두 반응이 없습니다. console.log('0' play); console.log('1' play);console.log('2' play);console.log('3' play); console.log(messageA_opacity_0, messageA_opacity_1); 아임웹 특성상 코드위젯창을 통해 html, css, 자바 파일들을 한꺼번에 작성하고 있고, 또한 내비쪽은 아임웹쪽에서 이미 자체적으로 구성이 되어 있기 때문에 내비들은 제외하고 작성하고 있는데 이 과정에서 혹시 문제가 발생했을 수도 있는지 아니면 어떤 문제때문인지 알려주시면 감사하겠습니다! function playAnimation() { switch (currentScene) { case 0: console.log('0 play'); break; case 1: console.log('1 play'); break; case 2: console.log('2 play'); break; case 3: console.log('3 play'); break; } }

6
임수진 프로필

비디오 촬영 관련 질문 임수진 10일 전
안녕하세요. 강의를 너무 재미있게 듣고 있습니다. 강의의 질이 너무 좋아서 평소에 HTML이나 CSS에 대한 공부 부담도 있었는데 그런 부분까지 커버하는 강의 같네요 ㅠㅠ 너무 좋아요. 제 질문은 비디오 촬영에 관련한 것입니다. 최근에 혼자 동영상까지 만들어보고 싶어 여러가지 알아보던 와중에 어떻게 촬영하셨는지 궁금해서 질문드립니다. 저는 미니스튜디오로 배경을 커버할까 생각중인데, 강사님은 어떻게 뒷배경을 깔끔하게 날리셨는지, 스튜디오 같은 곳을 활용하였는지 궁금합니다. 감사합니다!

3
과연 프로필

흰색박스... 도와주세용 과연 11일 전
얼마전에 질문드렸는데 해결이 안돼서 다시 질문합니다..! 좌우 흰색박스가 이동이 잘 안됩니다.. 일분이 배경 회색배경이 살짝 넓어지긴하는데 조금밖에 안됩니다...ㅠㅠ 소스비교해봐도 잘 모르겠는데 ... scrollRatio가 끝까지 가면 0.82..? 까지밖에 안가는거같아요... 깃헙 다시올립니다! https://github.com/jellybrown/apple_clone/blob/master/js/main.js

4
과연 프로필

동작(?)을 안해요..! 과연 16일 전
https://github.com/jellybrown/apple_clone/blob/master/js/main.js 제가 빼먹은게 있나요...? 어려워서 돌려봤는데 어디서 뭘빼먹었는지 모르겠어요 ㅠ.ㅠ

1
박용진 프로필

블로킹 박용진 17일 전
이미지 경로의 문제가 있는건가요???

8
릴렉스 프로필

4개의 씬을 5개로 늘려서 응용하려는 데 작동이 잘 안되네요 ㅠㅠ 릴렉스 19일 전
@charset 'utf-8'; html { font-family: 'Noto Sans KR', sans-serif; font-size: 14px; } body { overflow-x: hidden; color: rgb(29, 29, 31); letter-spacing: -0.05em; background: white; } p { line-height: 1.6; } a { color: rgb(29, 29, 31); text-decoration: none; } body.before-load { overflow: hidden; } .container { /* iPhone 가로 스크롤 방지 */ overflow-x: hidden; } .global-nav { position: absolute; top: 0; left: 0; z-index: 10; width: 100%; height: 44px; padding: 0 1rem; } .local-nav { position: absolute; top: 45px; left: 0; z-index: 11; width: 100%; height: 52px; padding: 0 1rem; border-bottom: 1px solid #ddd; } .local-nav-sticky .local-nav { position: fixed; top: 0; background: rgba(255, 255, 255, 0.1); /* for iPhone */ -webkit-backdrop-filter: saturate(180%) blur(15px); -moz-backdrop-filter: saturate(180%) blur(15px); -o-backdrop-filter: saturate(180%) blur(15px); backdrop-filter: saturate(180%) blur(15px); } .global-nav-links, .local-nav-links { display: flex; align-items: center; max-width: 1000px; height: 100%; margin: 0 auto; } .global-nav-links { justify-content: space-between; } .local-nav-links .product-name { margin-right: auto; font-size: 1.4rem; font-weight: bold; } .local-nav-links a { font-size: 0.8rem; } .local-nav-links a:not(.product-name) { margin-left: 2em; } .scroll-section { position: relative; padding-top: 50vh; border: 3px red solid; } #scroll-section-0 h1 { position: relative; top: -10vh; z-index: 5; font-size: 4rem; text-align: center; } #scroll-section-1 h1 { position: relative; top: -10vh; z-index: 5; font-size: 4rem; text-align: center; } .main-message { display: flex; align-items: center; justify-content: center; top: 35vh; margin: 5px 0; height: 3em; font-size: 2.5rem; opacity: 0; } .main-message p { font-weight: bold; text-align: center; line-height: 1.2; } .main-message small { display: block; margin-bottom: 0.5em; font-size: 1.2rem; } #scroll-section-3 .main-message { font-size: 3.5rem; } .description { max-width: 1000px; margin: 0 auto; padding: 0 1rem; font-size: 1.2rem; color: #888; } .description strong { float: left; margin-right: 0.2em; font-size: 3rem; color: rgb(29, 29, 31); } .desc-message { width: 50%; font-weight: bold; opacity: 0; } .pin { width: 1px; height: 100px; background: rgb(29, 29, 31); } #scroll-section-3 .b { top: 10%; left: 40%; } #scroll-section-3 .c { top: 15%; left: 45%; } .mid-message { max-width: 1000px; margin: 0 auto; padding: 0 1rem; font-size: 2rem; color: #888; } .mid-message strong { color: rgb(29, 29, 31); } .canvas-caption { max-width: 1000px; margin: -24rem auto 0; padding: 0 1rem; font-size: 1.2rem; color: #888; } .footer { display: flex; align-items: center; justify-content: center; height: 7rem; color: white; background: darkorange; } .sticky-elem { display: none; position: fixed; left: 0; width: 100%; } #show-scene-0 #scroll-section-0 .sticky-elem, #show-scene-1 #scroll-section-1 .sticky-elem, #show-scene-2 #scroll-section-2 .sticky-elem, #show-scene-3 #scroll-section-3 .sticky-elem, #show-scene-4 #scroll-section-4 .sticky-elem { { display: block; will-change: transform, opacity; } .sticky-elem-canvas { top: 0; height: 100%; } .sticky-elem-canvas canvas { position: absolute; top: 50%; left: 50%; } #scroll-section-3 { display: flex; flex-direction: column; align-items: center; } .image-blend-canvas.sticky { position: fixed; top: 0; } .loading { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100; background: white; opacity: 0; transition: 0.5s; } .before-load .container { display: none; } .before-load .loading { opacity: 1; } @keyframes loading-spin { 100% { transform: rotate(360deg); } } @keyframes loading-circle-ani { 0% { stroke-dashoffset: 157; } 75% { stroke-dashoffset: -147; } 100% { stroke-dashoffset: -157; } } .loading-circle { width: 54px; height: 54px; animation: loading-spin 3s infinite; } .loading-circle circle { stroke: black; stroke-width: 4; /* getTotalLength()로 stroke의 길이를 얻어올 수 있음 */ stroke-dasharray: 157; stroke-dashoffset: 0; fill: transparent; animation: loading-circle-ani 1s infinite; /* transition: 1s; */ } /* .loading-circle:hover circle { stroke-dashoffset: -157; } */ @media (min-width: 1024px) { #scroll-section-0 h1 { font-size: 9vw; } #scroll-section-1 h1 { font-size: 9vw; } .main-message { font-size: 4vw; } .description { padding: 0; font-size: 2rem; } .description strong { font-size: 6rem; } #scroll-section-3 .main-message { font-size: 6vw; } .main-message small { font-size: 1.5vw; } .desc-message { width: 20%; } #scroll-section-3 .b { top: 20%; left: 53%; } #scroll-section-3 .c { left: 55%; } .mid-message { width: 1000px; padding: 0; font-size: 4vw; } .canvas-caption { margin-top: -8rem; padding: 0; font-size: 2rem; } } ​ <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>AirMug Pro</title> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;900&display=swap" rel="stylesheet"> <link rel="stylesheet" href="css/default.css"> <link rel="stylesheet" href="css/main.css"> </head> <body class="before-load"> <div class="loading"> <svg class="loading-circle"> <circle cx="50%" cy="50%" r="25"></circle> </svg> </div> <div class="container"> <nav class="global-nav"> <div class="global-nav-links"> <a href="#" class="global-nav-item">Rooms</a> <a href="#" class="global-nav-item">Ideas</a> <a href="#" class="global-nav-item">Stores</a> <a href="#" class="global-nav-item">Contact</a> </div> </nav> <nav class="local-nav"> <div class="local-nav-links"> <a href="#" class="product-name">AirMug Pro</a> <a href="#">개요</a> <a href="#">제품사양</a> <a href="#">구입하기</a> </div> </nav> <section class="scroll-section" id="scroll-section-0"> <h1>영주마실 사과즙 30포</h1> <img src="./images/blend-image-1.jpg" alt=""> </section> <section class="scroll-section" id="scroll-section-1"> <!--<h1>영주마실 사과즙 30포</h1>--> <div class="sticky-elem sticky-elem-canvas"> <canvas id="video-canvas-0" width="1920" height="1080"></canvas> </div> <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-2"> <p class="description"> <strong>보통 스크롤 영역</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Beatae est ipsa minima, eligendi error cum vel dolorum pariatur officia facilis ipsam voluptatibus ad quasi porro quod quisquam quidem tempora accusantium accusamus, quaerat aliquam velit exercitationem incidunt? Id vitae quisquam saepe quasi accusantium tempore enim! Aperiam dolorum a vero repellat dolor, inventore ab odit totam molestias expedita? Enim quia dolor maiores veniam ea! Quam illo, est incidunt ipsa reiciendis modi quisquam reprehenderit fuga velit dolorem odit sequi autem blanditiis, ullam commodi quibusdam. Accusamus repellat aperiam quis neque laudantium, dignissimos hic nisi magnam praesentium enim beatae sint architecto cum numquam inventore rerum animi sed nostrum quae delectus, voluptas molestiae placeat aliquid! Vel quaerat error officiis magnam dolorum iste aspernatur at est! Quo, consequuntur? Reiciendis, dolor. Quo at cupiditate in iure obcaecati voluptatum vel ea! Ab vel harum facere hic fuga ducimus sapiente dolore dolorem, nobis sint perferendis cumque esse! Omnis fugiat sint error laborum eveniet labore nam ducimus quisquam in repudiandae impedit excepturi dignissimos tenetur libero placeat rerum maxime tempore, aut nihil. Qui, quam? Voluptate fuga possimus itaque quas nesciunt iste, facilis mollitia illo qui placeat temporibus inventore obcaecati. Recusandae, sequi dignissimos in natus eum maiores dolorem, deleniti nobis accusantium, aspernatur beatae. </p> </section> <section class="scroll-section" id="scroll-section-3"> <div class="sticky-elem sticky-elem-canvas"> <canvas id="video-canvas-1" width="1920" height="1080"></canvas> </div> <div class="sticky-elem main-message a"> <p> <small>편안한 촉감</small> 입과 하나 되다 </p> </div> <div class="sticky-elem desc-message b"> <p> 편안한 목넘김을 완성하는 디테일한 여러 구성 요소들, 우리는 이를 하나하나 새롭게 살피고 재구성하는 과정을 거쳐 새로운 수준의 머그, AirMug Pro를 만들었습니다. 입에 뭔가 댔다는 감각은 어느새 사라지고 오롯이 당신과 음료만 남게 되죠. </p> <div class="pin"></div> </div> <div class="sticky-elem desc-message c"> <p> 디자인 앤 퀄리티 오브 스웨덴,<br>메이드 인 차이나 </p> <div class="pin"></div> </div> </section> <section class="scroll-section" id="scroll-section-4"> <p class="mid-message"> <strong>Retina 머그</strong><br> 아이디어를 광활하게 펼칠<br> 아름답고 부드러운 음료 공간. </p> <canvas class="image-blend-canvas" width="1920" height="1080"></canvas> <p class="canvas-caption"> Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet at fuga quae perspiciatis veniam impedit et, ratione est optio porro. Incidunt aperiam nemo voluptas odit quisquam harum in mollitia. Incidunt minima iusto in corporis, dolores velit. Autem, sit dolorum inventore a rerum distinctio vero illo magni possimus temporibus dolores neque adipisci, repudiandae repellat. Ducimus accusamus similique quas earum laborum. Autem tempora repellendus asperiores illum ex! Velit ea corporis odit? Ea, incidunt delectus. Sapiente rerum neque error deleniti quis, et, quibusdam, est autem voluptate rem voluptas. Ratione soluta similique harum nihil vel. Quas inventore perferendis iusto explicabo animi eos ratione obcaecati. </p> </section> <footer class="footer"> 2020, 1분코딩 </footer> </div> <script src="js/main.js"></script> </body> </html> ​ (() => { let yOffset = 0; // window.pageYOffset 대신 쓸 변수 let prevScrollHeight = 0; // 현재 스크롤 위치(yOffset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이값의 합 let currentScene = 0; // 현재 활성화된(눈 앞에 보고있는) 씬(scroll-section) let enterNewScene = false; // 새로운 scene이 시작된 순간 true let acc = 0.2; let delayedYOffset = 0; let rafId; let rafState; const sceneInfo = [ { // 0 type: 'normal', //heightNum: 5, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objs:{ container:document.querySelector('#scroll-section-0') } }, { // 1 type: 'sticky', heightNum: 5, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-1'), messageA: document.querySelector('#scroll-section-1 .main-message.a'), messageB: document.querySelector('#scroll-section-1 .main-message.b'), messageC: document.querySelector('#scroll-section-1 .main-message.c'), messageD: document.querySelector('#scroll-section-1 .main-message.d'), canvas: document.querySelector('#video-canvas-0'), context: document.querySelector('#video-canvas-0').getContext('2d'), videoImages: [] }, values: { videoImageCount: 300, imageSequence: [0, 299], canvas_opacity: [1, 0, { start: 0.9, end: 1 }], messageA_opacity_in: [0, 1, { start: 0.1, end: 0.2 }], 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 }] } }, { // 2 type: 'normal', // heightNum: 5, // type normal에서는 필요 없음 scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-2') } }, { // 3 type: 'sticky', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-3'), messageA: document.querySelector('#scroll-section-3 .a'), messageB: document.querySelector('#scroll-section-3 .b'), messageC: document.querySelector('#scroll-section-3 .c'), pinB: document.querySelector('#scroll-section-3 .b .pin'), pinC: document.querySelector('#scroll-section-3 .c .pin'), canvas: document.querySelector('#video-canvas-1'), context: document.querySelector('#video-canvas-1').getContext('2d'), videoImages: [] }, values: { videoImageCount: 960, imageSequence: [0, 959], canvas_opacity_in: [0, 1, { start: 0, end: 0.1 }], canvas_opacity_out: [1, 0, { start: 0.95, end: 1 }], 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 }] } }, { // 4 type: 'sticky', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-4'), canvasCaption: document.querySelector('.canvas-caption'), canvas: document.querySelector('.image-blend-canvas'), context: document.querySelector('.image-blend-canvas').getContext('2d'), imagesPath: [ './images/blend-image-1.jpg', './images/blend-image-2.jpg' ], images: [] }, values: { rect1X: [ 0, 0, { start: 0, end: 0 } ], rect2X: [ 0, 0, { start: 0, end: 0 } ], blendHeight: [ 0, 0, { start: 0, end: 0 } ], canvas_scale: [ 0, 0, { start: 0, end: 0 } ], canvasCaption_opacity: [ 0, 1, { start: 0, end: 0 } ], canvasCaption_translateY: [ 20, 0, { start: 0, end: 0 } ], rectStartY: 0 } } ]; function setCanvasImages() { let imgElem; for (let i = 0; i < sceneInfo[1].values.videoImageCount; i++) { imgElem = new Image(); imgElem.src = `./video/001/IMG_${6726 + i}.JPG`; sceneInfo[1].objs.videoImages.push(imgElem); } let imgElem2; for (let i = 0; i < sceneInfo[3].values.videoImageCount; i++) { imgElem2 = new Image(); imgElem2.src = `./video/002/IMG_${7027 + i}.JPG`; sceneInfo[3].objs.videoImages.push(imgElem2); } let imgElem3; for (let i = 0; i < sceneInfo[4].objs.imagesPath.length; i++) { imgElem3 = new Image(); imgElem3.src = sceneInfo[4].objs.imagesPath[i]; sceneInfo[4].objs.images.push(imgElem3); } } function checkMenu() { if (yOffset > 44) { document.body.classList.add('local-nav-sticky'); } else { document.body.classList.remove('local-nav-sticky'); } } function setLayout() { // 각 스크롤 섹션의 높이 세팅 for (let i = 0; i < sceneInfo.length; i++) { if (sceneInfo[i].type === 'sticky') { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; } else if (sceneInfo[i].type === 'normal') { sceneInfo[i].scrollHeight = sceneInfo[i].objs.container.offsetHeight; } sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; } yOffset = window.pageYOffset; let totalScrollHeight = 0; for (let i = 0; i < sceneInfo.length; i++) { totalScrollHeight += sceneInfo[i].scrollHeight; if (totalScrollHeight >= yOffset) { currentScene = i; break; } } document.body.setAttribute('id', `show-scene-${currentScene}`); const heightRatio = window.innerHeight / 1080; sceneInfo[1].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; sceneInfo[3].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; } function calcValues(values, currentYOffset) { let rv; // 현재 씬(스크롤섹션)에서 스크롤된 범위를 비율로 구하기 const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; if (values.length === 4) { // 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]; } //console.log(scrollHeight); return rv; } //console.log(calcValues); 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; switch (currentScene) { case 1: console.log(objs.messageA.style.opacity); console.log(objs.messageB.style.opacity); console.log(objs.messageC.style.opacity); console.log(objs.messageD.style.opacity); console.log(currentYOffset); console.log(scrollHeight); // let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); // objs.context.drawImage(objs.videoImages[sequence], 0, 0); objs.canvas.style.opacity = calcValues(values.canvas_opacity, currentYOffset); 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 3: // console.log('2 play'); // let sequence2 = Math.round(calcValues(values.imageSequence, currentYOffset)); // objs.context.drawImage(objs.videoImages[sequence2], 0, 0); if (scrollRatio <= 0.5) { // in objs.canvas.style.opacity = calcValues(values.canvas_opacity_in, currentYOffset); } else { // out objs.canvas.style.opacity = calcValues(values.canvas_opacity_out, currentYOffset); } 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)})`; } // currentScene 3에서 쓰는 캔버스를 미리 그려주기 시작 if (scrollRatio > 0.9) { const objs = sceneInfo[4].objs; const values = sceneInfo[4].values; const widthRatio = window.innerWidth / objs.canvas.width; const heightRatio = window.innerHeight / objs.canvas.height; let canvasScaleRatio; if (widthRatio <= heightRatio) { // 캔버스보다 브라우저 창이 홀쭉한 경우 canvasScaleRatio = heightRatio; } else { // 캔버스보다 브라우저 창이 납작한 경우 canvasScaleRatio = widthRatio; } objs.canvas.style.transform = `scale(${canvasScaleRatio})`; objs.context.fillStyle = 'white'; objs.context.drawImage(objs.images[0], 0, 0); // 캔버스 사이즈에 맞춰 가정한 innerWidth와 innerHeight const recalculatedInnerWidth = document.body.offsetWidth / canvasScaleRatio; const recalculatedInnerHeight = window.innerHeight / canvasScaleRatio; const whiteRectWidth = recalculatedInnerWidth * 0.15; values.rect1X[0] = (objs.canvas.width - recalculatedInnerWidth) / 2; values.rect1X[1] = values.rect1X[0] - whiteRectWidth; values.rect2X[0] = values.rect1X[0] + recalculatedInnerWidth - whiteRectWidth; values.rect2X[1] = values.rect2X[0] + whiteRectWidth; // 좌우 흰색 박스 그리기 objs.context.fillRect( parseInt(values.rect1X[0]), 0, parseInt(whiteRectWidth), objs.canvas.height ); objs.context.fillRect( parseInt(values.rect2X[0]), 0, parseInt(whiteRectWidth), objs.canvas.height ); } break; case 4: // console.log('3 play'); let step = 0; // 가로/세로 모두 꽉 차게 하기 위해 여기서 세팅(계산 필요) const widthRatio = window.innerWidth / objs.canvas.width; const heightRatio = window.innerHeight / objs.canvas.height; let canvasScaleRatio; if (widthRatio <= heightRatio) { // 캔버스보다 브라우저 창이 홀쭉한 경우 canvasScaleRatio = heightRatio; } else { // 캔버스보다 브라우저 창이 납작한 경우 canvasScaleRatio = widthRatio; } objs.canvas.style.transform = `scale(${canvasScaleRatio})`; objs.context.fillStyle = 'white'; objs.context.drawImage(objs.images[0], 0, 0); // 캔버스 사이즈에 맞춰 가정한 innerWidth와 innerHeight const recalculatedInnerWidth = document.body.offsetWidth / canvasScaleRatio; const recalculatedInnerHeight = window.innerHeight / canvasScaleRatio; if (!values.rectStartY) { // values.rectStartY = objs.canvas.getBoundingClientRect().top; values.rectStartY = objs.canvas.offsetTop + (objs.canvas.height - objs.canvas.height * canvasScaleRatio) / 2; values.rect1X[2].start = (window.innerHeight / 2) / scrollHeight; values.rect2X[2].start = (window.innerHeight / 2) / scrollHeight; values.rect1X[2].end = values.rectStartY / scrollHeight; values.rect2X[2].end = values.rectStartY / scrollHeight; } const whiteRectWidth = recalculatedInnerWidth * 0.15; values.rect1X[0] = (objs.canvas.width - recalculatedInnerWidth) / 2; values.rect1X[1] = values.rect1X[0] - whiteRectWidth; values.rect2X[0] = values.rect1X[0] + recalculatedInnerWidth - whiteRectWidth; values.rect2X[1] = values.rect2X[0] + whiteRectWidth; // 좌우 흰색 박스 그리기 objs.context.fillRect( parseInt(calcValues(values.rect1X, currentYOffset)), 0, parseInt(whiteRectWidth), objs.canvas.height ); objs.context.fillRect( parseInt(calcValues(values.rect2X, currentYOffset)), 0, parseInt(whiteRectWidth), objs.canvas.height ); if (scrollRatio < values.rect1X[2].end) { step = 1; // console.log('캔버스 닿기 전'); objs.canvas.classList.remove('sticky'); } else { step = 2; // console.log('캔버스 닿은 후'); // 이미지 블렌드 // values.blendHeight: [ 0, 0, { start: 0, end: 0 } ] values.blendHeight[0] = 0; values.blendHeight[1] = objs.canvas.height; values.blendHeight[2].start = values.rect1X[2].end; values.blendHeight[2].end = values.blendHeight[2].start + 0.2; const blendHeight = calcValues(values.blendHeight, currentYOffset); objs.context.drawImage(objs.images[1], 0, objs.canvas.height - blendHeight, objs.canvas.width, blendHeight, 0, objs.canvas.height - blendHeight, objs.canvas.width, blendHeight ); objs.canvas.classList.add('sticky'); objs.canvas.style.top = `${-(objs.canvas.height - objs.canvas.height * canvasScaleRatio) / 2}px`; if (scrollRatio > values.blendHeight[2].end) { values.canvas_scale[0] = canvasScaleRatio; values.canvas_scale[1] = document.body.offsetWidth / (1.5 * objs.canvas.width); values.canvas_scale[2].start = values.blendHeight[2].end; values.canvas_scale[2].end = values.canvas_scale[2].start + 0.2; objs.canvas.style.transform = `scale(${calcValues(values.canvas_scale, currentYOffset)})`; objs.canvas.style.marginTop = 0; } if (scrollRatio > values.canvas_scale[2].end && values.canvas_scale[2].end > 0) { objs.canvas.classList.remove('sticky'); objs.canvas.style.marginTop = `${scrollHeight * 0.4}px`; values.canvasCaption_opacity[2].start = values.canvas_scale[2].end; values.canvasCaption_opacity[2].end = values.canvasCaption_opacity[2].start + 0.1; values.canvasCaption_translateY[2].start = values.canvasCaption_opacity[2].start; values.canvasCaption_translateY[2].end = values.canvasCaption_opacity[2].end; objs.canvasCaption.style.opacity = calcValues(values.canvasCaption_opacity, currentYOffset); objs.canvasCaption.style.transform = `translate3d(0, ${calcValues(values.canvasCaption_translateY, currentYOffset)}%, 0)`; } } break; } } function scrollLoop() { enterNewScene = false; prevScrollHeight = 0; for (let i = 0; i < currentScene; i++) { prevScrollHeight += sceneInfo[i].scrollHeight; } if (delayedYOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { enterNewScene = true; currentScene++; document.body.setAttribute('id', `show-scene-${currentScene}`); } if (delayedYOffset < prevScrollHeight) { enterNewScene = true; if (currentScene === 0) return; // 브라우저 바운스 효과로 인해 마이너스가 되는 것을 방지(모바일) currentScene--; document.body.setAttribute('id', `show-scene-${currentScene}`); } if (enterNewScene) return; playAnimation(); } function loop() { delayedYOffset = delayedYOffset + (yOffset - delayedYOffset) * acc; if (!enterNewScene) { if (currentScene === 1 || currentScene === 3) { const currentYOffset = delayedYOffset - prevScrollHeight; const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); if (objs.videoImages[sequence]) { objs.context.drawImage(objs.videoImages[sequence], 0, 0); } } } // 추가 코드 // home이나 end를 이용해 페이지 끝으로 고속 이동하면 body id가 제대로 인식 안되는 경우를 해결 // home 키로 페이지 맨 위로 갈 경우: scrollLoop와 첫 scene의 기본 캔버스 그리기 수행 /* if (delayedYOffset < 1) { scrollLoop(); sceneInfo[0].objs.canvas.style.opacity = 1; sceneInfo[0].objs.context.drawImage(sceneInfo[0].objs.videoImages[0], 0, 0); }*/ // end 키로 페이지 맨 아래로 갈 경우: 마지막 섹션은 스크롤 계산으로 위치 및 크기를 결정해야할 요소들이 많아서 1픽셀을 움직여주는 것으로 해결 if ((document.body.offsetHeight - window.innerHeight) - delayedYOffset < 1) { let tempYOffset = yOffset; scrollTo(0, tempYOffset - 1); } rafId = requestAnimationFrame(loop); if (Math.abs(yOffset - delayedYOffset) < 1) { cancelAnimationFrame(rafId); rafState = false; } } window.addEventListener('load', () => { document.body.classList.remove('before-load'); setLayout(); //sceneInfo[0].objs.context.drawImage(sceneInfo[0].objs.videoImages[0], 0, 0); let tempYOffset = yOffset; let tempScrollCount = 0; if (tempYOffset > 0) { let siId = setInterval(() => { scrollTo(0, tempYOffset); tempYOffset += 5; if (tempScrollCount > 20) { clearInterval(siId); } tempScrollCount++; }, 20); } window.addEventListener('scroll', () => { console.log(`yOffset: ${yOffset}`); yOffset = window.pageYOffset; scrollLoop(); checkMenu(); if (!rafState) { rafId = requestAnimationFrame(loop); rafState = true; } }); window.addEventListener('resize', () => { if (window.innerWidth > 900) { setLayout(); sceneInfo[4].values.rectStartY = 0; } if (currentScene === 4) { // 추가 코드 // Scene 3의 요소들은 위치나 크기가 미리 정해지지 않고 // 현재 창 사이즈나 스크롤 위치에 따라 가변적으로 변하기 때문에 // 리사이즈에 일일이 대응시키기가 까다롭습니다. // Scene 3에 진입 시점에 요소들의 위치와 크기가 결정이 되는 특징을 이용해서 // 현재 Scene이 3일 경우에는 좀 위로 스크롤이 되도록 해서 // Scene 3의 시작 지점 이전으로 돌리는 식으로 요소들의 레이아웃이 깨지는 현상을 방지해 줍니다. // 시작 지점 이전으로 스크롤을 이동 시키는 동작은 // 바로 위 518 라인의 자동 스크롤 코드를 그대로 활용했습니다. let tempYOffset = yOffset; let tempScrollCount = 0; if (tempYOffset > 0) { let siId = setInterval(() => { scrollTo(0, tempYOffset); tempYOffset -= 50; if (tempScrollCount > 20) { clearInterval(siId); } tempScrollCount++; }, 20); } } }); console.log(sceneInfo.length); window.addEventListener('orientationchange', () => { setTimeout(setLayout, 500); }); document.querySelector('.loading').addEventListener('transitionend', (e) => { document.body.removeChild(e.currentTarget); }); }); setCanvasImages(); })();

1
Kim jae-eok 프로필

안녕하세요 '현재 활성시킬 씬 결정하기' 강좌편에서 문의가 있습니다. Kim jae-eok 19일 전
안녕하세요 다름이 아니라 해당 '현재 활성시킬 씬 결정하기' 에서 코드를 입력하고 있는데 원하는 위치 스크롤 보다 400픽셀 앞에서 currentScene 변수가 변경하고 있어서 질문 드립니다. 아무리 확인해도 문제가 되는 부분이 없는거 같은데 확인 부탁드립니다. index. html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>AirMug Pro</title> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;900&display=swap" rel="stylesheet" /> <link rel="stylesheet" href="css/main.css" /> </head> <body id=""> <div class="contaner"> <nav class="global-nav"> <div class="global-nav-links"> <a href="#" class="global-nav-item">Room</a> <a href="#" class="global-nav-item">Ideas</a> <a href="#" class="global-nav-item">Stores</a> <a href="#" class="global-nav-item">Contact</a> </div> </nav> <nav class="local-nav"> <div class="local-nav-links"> <a href="#" class="product-name">AirMug Pro</a> <a href="#">개요</a> <a href="#">제품사양</a> <a href="#">구입하기</a> </div> </nav> <section class="scroll-section" id="scroll-section-0"> <h1>AirMug Pro</h1> <div class="sticky-elem main-message"> <p>온전히 빠져들게 하는<br />최고급 세라믹</p> </div> <div class="sticky-elem main-message"> <p>주변 맛을 느끼게 해주는<br />주변 맛 허용 모드</p> </div> <div class="sticky-elem main-message"> <p>온중일 편안한<br />맞춤형 손잡이</p> </div> <div class="sticky-elem main-message"> <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. Ullam, soluta quia? Soluta repellat consequatur aliquam sit deleniti atque qui nemo, molestiae, veritatis repudiandae, earum tempora mollitia dolores? Ipsa a ea ab nostrum ullam beatae, natus, nulla recusandae praesentium corporis magni, atque impedit error aliquid consequatur. Culpa cupiditate velit molestias sapiente! Aspernatur molestiae facilis repudiandae, fugiat nulla, hic culpa esse impedit eos, quam sapiente? Laboriosam ut perferendis atque velit sint, non mollitia? Provident cum obcaecati ex illo asperiores accusantium harum voluptas doloremque inventore possimus eum tenetur tempora ipsam debitis natus nulla similique veritatis recusandae, voluptatum accusamus quia adipisci perspiciatis. Itaque architecto eius veritatis rem quae facere illo ipsum error, explicabo nesciunt eos quibusdam dolorem magni impedit, magnam amet voluptates fugit assumenda deserunt consequuntur numquam. Quasi hic nisi molestias enim fugiat vel eveniet modi quisquam suscipit? Recusandae voluptates expedita exercitationem excepturi velit repellat error similique porro deleniti architecto tenetur quaerat corporis culpa, libero, neque facilis ullam eius reiciendis itaque et fugiat eum! Magnam totam at commodi nesciunt, quae dolores, praesentium labore, maxime perspiciatis iure omnis sed assumenda laborum harum. Dolor, voluptatem id. Eos qui, quidem assumenda sit asperiores quia beatae consequuntur necessitatibus dignissimos architecto, aspernatur facere ut porro cum repellat. Tempora, minima? </p> </section> <section class="scroll-section" id="scroll-section-2"> <div class="sticky-elem main-message"> <p> <small> 편안한 촉감 </small> 입과 하나 되다 </p> </div> <div class="sticky-elem desc-message"> <p> 편안한 목넘김을 완성하는 디테일한 여러 구성 요소들, 우리는 이를 하나하나 새롭게 살피고 재구성하는 과정을 거쳐 새로운 수준의 머그, AirMug Pro를 만들었습니다. 입에 뭔가 댔다는 감각은 어느새 사라지고 오롯이 당신과 음료만 남게 되죠. </p> <div class="pin"></div> </div> <div class="sticky-elem desc-message"> <p>디자인 앤 퀄리티 오브 스웨덴,<br />메이드 인 차이나</p> <div class="pin"></div> </div> </section> <section class="scroll-section" id="scroll-section-3"> <p class="mid-message"> <strong>Retina 머그</strong><br /> 아이디어를 광활하게 펼칠<br /> 아름답고 부드러운 음료 공간. </p> <!-- <canvas class="image-blend-canvas" width="1920" height="1080"></canvas> --> <p class="canvas-caption"> Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet at fuga quae perspiciatis veniam impedit et, ratione est optio porro. Incidunt aperiam nemo voluptas odit quisquam harum in mollitia. Incidunt minima iusto in corporis, dolores velit. Autem, sit dolorum inventore a rerum distinctio vero illo magni possimus temporibus dolores neque adipisci, repudiandae repellat. Ducimus accusamus similique quas earum laborum. Autem tempora repellendus asperiores illum ex! Velit ea corporis odit? Ea, incidunt delectus. Sapiente rerum neque error deleniti quis, et, quibusdam, est autem voluptate rem voluptas. Ratione soluta similique harum nihil vel. Quas inventore perferendis iusto explicabo animi eos ratione obcaecati. </p> </section> <footer class="footer"> 2020, 1분코딩 </footer> <script src="js/main.js"></script> </div> </body> </html> main.js (() => { let yOffset = 0; // window.pageYOffset 대신 쓸 변수 let prevScrollHeight = 0; // 현재 스크롤 위치(yOffSet)보다 이전에 위치한 스킄롤 섹션들의 스크롤 높이값의 합 let currentScene = 0; // 현재 활성화된 (눈앞에 보고 있는) 씬(scroll-section) const sceneInfo = [ { // 0 type: "sticky", heightNum: 5, // 브라우저 높이의 5배로 scrollHeght 세팅 scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-0"), }, }, { // 1 type: "normal", heightNum: 5, // 브라우저 높이의 5배로 scrollHeght 세팅 scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-1"), }, }, { // 2 type: "sticky", heightNum: 5, // 브라우저 높이의 5배로 scrollHeght 세팅 scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-2"), }, }, { // 3 type: "sticky", heightNum: 5, // 브라우저 높이의 5배로 scrollHeght 세팅 scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-3"), }, }, ]; /** * 각 씬 마다의 섹션 높이값 지정 */ function setLayout() { // 각 스크롤 섹션의 높이 저장 for (let i = 0; i < sceneInfo.length; i++) { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; sceneInfo[ i ].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; } } /** * 현재 보고 있는 화면에서 스크롤 높이 구하기 */ function scrollLoop() { prevScrollHeight = 0; for (let i = 0; i < currentScene; i++) { prevScrollHeight = prevScrollHeight + sceneInfo[i].scrollHeight; } if (yOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { currentScene++; } if (yOffset < prevScrollHeight) { if (currentScene === 0) { // 브라우저 바운스 효과로 인한 마이너스가 되는것을 방지 (모바일) return; } currentScene--; } console.log(currentScene); } window.addEventListener("resize", setLayout); window.addEventListener("scroll", () => { yOffset = window.pageYOffset; scrollLoop(); }); setLayout(); })(); main.css @charset 'utf-8'; html { font-family: "Noto Sans KR", sans-serif; font-size: 14px; } body { overflow-x: hidden; color: rgb(29, 29, 31); letter-spacing: -0.05em; background: white; margin: 0; } p { line-height: 1.6; } a { color: rgb(29, 29, 31); text-decoration: none; } .global-nav { position: absolute; top: 0; left: 0; width: 100%; height: 44px; padding: 0 1rem; } .local-nav { position: absolute; top: 45px; /* .global-nav 보다 1px 위에 */ left: 0; width: 100%; height: 52px; border-bottom: 1px solid #ddd; } .global-nav-links, .local-nav-links { display: flex; align-items: center; max-width: 1000px; height: 100%; margin: 0 auto; } .local-nav-links a:not(.product-name) { margin-left: 2em; } .local-nav-links .product-name { margin-right: auto; font-size: 1.4rem; font-weight: bold; } .local-nav-links a { font-size: 0.8rem; } .global-nav-links { justify-content: space-between; } /* */ .scroll-section { position: relative; border: 3px solid red; padding-top: 50vh; } #scroll-section-0 h1 { position: relative; font-size: 4rem; text-align: center; } .main-message { display: flex; align-items: center; justify-content: center; margin: 5px 0; height: 3em; font-size: 2.5rem; } .main-message p { font-weight: bold; text-align: center; line-height: 1.2; } /* */ .main-message small { display: block; margin-bottom: 0.5em; font-size: 1.2rem; } #scroll-section-2 .main-message { font-size: 3.5rem; } .description { font-size: 2rem; max-width: 1000px; padding: 0 1rem; font-size: 1.2rem; color: #888; } .description strong { float: left; margin-right: 0.2em; font-size: 3rem; color: rgb(29, 29, 31); } .desc-message { font-weight: bold; width: 50%; } .pin { width: 1px; height: 100px; background-color: rgb(29, 29, 31); } .mid-message { padding: 0 1rem; font-size: 2rem; color: #888; max-width: 1000px; margin: 0 auto; } .mid-message strong { color: rgb(29, 29, 31); } .canvas-caption { color: #888; padding: 0 1rem; font-size: 1.2rem; max-width: 1000px; margin: 0 auto; } .footer { display: flex; align-items: center; justify-content: center; height: 7rem; color: white; background-color: darkorange; } @media (min-width: 1024px) { #scroll-section-0 h1 { font-size: 9vw; } .main-message { font-size: 4vw; } .description { margin: 0 auto; } .description strong { font-size: 6rem; } #scroll-section-2 .main-message { font-size: 6vw; } .main-message small { font-size: 1.5vw; } .desc-message { width: 20%; } .mid-message { font-size: 4vw; } .canvas-caption { font-size: 2rem; } } /* */ .sticky-elem { display: none; position: fixed; top: 0; left: 0; width: 100%; } #show-scene-0 #scroll-section-0 .sticky-elem, #show-scene-1 #scroll-section-1 .sticky-elem, #show-scene-2 #scroll-section-2 .sticky-elem, #show-scene-3 #scroll-section-3 .sticky-elem { display: block; }

2
문태웅 Moon 프로필

font-size를 vw와 rem 나누는 기준이 있을까요? 문태웅 Moon 21일 전
@media (min-width: 1024px) { .main-message { font-size: 4vw; } .description { font-size: 2rem; } } 이런 식으로 font-size에 vw와 rem 쓸때를 구분한 이유가 있을까요?

2
Duram Byun 프로필

이미지를 추가했을때 궁금합니다. Duram Byun 23일 전
지금 첫번째 이미지 스크롤 후에 두번쨰 이미지도 스크롤 되서 올라가는데 그 이후에 이미지를 추가해서 스크롤 하려면 배열에 이미지를 추가한후 다른것도 수정해야되는 내용이 어떤건가요~?

1
B. 리즈 프로필

초반강의를 보고 커스터마이징 해보고 있어요~ B. 리즈 26일 전
jquery로만 하다가 javascript로 해볼라니 1도 모르겠고 어렵네요~~~ https://codepen.io/JoonSol/pen/eYJjpWp 여기 보면 사이드 버튼에 각 섹션에 불이 들어오게 구현해보고 싶은데요~ body에 붙는 아이디 값을 side btn ,index에 class가 붙게 하려고 하는데요~ 어떤식으로 써보면 좋을지 조언해 주시면 감사하겠습니다. 추가) 첫번째 섹션에서 스크롤 높이값을 찍어보고 있는데 마지막 섹션의 8play가 나오지 않아요~ 기존에 jquery로 할때로 이런 문제 때문에 마지막 섹션 이벤트 내용을 어떻게 처리할지 고민이었는데요. 8섹션의 높이값이 작아서 안나오는걸까요? 기존에는 현재 높이값의 5배로 했었는데 지금은 그냥 다 기본값으라서요~ 이 부분도 알려주심 감사하겠습니다.

2
yodj 프로필

requestAnimationFrame 발생 관련 yodj 26일 전
안녕하세요. 좋은 강의 해주셔서 감사합니다. 궁금한 부분이 있어 문의드립니다. 1. requestAnimationFrame 관련 업데이트 해주신 코드 apple-clone-v5 > 03-canvas-2.html에서 loop함수가 계속 실행되는 이유를 찾아보니, requestAnimationFrame에 의해 로드되는 순간부터 발생을 하고 있네요.  scroll을 하지 않더라도 이 함수가 계속 실행하면 불필요한 자원이 생기는건 아닌지 궁금합니다.(requestAnimationFrame가 많아지면 console.log만 찍어도 브라우저가 버벅이는 현상이 있는것 같더라구요.) scroll 여부에 따라 requestAnimationFrame 또는 cancelAnimationFrame을 해주는 방식으로 하는게 맞을지요. 2. delayedYOffset 문의 smoothscroll.html > 41번째에 있는 아래 구문의 의미가 궁금합니다. delayedYOffset = delayedYOffset + (pageYOffset - delayedYOffset) * acc; 질문에 대한 보충 영상이 있다면 몇 회차인지 알려주셔도 괜찮습니다.

2
지식공유자 되기
많은 사람들에게 배움의 기회를 주고,
경제적 보상을 받아보세요.
지식공유참여
기업 교육을 위한 인프런
“인프런 비즈니스” 를 통해 모든 팀원이 인프런의 강의들을
자유롭게 학습하는 환경을 제공하세요.
인프런 비즈니스