22%
59,400원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결애플 웹사이트 인터랙션 클론!
canvas에 마지막 이미지가 나타났다가 시작합니다.
안녕하세요 수업내용 토대로 새로운 웹사이트 제작중입니다. canvas로 영상을 이미지화하여 스크롤할때마다 이미지가 영상처럼 돌아가게 적용했는데요. 문제는 첫 시작 이미지가 마지막 이미지가 보였다가 시작합니다. 빠르게 스크롤하면 나타나고 천천히 하면 이런 현상이 나타나지 않습니다ㅠㅠ 이미지파일 순서대로 넣었고 소스도 수업내용과 동일합니다. 이유가 뭘까요? 시작할때 오퍼시티를 줘서 흐려놓긴 했는데 조금씩 보여서 찝찝합니다. 두번째 섹션이 시작하자마자 이미지를 그리고 있습니다. 그래서 첫번째 섹션에서 이미지를 불러오게도 해봤는데 소용이 없습니다..
- 미해결애플 웹사이트 인터랙션 클론!
질문이 있어서 올려봅니다..ㅎ
안녕하세요 그 강의롤 보면서 페이지를 만들고있는데 네비에게션바에 링크를 걸어서 새로운 페이지를 만들어주고 버튼을 누르면 이동하게 만들었습니다. 근데 ... 문제는 새로운 페이지로 이동한뒤 뒤로가기 버튼을 누르면 scrollRatio에 NaN값이 들어오네요 ㅠㅠ 방법이 있을까요 .. ?
- 미해결애플 웹사이트 인터랙션 클론!
캔버스 안 이미지 크기
수업 진행을 하다가 궁금한 것이 있어 질문합니다. getContext('2d').drawImage를 통해 가지고 온 이미지를 여백없이 화면에 딱 맞게 설정은 못하나요 ? 컵 영상말고 다른 영상을 넣어보고 싶은데 맞추기가 어렵네요ㅠ
- 미해결애플 웹사이트 인터랙션 클론!
왜 마이너스가 나오나요?
안녕하세요씬이 바뀌는 순간에 왜 음수가 나오는지는 알수 없나요?
- 미해결애플 웹사이트 인터랙션 클론!
질문있습니다.
선생님 안녕하세요. 인터렉티브 웹사이트 강의 중간까지 듣고 애플클론 강의를 듣고 있습니다. 이런 강의를 만들어주신거에 감사함을 느낍니다. 인터렉티브 웹사이트에서도 언급하셨지만 전역변수 사용을 막기위해서 현 강의들에서는 익명함수안에서 정의를 하시는 걸로 이해하고 있습니다. 그러지 않고, 각 js 파일에서 다음과 같이 처리하는 것도, 블록 Scope 안에서 사용하는 것이랑은 다른걸까요 ? { const arr = [1, 2, 3]; } console.log(arr);
- 미해결애플 웹사이트 인터랙션 클론!
css 우선순위?
안녕하세요!.main-message small{ display: block; font-size: 1rem; margin-bottom: 0.5em; } #scroll-section-2 .main-message { font-size: 3.5rem; color: blue; }html 이 14pt일때small 은 14*1 이고아래 main-message 는 14*3.5라는 의도는 알겠고 실제로도그렇게 확인이 됐는데요,궁금한건small태그는 main-message안에있기도 하니까, 아래 3.5배가 한번 더 적용되어야된다고 생각했는데 안 되는 이유가뭔가요??
- 미해결애플 웹사이트 인터랙션 클론!
font-weight: bold;
bold로 하면 얼만큼 두꺼워진다는 기준이 있나요?예를 들어 이 프로젝트처럼 폰트 import할 때 400, 900을 했으면 무조건 900으로되나요??
- 미해결애플 웹사이트 인터랙션 클론!
@charset 'utf-8';
@charset 'utf-8'; 은 무엇인가요?? 검색해봐도 말이 어려워서 이해가 잘 안되네요
- 미해결애플 웹사이트 인터랙션 클론!
vw 사용 시 글씨 밀림 문제
안녕하세요, 선생님. 강의를 듣던 중 mid-message에 vw값을 주고 브라우저를 일정 크기 이상 키우면 사진과 같이 글자가 커지다가 밀려서 내려가는 현상이 발생합니다. 폰트 사이즈에 상한선을 걸 수 있을까요?
- 미해결애플 웹사이트 인터랙션 클론!
코드 적용하면서 전체적으로 문제 없는지 확인 하다 해결 안되는 부분이 있어 문의드립니다~
안녕하세요~ js코드는 아니고 css 문젠가 싶기도 한데.. 나름 혼자 찾아서 해결하려 했는데 안되네요ㅜ 2가지 인데요, 첫째로 scene-2의 b번 메세지의 줄바꿈이 창 크기에 따라 유연하게 반응해야 하는데 아래 사진처럼 들쑥날쑥 합니다. \ 이런식으로 창 크기에 따라 다르고 심지어 글이 중간에 안보이기도 합니다. 둘째로 창 크기에 따라 scene-1에 해당하는 본문 내용이 scene-2로 넘쳐 흐릅니다. 문제 없이 잘 보일 때도 있지만 이 역시 창 크기가 변경되면 아래처럼 텍스트가 아래 영역으로 넘쳐 흐르고 그 정도가 창마다 다릅니다.
- 미해결애플 웹사이트 인터랙션 클론!
for문 같은 경우
제가 평소에 for문 대신에 forEach문을 사용하는데forEach문에서 break를 사용할수 없어서... return false;로 대신 하고 있습니다. 그렇게 되면 body태그에 id값이 정상적인 인텍스가 못들어 가네요... 왜 그런지 혹시 알수 있을까요??? ( 짐작하기로는 break는 바로 특정 조건에 정지가 되는것 같고, return false는 정지가 되긴 하는데 모든 배열요소들을 한번 돌고 정지하는것 같네요.. 맞나요?? )
- 미해결애플 웹사이트 인터랙션 클론!
질문 드립니다.
안녕하세요~ 매일 한걸음 한걸음 강의들으면 공부하고 있는 수강생 입니다ㅎㅎ '완전히 빠져들게~~ 세라믹' 이 문장을 중간에 오게 하기 위해서 main-message의 스타일에 top: 35vh; 를 추가해주셨는데 왜 화면의 중간에 위치하려면 35%로 적어주는 걸까요? 대략 그정도 되어보여서 임의로 정하신 건지 아니라면 통상적으로 그렇게 사용하는 건지 만약 텍스트가 2줄 이상이라면 수치는 변경되야 하는건지 잘 모르겠습니다ㅎㅎ
- 미해결애플 웹사이트 인터랙션 클론!
video section 영역을 2개 이상 추가 했더니 첫번째 두번째 영역은 나오는데 3번째 영역부터 이미지가 그려지지 않습니다.
안녕하세요 좋은 강의 감사합니다. 예제 샘플을 이용해서 한가지 해보고 싶은게 있어 테스트 중에 있습니다. scroll section-0, scroll section-2 구간에 video-canvas-0, video-canvas-1을 main-js 샘플을 만들어 주신것처럼 똑같이 만들었습니다. 추후에 scroll-section-4를 추가 하고 video-canvas-2를 만들어 1,2와 같이 동일하게 만들었는데 3번째 영역이 보여지질 않습니다 ㅜㅜ 어떤 문제가 있는지 알고 싶습니다. 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>IKEA</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="main.css"> </head> <body> <div class="container"> <section class="scroll-section" id="scroll-section-0"> <div class="sticky-elem stick-elem-canvas"> <canvas id="video-canvas-0" width="1920" height="1080"></canvas> </div> </section> <section class="scroll-section" id="scroll-section-1"> <p class="description"> <strong>재료소개</strong> 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 </p> </section> <section class="scroll-section" id="scroll-section-2"> <div class="sticky-elem stick-elem-canvas"> <canvas id="video-canvas-1" width="1920" height="1080"></canvas> </div> </section> <section class="scroll-section" id="scroll-section-3"> <p class="description"> <strong>재료소개</strong> 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 </p> </section> <section class="scroll-section" id="scroll-section-4"> <div class="sticky-elem stick-elem-canvas"> <canvas id="video-canvas-2" width="1920" height="1080"></canvas> </div> </section> <section class="scroll-section" id="scroll-section-5"> <p class="description"> <strong>재료소개</strong> 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 재료소개 <br/><br/><br/><br/><br/><br/><br/> </p> </section> </div> <script src="js/main.js"></script> </body> </html> main.css #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, #show-scene-5 #scroll-section-5 .sticky-elem { display: block; will-change: transform, opacity; } main.js (() => { let yOffset = 0; //window.pageYoffset 대신 쓸 변수 let prevScrollHeight = 0; //현재 스크롤 위치(yOffset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이값의 합 let currentScene = 0; //현재 활성화된(눈 앞에 보고있는) 씬(scroll-section) let enterNewScene = false; //새로운 scene이 시작된 순간 true let acc = 0.1; let delayedYOffset = 0; let rafId; let rafState; const sceneInfo = [ { // 0 type : 'sticky', heightNum : 8, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight : 0, objs: { container : document.querySelector('#scroll-section-0'), canvas : document.querySelector('#video-canvas-0'), context : document.querySelector('#video-canvas-0').getContext('2d'), videoImages : [] }, values : { videoImageCount : 314, imageSequence : [0, 313], canvas_opacity : [1, 0, {start: 0.9, end: 1}], } }, { // 1 type : 'normal', // heightNum : 5, // 브라우저 높이의 5배로 scrollHeight 세팅 (normal에서는 필요없음) scrollHeight : 0, objs: { container : document.querySelector('#scroll-section-1') } }, { // 2 type : 'sticky', heightNum : 8, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight : 0, objs: { container: document.querySelector('#scroll-section-2'), canvas : document.querySelector('#video-canvas-1'), context : document.querySelector('#video-canvas-1').getContext('2d'), videoImages : [] }, values: { videoImageCount : 471, imageSequence : [0, 470], canvas_opacity_in : [0, 1, {start: 0, end: 0.1}], canvas_opacity_out : [1, 0, {start: 0.95, end: 1}], } }, { // 3 type : 'normal', // heightNum : 5, // 브라우저 높이의 5배로 scrollHeight 세팅 (normal에서는 필요없음) scrollHeight : 0, objs: { container : document.querySelector('#scroll-section-3') } }, { // 4 type : 'sticky', heightNum : 8, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight : 0, objs: { container: document.querySelector('#scroll-section-4'), canvas : document.querySelector('#video-canvas-2'), context : document.querySelector('#video-canvas-2').getContext('2d'), videoImages : [] }, values: { videoImageCount : 338, imageSequence : [0, 337], canvas_opacity_in : [0, 1, {start: 0, end: 0.1}], canvas_opacity_out : [1, 0, {start: 0.95, end: 1}], } }, { // 5 type : 'normal', // heightNum : 5, // 브라우저 높이의 5배로 scrollHeight 세팅 (normal에서는 필요없음) scrollHeight : 0, objs: { container : document.querySelector('#scroll-section-5') } }, ]; function setCanvasImages() { let imgElem1; for (let i = 1; i < sceneInfo[0].values.videoImageCount; i++) { let filenameNumber = i; let filename = ''; if ( filenameNumber < 10) { filename = '000' + filenameNumber; } else if ( filenameNumber < 100) { filename = '00' + filenameNumber; } else if (filenameNumber >= 100 && filenameNumber < 1000) { filename = '0' + filenameNumber; } else { filename = String(filenameNumber); } imgElem1 = new Image(); imgElem1.src = `./video/chapter1/img_${filename}.jpg`; sceneInfo[0].objs.videoImages.push(imgElem1); // 24프레임 } let imgElem2; for (let i = 1; i < sceneInfo[2].values.videoImageCount; i++) { let filenameNumber = i; let filename = ''; if ( filenameNumber < 10) { filename = '000' + filenameNumber; } else if ( filenameNumber < 100) { filename = '00' + filenameNumber; } else if (filenameNumber >= 100 && filenameNumber < 1000) { filename = '0' + filenameNumber; } else { filename = String(filenameNumber); } imgElem2 = new Image(); imgElem2.src = `./video/chapter2/img_${filename}.jpg`; sceneInfo[2].objs.videoImages.push(imgElem2); // 24프레임 } let imgElem3; for (let i = 1; i < sceneInfo[4].values.videoImageCount; i++) { let filenameNumber = i; let filename = ''; if ( filenameNumber < 10) { filename = '000' + filenameNumber; } else if ( filenameNumber < 100) { filename = '00' + filenameNumber; } else if (filenameNumber >= 100 && filenameNumber < 1000) { filename = '0' + filenameNumber; } else { filename = String(filenameNumber); } imgElem3 = new Image(); imgElem3.src = `./video/chapter3/img_${filename}.jpg`; sceneInfo[4].objs.videoImages.push(imgElem3); // 24프레임 } } function setLayout() { // 각 스크롤 섹션의 높이 세팅 for (let i = 0; i < sceneInfo.length; i++) { if (sceneInfo[i].type === 'sticky') { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; //각섹션 스크롤 총 높이 = hightnum * 뷰포트 높이 } 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[0].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; sceneInfo[2].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; sceneInfo[4].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; } function calcValues(values, currentYOffset) { let rv; // 현재 씬 (스크롤섹션)에서 스크롤된 범위를 비율로 구하기 const scrollHeight = sceneInfo[currentScene].scrollHeight; let scrollRatio = currentYOffset / scrollHeight; if (values.length === 3) { // start ~ end 사이에 애니메이션 실행 const partScrollStart = values[2].start * scrollHeight; const partScrollEnd = values[2].end * scrollHeight; const partScrollHeight = partScrollEnd - partScrollStart; if (currentYOffset >= partScrollStart && currentYOffset <= partScrollEnd) { rv =(currentYOffset - partScrollStart) / partScrollHeight * (values[1] - values[0]) + values[0]; } else if ( currentYOffset < partScrollStart) { rv = values[0]; } else if ( currentYOffset > partScrollEnd) { rv = values[1]; } } else { rv = scrollRatio * (values[1] - values[0]) + values[0]; } return rv; } function playAnimation() { const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; const currentYOffset = yOffset - prevScrollHeight; //섹션에 현재 yoffset 값 const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; switch (currentScene) { case 0 : // let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); //math 반올림 처리 // objs.context.drawImage(objs.videoImages[sequence], 0, 0); objs.canvas.style.opacity = calcValues(values.canvas_opacity, currentYOffset); break; case 1 : break; case 2 : // let sequence2 = Math.round(calcValues(values.imageSequence, currentYOffset)); //math 반올림 처리 // objs.context.drawImage(objs.videoImages[sequence2], 0, 0); if (scrollRatio <= 0.3) { // in objs.canvas.style.opacity = calcValues(values.canvas_opacity_in, currentYOffset); } else { // out objs.canvas.style.opacity = calcValues(values.canvas_opacity_out, currentYOffset); } break; case 3 : break; case 4 : // let sequence4 = Math.round(calcValues(values.imageSequence, currentYOffset)); //math 반올림 처리 // objs.context.drawImage(objs.videoImages[sequence4], 0, 0); if (scrollRatio <= 0.3) { // in objs.canvas.style.opacity = calcValues(values.canvas_opacity_in, currentYOffset); } else { // out objs.canvas.style.opacity = calcValues(values.canvas_opacity_out, currentYOffset); } break; case 5 : break; } } function scrollLoop() { enterNewScene = false; prevScrollHeight = 0; for (let i = 0; i < currentScene; i++) { prevScrollHeight += sceneInfo[i].scrollHeight; } if (delayedYOffset < prevScrollHeight + sceneInfo[currentScene].scrollHeight) { document.body.classList.remove('scroll-effect-end'); } if (delayedYOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { enterNewScene = true; if (currentScene === sceneInfo.length - 1) { document.body.classList.add('scroll-effect-end'); } if (currentScene < sceneInfo.length - 1) { 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 === 0 || currentScene === 2 || currentScene === 4) { 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); } } } // 일부 기기에서 페이지 끝으로 고속 이동하면 body id가 제대로 인식 안되는 경우를 해결 // 페이지 맨 위로 갈 경우: 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); } // 페이지 맨 아래로 갈 경우: 마지막 섹션은 스크롤 계산으로 위치 및 크기를 결정해야할 요소들이 많아서 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('DOMContentLoaded', setLayout); window.addEventListener('load', () => { setLayout(); // 중간에 새로고침 시, 콘텐츠 양에 따라 높이 계산에 오차가 발생하는 경우를 방지하기 위해 before-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', () => { yOffset = window.pageYOffset; scrollLoop(); // checkMenu(); if (!rafState) { rafId = requestAnimationFrame(loop); rafState = true; } }); window.addEventListener('resize', () => { if (window.innerWidth > 900) { window.location.reload(); } }); window.addEventListener('orientationchange', () => { scrollTo(0, 0); setTimeout(() => { window.location.reload(); }, 500); }); // document.querySelector('.loading').addEventListener('transitionend', (e) => { // document.body.removeChild(e.currentTarget); // }); }); setCanvasImages(); })(); 소스는 다음과 같습니다.
- 미해결애플 웹사이트 인터랙션 클론!
안녕하세요 ㅎㅎㅎㅎㅎ 질문 하나 있습니다 ㅠㅠㅠ
안녕하세요 ㅎㅎㅎㅎㅎ 좋은 강의 감사합니다!!!!! 저는 스타트업에서 AI개발하고있는데 웹개발에 관심이 생겨 이제 막 자바스크립트 기초 공부 완료했습니다 ㅎㅎ 사실 예전에 HTML이랑 CSS 기초 공부하고 다음으로 뭘 공부하면 좋을까 고민하던중에 애플홈페이지를 클론코딩한다는 글에 우와!!! 하고 결제했었는데 그동안 바빴어서.... 이제야 공부하기 시작했네요ㅋㅋ 근데 생각보다 많이 어렵네요 ㅠㅠㅠㅠ 도전정신을 가지고 공부하고 복습을 열심히 해야겠습니다!! 1분코딩님께 이런 질문 드려도 되는지 모르겠는데요ㅠㅠ 얼마전에 회사에서 급하게 홍보용페이지를 만들어야할 일이 생겨서 외주 맡겼었는데.. 외주로 받은 페이지를 제가 클론코딩해보면서 이 강의를 듣기 위한 기초체력을 먼저 만들어보고싶습니다. http://aims.kr/ 저희 홍보용페이지인데 이 페이지를 열면 바로 배경이 생기면서 회사 마크가 애니메이션으로 나오는데 이것도 인터렉션 효과로 만든건가요..? 제가 한번 구현해보고싶은데 제가 잘 몰라서 어떻게 검색해야되는지도 잘 모르겠습니다 ㅠㅠㅠㅠ 키워드라도 알 수 있을까요..? 읽어주셔서 감사합니다!!!!!
- 미해결애플 웹사이트 인터랙션 클론!
main.js 질문 있어요
main-add.js 에서 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)})`; } pinB, pinC의 scale을 조절하는 코드 objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; 이 부분이 if에만 있어도 잘 구동하는 것 같은데 if와 else에 두 번 적는 이유가 궁금합니다.
- 미해결애플 웹사이트 인터랙션 클론!
폰트사이즈에서 em/rem이 뭔지 궁금합니다
안녕하세요! 폰트사이즈를 px, em, (오늘 처음봤지만)rem 처럼 다양하게 쓰던데 이때 em과 rem이 뭔지 잘 모르겠어요 검색해봤는데도 잘 모르겠더라구요ㅠㅠㅎㅎ
- 미해결애플 웹사이트 인터랙션 클론!
강의에서 잠깐 나오는 desc 메시지 와 다르게 캔버스가 잠깐 보입니다..
현제 제가 section-1에서 2로 넘어갈때 위 처럼 캔버스와 main텍스트가 번쩍 하고 보입니다. 둘다 opacity를 줘도 해결되지 않고 자바스크립트 문제인 것 같은데 어디가 문제인지 도저히 모르겠습니다.. 아래에 제 자바스크립트 코드입니다. (() => { let yoffset = 0;//현재 스크롤된 높이 let prevScrollHeight = 0; // 이전 씬의 총 높이 let currentScene = 0;//현재 활성화된 씬 const sceneInfo=[ //0 { type: 'sticky', scrollHeight: 0, heightNum: 5, 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'), canvas: document.querySelector('#video-canvas-0'), context: document.querySelector('#video-canvas-0').getContext('2d'), videoImage: [] }, //모든 애니메이션 정보를 담음 values:{ //이미지의 갯수 videoImageCount : 300, imageSequence: [0,299], //message A messageA_opacity_in : [0,1,{ start : 0.1, end: 0.2 }], messageA_opacity_out : [1,0,{ start : 0.25, end: 0.3 }], messageA_translate_in: [20,0, { start: 0.1,end: 0.2 }], messageA_translate_out: [0,-20, { start: 0.25,end: 0.3 }], //message B messageB_opacity_in : [0,1,{ start : 0.3, end: 0.4 }], messageB_opacity_out : [1,0,{ start : 0.45, end: 0.5 }], messageB_translate_in: [20,0, { start: 0.3,end: 0.4 }], messageB_translate_out: [0,-20, { start: 0.45,end: 0.5 }], //message C messageC_opacity_in : [0,1,{ start : 0.5, end: 0.6 }], messageC_opacity_out : [1,0,{ start : 0.65, end: 0.7 }], messageC_translate_in: [20,0, { start: 0.5,end: 0.6 }], messageC_translate_out: [0,-20, { start: 0.65,end: 0.7 }], //message D messageD_opacity_in : [0,1,{ start : 0.7, end: 0.8 }], messageD_opacity_out : [1,0,{ start : 0.85, end: 0.9 }], messageD_translate_in: [20,0, { start: 0.7,end: 0.8 }], messageD_translate_out: [0,-20, { start: 0.85,end: 0.9 }], //canvas_Opacity canvas_opacity : [1,0,{ start:0.9, end: 1 }], } }, //1 { type: 'normal', scrollHeight: 0, objs: { container : document.querySelector('#scroll-section-1'), } }, //2 { type: 'sticky', scrollHeight: 0, heightNum: 5, objs: { container : document.querySelector('#scroll-section-2'), messageA: document.querySelector('#scroll-section-2 .main-message.a'), messageB: document.querySelector('#scroll-section-2 .desc-message.b'), messageC: document.querySelector('#scroll-section-2 .desc-message.c'), pinB: document.querySelector('#scroll-section-2 .b .pin'), pinC: document.querySelector('#scroll-section-2 .c .pin'), canvas: document.querySelector('#video-canvas-1'), context: document.querySelector('#video-canvas-1').getContext('2d'), videoImage: [] }, values: { //이미지의 갯수 videoImageCount : 960, imageSequence: [0,959], //messageA messageA_opacity_in: [0,1,{ start: 0.25, end: 0.3 }], messageA_opacity_out: [1,0,{ start: 0.4, end: 0.45 }], messageA_translate_in: [20,0,{ start: 0.15, end: 0.2 }], messageA_translate_out: [0,-20,{ start: 0.4, end: 0.45 }], //messageB messageB_opacity_in: [0,1,{ start: 0.6, end: 0.65 }], messageB_opacity_out: [1,0,{ start: 0.68, end: 0.73 }], messageB_translate_in: [30,0,{ start: 0.6, end: 0.65 }], messageB_translate_out: [0,-20,{ start: 0.68, end: 0.73 }], //messageC messageC_opacity_in: [0,1,{ start: 0.87, end: 0.92 }], messageC_opacity_out: [1,0,{ start: 0.95, end: 1 }], messageC_translate_in: [30,0,{ start: 0.87, end: 0.92 }], messageC_translate_out: [0,-20,{ start: 0.95, end: 1 }], //pin pinB_scaleY: [0.5, 1, { start: 0.6, end: 0.65 }], pinC_scaleY: [0.5, 1, { start: 0.87, end: 0.92 }], //canvas_Opacity canvas_opacity_in: [0, 1, { start: 0, end: 0.1 }], canvas_opacity_out: [1, 0, { start: 0.95, end: 1 }], }, }, //3 { type: 'sticky', scrollHeight: 0, heightNum: 5, objs: { container : document.querySelector('#scroll-section-3'), } }, ] function setCanvasImages(){ let imgElem; let imgElem2; for(let i =0; i < sceneInfo[0].values.videoImageCount; i++){ //이미지 객체 새성 imgElem = new Image; imgElem.src = `./video/001/IMG_${6726 + i}.JPG`; sceneInfo[0].objs.videoImage.push(imgElem); } for(let i =0; i < sceneInfo[2].values.videoImageCount; i++){ //이미지 객체 새성 imgElem2 = new Image; imgElem2.src = `./video/002/IMG_${7027 + i}.JPG`; sceneInfo[2].objs.videoImage.push(imgElem2); } } setCanvasImages(); function setLayout(){ const heightRatio = window.innerHeight / 1080; //각 스크롤 섹션의 높이 세팅 for(let i =0; i < sceneInfo.length; i++){ if( sceneInfo[i].type === 'sticky' ){ sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; }else{ sceneInfo[i].scrollHeight = sceneInfo[i].objs.container.offsetHeight; } sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; } let totalScrollHeight = 0; yoffset = window.pageYOffset; for(let i =0; i < sceneInfo.length; i++){ totalScrollHeight += sceneInfo[i].scrollHeight; if( totalScrollHeight >= yoffset ){ currentScene = i; break; } } document.querySelector('body').id = `show-scene-${currentScene}` sceneInfo[0].objs.canvas.style.transform = `translate3d(-50%,-50%,0) scale(${heightRatio})`; sceneInfo[2].objs.canvas.style.transform = `translate3d(-50%,-50%,0) scale(${heightRatio})`; } //values = 값 변화의 시작과 끝, 현재 씬의 높이 비율 currentYoffset = 현재 씬의 스크롤한 높이 //scrollRatio = 현재 씬의 스크롤 비율 function calcValues(values,currnetYoffset){ let rv; const scrollHeight = sceneInfo[currentScene].scrollHeight; let scrollRatio = currnetYoffset / scrollHeight; //start와 end의 원소가 있는 배열의 적용 if( values.length === 3 ){ const partScrollStart = values[2].start * scrollHeight; const partScrollEnd = values[2].end * scrollHeight; const partScrollHeight = partScrollEnd - partScrollStart; if( currnetYoffset >= partScrollStart && currnetYoffset <= partScrollEnd ){ rv = (currnetYoffset - partScrollStart) / partScrollHeight * (values[1] - values[0]) + values[0]; }else if( currnetYoffset < partScrollStart ){ rv = values[0]; }else if(currnetYoffset > partScrollEnd){ rv = values[1]; } }else{ rv = scrollRatio * (values[1] - values[0]) + values[0]; } return rv; } function playAnimation(){ const values = sceneInfo[currentScene].values; const objs = sceneInfo[currentScene].objs; const currnetYoffset = yoffset - prevScrollHeight; const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currnetYoffset / scrollHeight; if( currnetYoffset < 0 ) return; switch (currentScene){ case 0: let sequence = parseInt(calcValues(values.imageSequence,currnetYoffset)); objs.context.drawImage(objs.videoImage[sequence],0,0); objs.canvas.style.opacity = calcValues(values.canvas_opacity,currnetYoffset); if( scrollRatio <= 0.22 ){ objs.messageA.style.opacity = calcValues(values.messageA_opacity_in,currnetYoffset); objs.messageA.style.transform = `translateY(${calcValues(values.messageA_translate_in,currnetYoffset)}%)`; }else{ objs.messageA.style.opacity = calcValues(values.messageA_opacity_out,currnetYoffset); objs.messageA.style.transform = `translateY(${calcValues(values.messageA_translate_out,currnetYoffset)}%)`; } if( scrollRatio <= 0.42 ){ objs.messageB.style.opacity = calcValues(values.messageB_opacity_in,currnetYoffset); objs.messageB.style.transform = `translateY(${calcValues(values.messageB_translate_in,currnetYoffset)}%)`; }else{ objs.messageB.style.opacity = calcValues(values.messageB_opacity_out,currnetYoffset); objs.messageB.style.transform = `translateY(${calcValues(values.messageB_translate_out,currnetYoffset)}%)`; } if( scrollRatio <= 0.62 ){ objs.messageC.style.opacity = calcValues(values.messageC_opacity_in,currnetYoffset); objs.messageC.style.transform = `translateY(${calcValues(values.messageC_translate_in,currnetYoffset)}%)`; }else{ objs.messageC.style.opacity = calcValues(values.messageC_opacity_out,currnetYoffset); objs.messageC.style.transform = `translateY(${calcValues(values.messageC_translate_out,currnetYoffset)}%)`; } if( scrollRatio <= 0.82 ){ objs.messageD.style.opacity = calcValues(values.messageD_opacity_in,currnetYoffset); objs.messageD.style.transform = `translateY(${calcValues(values.messageD_translate_in,currnetYoffset)}%)`; }else{ objs.messageD.style.opacity = calcValues(values.messageD_opacity_out,currnetYoffset); objs.messageD.style.transform = `translateY(${calcValues(values.messageD_translate_out,currnetYoffset)}%)`; } break; case 1: break; case 2: let sequence2 = parseInt(calcValues(values.imageSequence,currnetYoffset)); objs.context.drawImage(objs.videoImage[sequence2],0,0); if (scrollRatio <= 0.5) { // in objs.canvas.style.opacity = calcValues(values.canvas_opacity_in, currnetYoffset); } else { // out objs.canvas.style.opacity = calcValues(values.canvas_opacity_out, currnetYoffset); } if( scrollRatio <= 0.32 ){ objs.messageA.style.opacity = calcValues(values.messageA_opacity_in,currnetYoffset); objs.messageA.style.transform = `translateY(${calcValues(values.messageA_translate_in,currnetYoffset)}%)`; }else{ objs.messageA.style.opacity = calcValues(values.messageA_opacity_out,currnetYoffset); objs.messageA.style.transform = `translateY(${calcValues(values.messageA_translate_out,currnetYoffset)}%)`; } if( scrollRatio <= 0.67 ){ objs.messageB.style.opacity = calcValues(values.messageB_opacity_in,currnetYoffset); objs.messageB.style.transform = `translateY(${calcValues(values.messageB_translate_in,currnetYoffset)}%)`; }else{ objs.messageB.style.opacity = calcValues(values.messageB_opacity_out,currnetYoffset); objs.messageB.style.transform = `translateY(${calcValues(values.messageB_translate_out,currnetYoffset)}%)`; } if( scrollRatio <= 0.93 ){ objs.messageC.style.opacity = calcValues(values.messageC_opacity_in,currnetYoffset); objs.messageC.style.transform = `translateY(${calcValues(values.messageC_translate_in,currnetYoffset)}%)`; }else{ objs.messageC.style.opacity = calcValues(values.messageC_opacity_out,currnetYoffset); objs.messageC.style.transform = `translateY(${calcValues(values.messageC_translate_out,currnetYoffset)}%)`; } break; case 3: break; } } function scrollLoop(){ prevScrollHeight = 0; for(let i=0; i < currentScene; i++){ prevScrollHeight += sceneInfo[i].scrollHeight; } if( yoffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight){ currentScene++; document.querySelector('body').id = `show-scene-${currentScene}` } if( yoffset < prevScrollHeight ){ if( currentScene === 0 ) return; currentScene--; document.querySelector('body').id = `show-scene-${currentScene}` } } //윈도우의 창 사이즈 변경시 다시 높이를 세팅함 window.addEventListener('scroll',() => { yoffset = window.pageYOffset; scrollLoop(); playAnimation(); }); window.addEventListener('resize',setLayout); window.addEventListener('load', () => { setLayout(); sceneInfo[0].objs.context.drawImage(sceneInfo[0].objs.videoImage[0],0,0); }); })()
- 미해결애플 웹사이트 인터랙션 클론!
가로로 길 시 검은색 공백
16:9보다 가로로 더 긴 화면에서는 검은색이 노출되는데 정상인가요?
- 미해결애플 웹사이트 인터랙션 클론!
선색님 혹시 id는 body말고 container에 줘도 괜찮을까요?
선생님 안녕하세요 :)여러 페이지를 만들때 body에 무엇을 할당할경우 영향을 최소화 하기위해(물론 영향은 없을것같습니다만.. ㅠㅠ) container에 show-scene-0같은 아이디를 줘도 괜찮을까요?
- 미해결애플 웹사이트 인터랙션 클론!
질문있습니다
//컨버스 사이즈에 맞춰 가정한 innerWidth와 innerHeight //원래 비율 찾기 const recalculatedInnerWidth = window.innerWidth / canvasScaleRation; console.log(recalculatedInnerWidth); const recalculatedInnerHeight = window.innerHeight / canvasScaleRation; //캔버스 영역에서 하얀색 캔버스 box그리기 15%짜리 const whiteRectWidth = recalculatedInnerWidth * 0.15; //0번은 출발값 (박스들이 처음 setting 된 값) values.rect1X[0] = (objs.canvas.width - recalculatedInnerWidth) / 2; console.log(recalculatedInnerWidth); //애니메이션이 끝났을 때 최종 값 values.rect1X[1] = values.rect1X[0] - whiteRectWidth; 안녕하세요 해당 코드에서 values.rect1X[0] 를 찍어봤는데 0이 나오더라구요 원래 0이 나오는게 맞나요? 확인해보니 개발자도구를 아래로 놓으면 0이 나오고 옆으로 놓으면 숫자가 출력되는데 무슨 차이인가요 ?! 지금 15% 들어가있는게 파란색 박스인가요? 이휴를 정확하게 모르겠네요 시작점이 0부터 시작한다는 말 인가요?