미해결
애플 웹사이트 인터랙션 클론!
@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(); })();
인터랙티브-웹 HTML/CSS svg 웹 디자인 javascript 클론코딩
릴렉스
2020-07-19T08:22:18.998Z
댓글 1
좋아요 0
조회수 263
해결됨
애플 웹사이트 인터랙션 클론!
안녕하세요 다름이 아니라 해당 '현재 활성시킬 씬 결정하기' 에서 코드를 입력하고 있는데 원하는 위치 스크롤 보다 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; }
HTML/CSS javascript svg 인터랙티브-웹 웹 디자인 클론코딩
Kim jae-eok
2020-07-19T07:51:27.048Z
댓글 2
좋아요 0
조회수 326