월 15,400원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결애플 웹사이트 인터랙션 클론!
동작(?)을 안해요..!
https://github.com/jellybrown/apple_clone/blob/master/js/main.js 제가 빼먹은게 있나요...? 어려워서 돌려봤는데 어디서 뭘빼먹었는지 모르겠어요 ㅠ.ㅠ
- 해결됨애플 웹사이트 인터랙션 클론!
블로킹
이미지 경로의 문제가 있는건가요???
- 애플 웹사이트 인터랙션 클론!
드로우 이미지 오류
삭제된 글입니다
- 미해결애플 웹사이트 인터랙션 클론!
4개의 씬을 5개로 늘려서 응용하려는 데 작동이 잘 안되네요 ㅠㅠ
@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(); })();
- 해결됨애플 웹사이트 인터랙션 클론!
안녕하세요 '현재 활성시킬 씬 결정하기' 강좌편에서 문의가 있습니다.
안녕하세요 다름이 아니라 해당 '현재 활성시킬 씬 결정하기' 에서 코드를 입력하고 있는데 원하는 위치 스크롤 보다 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; }
- 미해결애플 웹사이트 인터랙션 클론!
font-size를 vw와 rem 나누는 기준이 있을까요?
@media (min-width: 1024px) { .main-message { font-size: 4vw; } .description { font-size: 2rem; } } 이런 식으로 font-size에 vw와 rem 쓸때를 구분한 이유가 있을까요?
- 미해결애플 웹사이트 인터랙션 클론!
이미지를 추가했을때 궁금합니다.
지금 첫번째 이미지 스크롤 후에 두번쨰 이미지도 스크롤 되서 올라가는데 그 이후에 이미지를 추가해서 스크롤 하려면 배열에 이미지를 추가한후 다른것도 수정해야되는 내용이 어떤건가요~?
- 해결됨애플 웹사이트 인터랙션 클론!
초반강의를 보고 커스터마이징 해보고 있어요~
jquery로만 하다가 javascript로 해볼라니 1도 모르겠고 어렵네요~~~ https://codepen.io/JoonSol/pen/eYJjpWp 여기 보면 사이드 버튼에 각 섹션에 불이 들어오게 구현해보고 싶은데요~ body에 붙는 아이디 값을 side btn ,index에 class가 붙게 하려고 하는데요~ 어떤식으로 써보면 좋을지 조언해 주시면 감사하겠습니다. 추가) 첫번째 섹션에서 스크롤 높이값을 찍어보고 있는데 마지막 섹션의 8play가 나오지 않아요~ 기존에 jquery로 할때로 이런 문제 때문에 마지막 섹션 이벤트 내용을 어떻게 처리할지 고민이었는데요. 8섹션의 높이값이 작아서 안나오는걸까요? 기존에는 현재 높이값의 5배로 했었는데 지금은 그냥 다 기본값으라서요~ 이 부분도 알려주심 감사하겠습니다.
- 미해결애플 웹사이트 인터랙션 클론!
requestAnimationFrame 발생 관련
안녕하세요. 좋은 강의 해주셔서 감사합니다. 궁금한 부분이 있어 문의드립니다. 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; 질문에 대한 보충 영상이 있다면 몇 회차인지 알려주셔도 괜찮습니다.
- 해결됨애플 웹사이트 인터랙션 클론!
질문드려요~~~~~~
중간까지 하다가 처음부터 다시 하고 있는데 ~ 첨부터 막혔어요~, style 속성을 읽지 못하는데 이유를 모르겠습니다.~html이나,css완성본을 썼는데 혹시 연관이 있을까요? 코드의 문제점을 알고 싶습니다. (() =>{ // 1.animate 정보를 배열에 담는다. const sceneInfo =[ // 2.객체 4개를 만든다.섹션이 4개 이므로 // 3.각 구간의 type을 설정한다. {//0 type:'sticky', heightNum:5,//브라우저 높이의 5배로 scrollHeight세팅 scrollHeight:0, // 5.objs객체를 담는다. objs:{ container: document.querySelector('#scroll-section-0'), } }, {//1 type:'normal', heightNum:5, scrollHeight:0, objs:{ container: document.querySelector('#scroll-section-1'), } }, {//2 type:'sticky', heightNum:5,//브라우저 높이의 5배로 scrollHeight세팅 scrollHeight:0, objs:{ container: document.querySelector('#scroll-section-2'), } }, {//3 type:'sticky', heightNum:5,//브라우저 높이의 5배로 scrollHeight세팅 scrollHeight:0, objs:{ container: document.querySelector('#scroll-section-3'), } }, {//4 type:'sticky', heightNum:5,//브라우저 높이의 5배로 scrollHeight세팅 scrollHeight:0, objs:{ container: document.querySelector('#scroll-section-4'), } }, ]; //4.layout setting 함수 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`; } } window.addEventListener('resize',setLayout); setLayout() })(); 슷한 내용을 질문한 수강생이 있는지 먼저 검색을 해주세요. (중복 질문을 자제해주세요.)• 서비스 운영 관련 질문은 인프런 우측 하단 ‘문의하기’를 이용해주세요. (영상 재생 문제, 사이트 버그, 강의 환불 등) 질문 전달에도 요령이 필요합니다.• 지식공유자가 질문을 좀 더 쉽게 확인할 수 있게 도와주세요.• 강의실 페이지(/lecture) 에서 '질문하기'를 이용해주시면 질문과 연관된 수업 영상 제목이 함께 등록됩니다.• 강의 대시보드에서 질문을 남길 경우, 관련 섹션 및 수업 제목을 기재해주세요. • 수업 특정 구간에 대한 질문은 꼭 영상 타임코드를 남겨주세요! 구체적인 질문일수록 명확한 답을 받을 수 있어요.• 질문 제목은 핵심 키워드를 포함해 간결하게 적어주세요.• 질문 내용은 자세하게 적어주시되, 지식공유자가 답변할 수 있도록 구체적으로 남겨주세요.• 정확한 질문 내용과 함께 코드를 적어주시거나, 캡쳐 이미지를 첨부하면 더욱 좋습니다. 기본적인 예의를 지켜주세요.• 정중한 의견 및 문의 제시, 감사 인사 등의 커뮤니케이션은 더 나은 강의를 위한 기틀이 됩니다. • 질문이 있을 때에는 강의를 만든 지식공유자에 대한 기본적인 예의를 꼭 지켜주세요. • 반말, 욕설, 과격한 표현 등 지식공유자를 불쾌하게 할 수 있는 내용은 스팸 처리 등 제재를 가할 수 있습 니다.
- 미해결애플 웹사이트 인터랙션 클론!
선생님 저는 sticky__elem이 display : none; 해도 안사라지는데 어떻게하죠
@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; } /* Nav */ .global__nav { /* js에서 스크롤높이에 영향을 미치기 때문에 포지션 설정 */ position: absolute; top: 0; left: 0; width: 100%; /* global__nav의 전체 높이 설정 */ height: 44px; /* 모바일 패딩 좌우 1rem */ padding: 0 1rem; } .local__nav { /* js에서 스크롤높이에 영향을 미치기 때문에 포지션 설정 */ position: absolute; top: 45px; left: 0; width: 100%; height: 52px; border-bottom: 1px solid #bbd; /* 모바일 패딩 좌우 1rem */ padding: 0 1rem; } .global__nav__links, .local__nav__links { display: flex; /* 수직방향도 중앙 정렬 aling-items */ align-items: center; /* 총 너비 1000px */ max-width: 1000px; /* aling-items를 썼을때 자신의 컨텐츠만큼만 크기를 차지하기 때문에 센터정렬이 안되서 자신을 감싸는 부모의 높이만큼 높이를 설정해줘야함 그래서 height 100%(부모의높이 100프로)설정 */ height: 100%; /* center 정렬을위해서 왼쪽,오른쪽 margin auto */ /* margin auto를 사용하면 사용한 방향의 마진을 모두 사용함 */ margin: 0 auto; } .global__nav__links { /* links안에 메뉴들 축방향으로 justify-content로 정렬 */ 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; } /* not = local__nav__links a에 있는 것중에 not()괄호안에 들어가는 것은 빼고 적용한다는 것 */ .local__nav__links a:not(.product__name) { margin-left: 2em; } /* !-- Nav -- */ /* Section */ .sticky__elem { /* Scroll구간이 맞을때만 보이기 위헤서 display:none으로 안보이게 만들어줌 */ position: fixed; display: none; top: 0; left: 0; width: 100%; } .scroll__section { /* scroll__section들이 너무 붙어있어서 padding-top으로 떨어뜨려줍니다 */ padding-top: 50vh; border: 3px solid red; } /* scroll__section__0 */ #scroll__section__0 h1 { /* font-size를 body기준 4rem */ font-size: 4rem; /* text 중앙 정렬 */ text-align: center; } /* !--scroll__section__0 -- */ /* scroll__section__1 */ .main__message { /* main_messge 중앙정렬 */ display: flex; align-items: center; justify-content: center; /* 위아래에 margin을 넣어서 main__message마다 간격주기 */ margin: 5px 0px; /* em을 쓰는 이유는 폰트사이즈 기준으로 3배가 되는것*/ height: 3em; /* rem은 html의 font-size 기준 */ font-size: 2.5rem; } .main__message p { /* main__messge의 p문단 설정 */ font-weight: bold; /* text중앙 정렬 */ text-align: center; /* 글자 간격높이 */ line-height: 1.2; } .description { max-width: 1000px; margin: 0 auto; /* descripition 좌우 여백 1rem설정 */ 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); } /* !-- scroll__section__1 -- */ /* scroll__section__2 */ .main__message small { /* 다음줄로 넘어갈 수 있게 small = block */ display: block; margin-bottom: 0.5em; font-size: 1.2rem; } #scroll__section__2 .main__message { font-size: 3.5rem; } .desc__message { width: 50%; font-weight: bold; } .pin { width: 1px; height: 100px; background: rgb(29, 29, 31); } /* !-- scroll__section__2 -- */ /* scroll__section__3 */ .mid__mesagge { max-width: 1000px; margin: 0 auto; padding: 0 1rem; font-size: 2rem; color: #888; } .mid__mesagge strong { color: rgb(29, 29, 31); ; } .canvas__caption { max-width: 1000px; margin: 0 auto; padding: 0 1rem; font-size: 1.2rem; color: #888; } /* !-- scroll__section__3 */ /* Footer */ .footer { display: flex; align-items: center; justify-content: center; height: 7rem; color: white; background: gold; } /* !-- footer -- */ /* desctop size */ @media(min-width: 1024px) { #scroll__section__0 h1 { font-size: 9vw; } .main__message { font-size: 4vw; } .description { font-size: 2rem; } .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__mesagge { font-size: 4vw; } .canvas__caption { font-size: 2rem; } } /* !--Desktop size -- */
- 미해결애플 웹사이트 인터랙션 클론!
canvas_scale관해 질문드립니다.
안녕하세요 강사님! 오늘 values.canvas_scale로 블랜딩 이미지를 줄이는 방법을 배웠는데, values에서 canvas_scale = [0, 0 {start:0, end:0})에 관한 개념이 헷갈려서 그런 지 values.canvas_scale[0] = canvasScaleRatio가 되는 이유를 잘 모르겠습니다. values.canvas_scale[0]과 [1]은 블랜딩 이미지의 x좌표와 y좌표 아닌가요? rect1X와 rect2X 때는 그렇게 사용한 것같은데 갑자기 캔버스를 가로 세로 스케일로 줄이는 비율을 넣어줘서 이해가 잘 가지 않습니다. 감사합니다!
- 미해결애플 웹사이트 인터랙션 클론!
오마갓.. 정리하다가 헷갈립니다.
.main-message { display: flex; align-items: center; justify-content: center; margin: 5px 0; height: 3em; font-size: 2.5rem; } 여기서 height:3em은 현재 메인 메시지클래스의 폰트사이즈 2.5rem의 세배크기라고하셧는데 main-message의 부모는 html아닌가요? ㅠㅠ rem:html폰트사이즈 em: 현재 부모의 폰트사이즈 기준 이렇게 알고있었는데 em은 요소 자신의 폰트사이즈에 비례해서 크기가 정해지는건가요?
- 해결됨애플 웹사이트 인터랙션 클론!
style적용 우선순위에 대해질문
우선 영상 잘보고있습니다. 감사합니다 고화질 비디오 부드럽게처리2에서 <canvas class="sample-canvas" width="1920" height="1080"></canvas> 로 인라인 스타일을 주시고 css에서 width:100%으로 줬기때문에 화면크기에 따라 바뀐다고 하셨는데 인라인스타일로 주면 css보다 우선순위가 높게잡혀서 css가 안먹히지 않나요?? 답변미리감사드립니다
- 해결됨애플 웹사이트 인터랙션 클론!
offsetTop에 관한 설명이 잘 이해가 되지 않습니다.
안녕하세요 현재 getBoundingClientTop 대신 offsetTop을 쓰는 부분에 관한 영상을 보고 있는데, 몇 번을 다시 봐도 이해가 잘 가지 않습니다. 왜 우리가 offsetTop값이 아니라 scale로 조정된 값이 필요하고, offsetTop은 왜 612라는 값을 나오는 것인 지 모르겠습니다. 좀 더 쉽게 다시 한 번 부가 설명해주시면 감사하겠습니다.
- 미해결애플 웹사이트 인터랙션 클론!
저는 pin이 안나올까요 ㅠㅠ
@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; } .global-nav { position: absolute; top: 0; left: 0; width: 100%; height: 44px; padding: 0 1rem; } .local-nav { position: absolute; top: 45px; left: 0; width: 100%; height: 52px; padding: 0 1rem; border-bottom: 1px solid #ddd; } .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.2rem; } .local-nav-links a { font-size: 0.8rem; } .local-nav-links a:not(.product-name) { margin-left: 2em; } .scroll-section { padding-top: 50vh; border: 3px solid red; } #scroll-section-0 h1 { font-size: 4rem; text-align: center; } .main-message { top: 35vh; display: flex; align-items: center; justify-content: center; margin: 5px 0; height: 3em; font-size: 2.5rem; opacity: 0; } .main-message small { display: block; font-size: 1.2rem; margin-bottom: 0.5em; } #scroll-section-2 .main-message { font-size: 3.5rem; } .desc-message { font-weight: bold; width: 50%; } .pin { width: 1px; height: 100px; background: rgb(29, 29, 31); } #scroll-section-2.b { top: 10%; left: 40%; } #scroll-section-2.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: 0 auto; padding: 0 1rem; color: #888; font-size: 1.2rem; } .footer { display: flex; align-items: center; justify-content: center; height: 7rem; color: white; background: darkorange; } .main-message p { line-height: 1.2; font-weight: bold; text-align: center; } .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); } .sticky-elem { position: fixed; left: 0; width: 100%; display: none; } #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; } @media (min-width:1024px) { #scroll-section-0 h1 { font-size: 9vw; } .main-message { font-size: 2rem; } .description { font-size: 2rem; } .description strong { font-size: 6rem; } #scroll-section-2 .main-message { font-size: 6vw; } .main-message small { font-size: 1.5vw; } .desc-message { width: 20%; } #scroll-section-2.b { top: 20%; left: 53%; } #scroll-section-2.c { left: 55%; } .mid-message { font-size: 4vw; } canvas-caption { 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> <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">Storeds</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 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. Nam praesentium ducimus odio, eveniet eaque soluta aliquid animi unde inventore est sapiente fugit laboriosam voluptatum ipsum quidem illum molestiae obcaecati ut voluptas assumenda quae. Rerum hic, odio unde eum quod? Inventore eius provident vel impedit veniam incidunt at voluptatibus facere odio totam iure ipsam libero, sit eligendi nostrum velit laborum quaerat ipsum ut consectetur. Quos voluptatibus pariatur, accusantium laborum iusto, impedit. Ex enim veritatis nesciunt reprehenderit architecto reiciendis amet cum nihil aut beatae itaque, nulla labore eum quaerat facilis dolorem veniam! Consequatur aliquam placeat dicta voluptas nam recusandae quibusdam nostrum, culpa! Blanditiis in ipsa quaerat dolorem tempore velit, deleniti, excepturi placeat eum facilis officia recusandae praesentium nesciunt porro quibusdam debitis consequuntur, nobis saepe? Fugit voluptates consectetur sapiente maxime dolore, hic, esse laborum expedita eligendi natus cum dolor pariatur qui dicta, minus. Repudiandae architecto, sit et similique odio corrupti repellat. Id iusto perferendis dolores quos non aliquid quis officia, voluptatibus odio. Tenetur fugit repellat quas, consectetur explicabo nihil labore optio molestias, omnis minima dolorum! Expedita corporis beatae, inventore porro nostrum tempora rem. Eligendi, rerum? Quaerat eaque deserunt animi, voluptatibus, quis accusantium fuga recusandae temporibus deleniti ipsa ut laborum, magni cupiditate, illo. Corporis. </p> </section> <section class="scroll-section" id="scroll-section-2"> <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-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 영주마실</footer> </div> <script src="js/main.js"></script> </body> </html> (() => { //전역변수를 피하기 위해서 씀. (function()) let yOffset = 0; // window.pageYOffset 대신 쓸 변수 let prevScrollHeight = 0; //현재 스크롤 위치(yOffset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이값의 합 let currentScene = 0; //현재 활성화된(눈 앞에 보고있는) 씬(scroll-section) let currenYOffset = yOffset - prevScrollHeight; let enterNewScene = false; // 새로운 씬에 들어갔다. // sceneInfo 추가내용 적용 후 const sceneInfo = [ { // 0 type: 'sticky', heightNum: 5, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objs: { 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 }], 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', // heightNum: 5, // type normal에서는 필요 없음 scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-1') } }, { // 2 type: 'sticky', heightNum: 5, 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', heightNum: 5, 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].heightNum * window.innerHeight; }else if(sceneInfo[i].type === 'nomarl'){ 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}`); } function calcValues(values, currentYOffset) { 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; // 안써주면 undefined } // playAnimation 추가내용 적용 후 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 0: // console.log('0 play'); 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 2: // console.log('2 play'); 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)})`; } break; case 3: // console.log('3 play'); 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}`); } if (yOffset < prevScrollHeight) { enterNewScene = true; if (currentScene === 0) return; // 브라우저 바운스 효과로 인해 마이너스 되는것을 방지 currentScene--; document.body.setAttribute('id', `show-scene-${currentScene}`); } if (enterNewScene) return; playAnimation(); } window.addEventListener('resize', setLayout); //세로크기에 따라 height 반응형 window.addEventListener('scroll', () => { yOffset = window.pageYOffset; scrollLoop(); }); window.addEventListener('load', setLayout); window.addEventListener('resize', setLayout); setLayout(); })();
- 애플 웹사이트 인터랙션 클론!
새로운 캔버스 영역
삭제된 글입니다
- 미해결애플 웹사이트 인터랙션 클론!
샘플 동영상 어떻게 찍으셨어요?
실습하려고 하는데 핸드폰으로 찍은 거는 너무 길쭉해서 쓰기가 어렵네요.. 동영상 편집같은 것을 잘안해봐서 샘플 동영상같이 깔끔하고 화면에 딱맞는 크기로 만들려면 어떻게 해야하는지 말씀해주시면 감사하겠습니다~!
- 해결됨애플 웹사이트 인터랙션 클론!
document.body.offsetHeigh vs window.innerHeight
document.body.offsetHeight 과 window.innerHeight 의 차이를 알고싶습니다 ㅠ__ㅠ 차이를 비교해보려고 document.body.offsetHeight->window.offsetHeight로 바꿔보니 안되더라구요 검색해보니 둘다 스크롤바를 포함한 높이라고 하는데.. body일때랑 window일때랑 다르게 써야하는건지.. 같은 스크롤바 포함 높이인데 다르게 써야하는 이유가 궁금합니다~
- 미해결애플 웹사이트 인터랙션 클론!
canvasScrollRatio를 맞춰주는 기준
let heightRatio = window.innerHeight/objs.canvas.height; let widthRatio = window.innerWidth/objs.canvas.width; let canvasRatio; console.log('heightRatio:',heightRatio,'widthRatio:',widthRatio); if (heightRatio<=widthRatio) { //캔버스보다 브라우저가 납작할때 canvasRatio = widthRatio } else { //캔버스보다 브라우저가 길쭉할때 canvasRatio = heightRatio; } 1.캔버스 대비 화면의 폭/높이 비율을 구하는 건 알겠는데 왜 한쪽비율의 값이 더 클 때 그 값을 canvasRatio로 쓰는지 잘 모르겠습니다. 스크롤하다가 캔버스의 윗 부분이 화면에 들어맞으면 움직이기 위해서라면 heigthRatio만 조절해서 캔버스의 높이를 화면의 높이와 맞춰주면 되지 않나 헷갈려서요~ objs.canvas.style.transform=`scale(${canvasRatio})`; objs.context.drawImage(objs.videoImages[0],0,0); //recalculated하는 이유? //캔버스 사이즈에 맞춰 가정한 innerWidth,innerHeight const recalculatedWidth = window.innerWidth / canvasRatio; const recalculatedHeight= window.innerHeight / canvasRatio; 2.recalcaulated하는 이유 recalculatedWidth/Height 값을 구하면 둘 중 하나는 원래 캔버스의 너비/높이 중 하나의 값을 가지게 된다고 생각되는데요. 캔버스를 화면의 크기에 맞춰준 상태인데 다시 계산하는 이유가 있을까요? 화면의 크기에 맞춰진 캔버스의 양 끝에 그대로 사각형을 그려주면 되는 게 아닌가 싶어서요. 질문이 너무 긴데 이해가 쉽지 않아 여쭤봅니다. 항상 강의 잘 듣고 있습니다.