22%
59,400원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결애플 웹사이트 인터랙션 클론!
초기화 스크롤 에러 질문
브라우저 로딩 시점에서 js부분에서 이벤트를 따로 load에 넣어주지 않아도 body css에 overflow hidden 만 줘도 에러가 나지 않는데 이렇게 해도 상관없는 걸까요? 아니면 에러가 눈에 보이지 않을뿐 load이벤트에 넣어주는게 좋은건가요? 둘의 차이가 궁금합니다.
- 미해결애플 웹사이트 인터랙션 클론!
캔버스 동작 오류
안녕하세요 강의 보면서 따라하던 도중 오류가 생겼는데 어떻게 해결을 해야할지 감이 안와서 질문 남깁니다.. 문제) case3 에서 캔버스가 그려지고 난후 clear가 되지 않아서 계속 겹쳐져서 그려짐 (아래 이미지 첨부) -> clearRect를 사용했더니 fillstyle이 적용안됨 ㅠㅠ 강의를 다시 돌려보면서 혹시 오타라도 났나 싶어 계속 오류를 찾아보고 있는데 발견을 못하겠습니다ㅠㅠ 이 경우에는 어떻게 접근해야 좋을까요? 아래 이미지는 검은색 영역이 그려지고 없어지지 않아서 이미지와 겹쳐있습니다. (이미지는png라서 배경을 따로 줬어요! 배경을 없애도 똑같네요 ㅠ) 근대 캔버스가 움직이는건 아무리 해도 모르겠네요 ㅠㅠ 참고! 가끔 아래서 새로고침후, 위로 올릴때만 정상적으로 동작합니다! 선생님 코드와 다른점 ) 영상을 사진으로 변화한것은 예제와 다른걸 썻는데 , 크기가 제각각이라 제가 임의로 heightRatio를 다르게 썻습니다. 그래서 스크롤을 빠르게 하거나 창크기가 바뀌면 하얀 부분이 나오는데 이거는 제가 다시 계산해서 할 수 있을꺼 같습니다. 그 외 나머지는 css가 조금 다른것 빼고 html, js부분은 똑같습니다. 아래쪽에 제 코드 첨부합니다 (() => { let yOffset = 0; //window.pageYOffset 대신 사용할 변수 let prevScrollHeight = 0; //현재 스크롤위치(yoffset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이의 합 let currentScene = 0; //현재활성화된 씬((지금 보고있는 스크린)(=scroll-section) let enterNewScene = false; //새로운 씬이 시작되는 순간 True (씬이 바뀔때 생기는 오류 방지변수) const sceneInfo = [ // 배열 객체는 4개를 만든다(섹션이 4개이기 때문) {//1 type:'sticky', //브라우저 높이의 5배로 scrollHeight 세팅 heightNum:5, // 스크롤높이 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: 313, imageSequence:[0, 312], canvasOpacity:[1,0,{start:0.9, end:1}], messageA_opacity_in:[0, 1, {start: 0.1, end: 0.2}], messageA_translateY_in:[20, 0, {start: 0.1, end: 0.2}], messageA_opacity_out:[1, 0, {start: 0.25, end: 0.3}], messageA_translateY_out:[0, -20, {start: 0.25, end: 0.3}], messageB_opacity_in:[0, 1, {start: 0.3, end: 0.4}], messageB_translateY_in:[20, 0, {start: 0.3, end: 0.4}], messageB_opacity_out:[1, 0, {start: 0.45, end: 0.5}], messageB_translateY_out:[0, -20, {start: 0.45, end: 0.5}], messageC_opacity_in:[0, 1, {start: 0.5, end: 0.6}], messageC_translateY_in:[20, 0, {start: 0.5, end: 0.6}], messageC_opacity_out:[1, 0, {start: 0.65, end: 0.7}], messageC_translateY_out:[0, -20, {start: 0.65, end: 0.7}], messageD_opacity_in:[0, 1, {start: 0.7, end: 0.8}], messageD_translateY_in:[20, 0, {start: 0.7, end: 0.8}], messageD_opacity_out:[1, 0, {start: 0.85, end: 0.95}], messageD_translateY_out:[0, -20, {start: 0.85, end: 0.95}], } }, {//2 type:'nomal', // heightNum:5 노말에서는 필요없음 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-2'), context: document.querySelector('#video-canvas-2').getContext('2d'), videoImages: [] }, values: { videoImageCount: 292, imageSequence:[0, 291], canvasOpacity_in:[0,1,{start:0, end:0.1}], canvasOpacity_out:[1,0,{start:0.9, end:1}], messageA_opacity_in: [0, 1, { start: 0.15, end: 0.2 }], messageA_translateY_in: [20, 0, { start: 0.15, end: 0.2 }], messageA_opacity_out: [1, 0, { start: 0.3, end: 0.35 }], messageA_translateY_out: [0, -20, { start: 0.3, end: 0.35 }], messageB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], messageB_translateY_in: [50, 30, { start: 0.5, end: 0.55 }], messageB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], messageB_translateY_out: [30, 0, { start: 0.58, end: 0.63 }], messageC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], messageC_translateY_in: [50, 30, { start: 0.72, end: 0.77 }], messageC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], messageC_translateY_out: [30, 0, { start: 0.85, end: 0.9 }], pinB_scaleY: [0.5, 1, { start: 0.5, end: 0.55 }], pinC_scaleY: [0.5, 1, { start: 0.72, end: 0.77 }], pinB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], pinC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], pinB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], pinC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }] } }, {//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'), imagePath: [ './images/mecolro.PNG', './images/IMG_1667.JPG' ], images: [], }, values: { rect1X: [0, 0, {start:0, end: 0}], rect2X: [0, 0, {start:0, end: 0}], rectStartY: 0, } } ]; function setCanvasImage(){ let imgElem; for (let i = 0; i < sceneInfo[0].values.videoImageCount; i++){ imgElem = new Image(); imgElem.src = `/video/myhome_30frame_4k/frame_${1 + i}.jpg`; sceneInfo[0].objs.videoImages.push(imgElem); }; let imgElem2; for (let i = 0; i < sceneInfo[2].values.videoImageCount; i++){ imgElem2 = new Image(); imgElem2.src = `/video/logo_30frame/frame_${1 + i}.jpg`; sceneInfo[2].objs.videoImages.push(imgElem2); }; let imgElem3; for (let i = 0; i < sceneInfo[3].objs.imagePath.length; i++){ imgElem3 = new Image(); imgElem3.src = sceneInfo[3].objs.imagePath[i]; sceneInfo[3].objs.images.push(imgElem3); }; }; setCanvasImage(); 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 == 'nomal'){ 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}`); // console.log(sceneInfo); let heightRatio = sceneInfo[currentScene].scrollHeight / 2160 / 3; // if(heightRatio > 0.7){heightRatio = 0.7;}; sceneInfo[0].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; sceneInfo[2].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; // console.log(currentScene); // console.log(heightRatio); }; function calcValues(values, currentYOffset){ let rv; const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; //star와 end의 애니메이션 실행 if(values.length == 3){ const partScrollStart = values[2].start * scrollHeight; const partScrollEnd = values[2].end * scrollHeight; const partScrollHeight = partScrollEnd - partScrollStart; if(currentYOffset >= partScrollStart && currentYOffset <= partScrollEnd){ rv = (currentYOffset - partScrollStart) / partScrollHeight * (values[1] - values[0]) + values[0]; } else if (currentYOffset < partScrollStart){ rv = values[0]; } else if (currentYOffset > partScrollEnd){ rv = values [1]; } } else { rv = scrollRatio * (values[1] - values[0]) + values[0]; } return rv; } function playAnimation(){ const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; const currentYOffset = yOffset - prevScrollHeight; const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; switch (currentScene){ case 0: let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); objs.context.drawImage(objs.videoImages[sequence],0 ,0); objs.canvas.style.opacity = calcValues(values.canvasOpacity, currentYOffset); if(scrollRatio <= 0.22){ objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else{ 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){ objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; } else{ 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){ objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; } else{ 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){ objs.messageD.style.opacity = calcValues(values.messageD_opacity_in, currentYOffset); objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_in, currentYOffset)}%, 0)`; } else{ objs.messageD.style.opacity = calcValues(values.messageD_opacity_out, currentYOffset); objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_out, currentYOffset)}%, 0)`; } break; case 1: // 노말타입이라 동작을 안하므로 지워도 되지만 미관상 놔둔다 break; case 2: let sequence2 = Math.round(calcValues(values.imageSequence, currentYOffset)); objs.context.drawImage(objs.videoImages[sequence2],0 ,0); if(scrollRatio <= 0.5){ objs.canvas.style.opacity = calcValues(values.canvasOpacity_in, currentYOffset); } else{ objs.canvas.style.opacity = calcValues(values.canvasOpacity_out, currentYOffset); } if(scrollRatio <= 0.22){ objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else{ 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.57){ objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } else{ objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } if(scrollRatio <= 0.83){ objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } else{ objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } // case3에서 그려질 canvas를 case2에서 미리 동작하게 만들어주자 (코드는 반복, 수치만 살짝 바꿈) if(scrollRatio > 0.9){ // case2에서 사용되는 objs와 values값이 다르지만 if문을 통해 영역을 설정했으므로 const로 다시 선언해준다! const objs = sceneInfo[3].objs; const values = sceneInfo[3].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); const canvasCalcWidth = document.body.offsetWidth / canvasScaleRatio; const whiteRectWidth = canvasCalcWidth * 0.2; values.rect1X[0] = (objs.canvas.width - canvasCalcWidth) / 2; values.rect1X[1] = values.rect1X[0] - whiteRectWidth; values.rect2X[0] = values.rect1X[0] + canvasCalcWidth - whiteRectWidth; values.rect2X[1] = values.rect2X[0] + whiteRectWidth; // 시작위치는 초기값으로 설정 (values.rectnX[0]) 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 3: 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 = 'black'; objs.context.drawImage(objs.images[0],0 ,0); // 캔버스 사이즈에 맞춰 가정한 innerwidth와 innerheight const canvasCalcWidth = document.body.offsetWidth / canvasScaleRatio; const canvasCalcHeight = window.innerHeight / canvasScaleRatio; if(!values.rectStartY){ 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 = canvasCalcWidth * 0.2; values.rect1X[0] = (objs.canvas.width - canvasCalcWidth) / 2; values.rect1X[1] = values.rect1X[0] - whiteRectWidth; values.rect2X[0] = values.rect1X[0] + canvasCalcWidth - 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 ); console.log(parseInt(calcValues(values.rect1X, currentYOffset))); //캔버스가 브라우저 상단에 닿은 경우부터 이미지 블랜드된 후 다시 스크롤되는 것을 3단계 step으로 나눈다 if(scrollRatio < values.rect1X[2].end / 9){ //캔버스가 브라우저 상단에 닿지 않은 단계 step = 1; console.log('before') objs.canvas.classList.remove('sticky'); } else { //이미지가 블랜드되고 스케일이 조정되는 단계까지 step = 2; console.log('after') objs.canvas.classList.add('sticky'); objs.canvas.style.top = `${-(objs.canvas.height - objs.canvas.height * canvasScaleRatio)/2}px`; // if(){ // //스케일 조정되고 다시 스크롤 되는 단계 // step = 3; // }; }; break; } // console.log(currentScene); } 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('scroll',() => { yOffset = window.pageYOffset; scrollLoop(); }); window.addEventListener('load', () => { setLayout(); sceneInfo[0].objs.context.drawImage(sceneInfo[0].objs.videoImages[0],0 ,0); }); //DOMContentLoaded 도 많이사용 (이미지가 로딩안되도 로딩시키는 매서드) 하지만 여기서는 이미지가 중요하기 때문에 로드를 사용한다 window.addEventListener('resize',setLayout); })();
- 미해결애플 웹사이트 인터랙션 클론!
canvas 너비를 화면 꽉 차게 하려면 어떻게 해야 할까요?
위의 사진과 같이 2000px 혹은 그 이상의 넓은 화면에서 보면 양 옆에 보기 싫게 흰 너비가 생기게 됩니다. 이걸 어떻게 하면 없앨 수 있을까요? 아예 동영상을 렌더링 할 시 넓게 해봤더니 여전히 안되더군요.
- 해결됨애플 웹사이트 인터랙션 클론!
즉시 실행 함수 안의 변수 접근
안녕하세요 선생님. 스크롤 높이 세팅 강의의 4:33분에서 전역변수가 되지 않게 하기 위해 즉시호출 함수를 실행하게 한다고 해주셨는데요, 그럼 혹시 디버깅 등을 위해서 콘솔에서 (()=>{})(); 안의 변수의 값을 확인하고 싶을 때는 어떻게 할 수 있나요?? + 추후 수업을 들으면서, function 자체내에 console.log를 넣으면 되는 부분을 알게되었습니다. 감사합니다.
- 해결됨애플 웹사이트 인터랙션 클론!
canvas에 사진이 출력되지 않습니다
해당 강의를 바탕으로 따로 페이지 하나를 제작하고 있는데 사진은 지정이 되지만 사진이 출력되지 않네요 ㅠ 그리고 Uncaught TypeError : Cannot read properties of undefined (reading @@@) 에러가 length랑 style에 뜨는데 원인과 해결방법을 알 수 있을까요? -> 이 부분은 해결했습니다. main.js 코드입니다 + canvas를 통해 (머그컵과 같은) video 모션 이미지들이 들어가는 곳은 section 2,3,4 입니다. (() => { // 변수 모음 let yOffset = 0; // window.pageYOffset 대신 쓸 변수 let prevScrollHeight; // 현재 스크롤 위치 이전의 섹션들의 높이 합 let currentScene = 0; // 현재 보고 있는 씬 scroll-section 몇 번째인지 let enterNewScene = false; // 새로운 세션 시작된 순간 true const sceneInfo = [ { // section 0 - 인트로 type: "sticky", heightNum: 5, // 브라우저 높이의 5배로 세팅 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 }], }, }, { // section 1 - 서비스 리스트 type: "normal", // heightNum: 5, // type normal에서는 필요 없음 scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-1"), content: document.querySelector("#scroll-section-1 .description"), }, }, { // section 2 - 공개행정 type: "sticky", heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-2"), // messageABCD canvas: document.querySelector("#video-canvas-2"), context: document.querySelector("#video-canvas-2").getContext("2d"), videoImages: [], }, values: { videoImageCount: 14, imageSequence: [0, 13], canvas_opacity: [1, 0, { start: 0.9, end: 1 }], canvas_opacity_out: [1, 0, { start: 0.95, end: 1 }], // ,assage opacity, translate }, }, { // section 3 - 참여행정 type: "sticky", heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-3"), // messageABCD canvas: document.querySelector("#video-canvas-3"), //content: document.querySelector("#scroll-section-3 .description"), context: document.querySelector("#video-canvas-3").getContext("2d"), videoImages: [], }, values: { videoImageCount: 15, imageSequence: [0, 14], canvas_opacity: [1, 0, { start: 0.9, end: 1 }], canvas_opacity_out: [1, 0, { start: 0.95, end: 1 }], // ,assage opacity, translate }, }, { // section 4 - 능률행정 type: "sticky", heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-4"), // messageABCD canvas: document.querySelector("#video-canvas-4"), //content: document.querySelector("#scroll-section-4 .description"), //context: document.querySelector("#video-canvas-4").getContext("2d"), videoImages: [], }, values: { videoImageCount: 12, imageSequence: [0, 11], canvas_opacity: [1, 0, { start: 0.9, end: 1 }], canvas_opacity_out: [1, 0, { start: 0.95, end: 1 }], // ,assage opacity, translate }, }, { // section 5 - 마무리 type: "sticky", heightNum: 5, // 브라우저 높이의 5배로 세팅 scrollHeight: 0, objs: { container: document.querySelector("#scroll-section-5"), messageA: document.querySelector("#scroll-section-5 .main-message.a"), messageB: document.querySelector("#scroll-section-5 .main-message.b"), messageC: document.querySelector("#scroll-section-5 .main-message.c"), messageD: document.querySelector("#scroll-section-5 .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 }], }, }, ]; function setCanvasImages() { let imgElem1; for (let i = 0; i < sceneInfo[2].values.videoImageCount; i++) { imgElem1 = new Image(); imgElem1.src = `./video/section2_file/section2IMG_${i}.JPG`; sceneInfo[2].objs.videoImages.push(imgElem1); } let imgElem2; for (let i = 0; i < sceneInfo[3].values.videoImageCount; i++) { imgElem2 = new Image(); imgElem2.src = `./video/section3_file/section3IMG_${i}.JPG`; sceneInfo[3].objs.videoImages.push(imgElem2); } let imgElem3; for (let i = 0; i < sceneInfo[4].values.videoImageCount; i++) { imgElem3 = new Image(); imgElem3.src = `./video/section4_file/section4IMG_${i}.JPG`; sceneInfo[4].objs.videoImages.push(imgElem3); } } 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; } // 섹션의 높이를 html에 세팅 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[2].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; } // 값이 변화되는 걸 계산 // currentYOffset - 현재 섹션에서 얼마나 scroll되었는지 비율 계산 function calcValues(values, currentYOffset) { let rv; // return value 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]; // 0~1 -> 범위 확장 return rv; } return rv; } // 애니메이션 진행 처리 함수 function playAnimation() { const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; const currentYOffset = yOffset - prevScrollHeight; // 현재 섹션에서 위치 const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; //console.log(currentScene, currentYOffset); switch (currentScene) { case 0: // 인트로 // console.log('0 play'); // let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); // objs.context.drawImage(objs.videoImages[sequence], 0, 0); if (scrollRatio <= 0.22) { // in objs.messageA.style.opacity = calcValues( values.messageA_opacity_in, currentYOffset ); objs.messageA.style.transform = `translate3d(0, ${calcValues( values.messageA_translateY_in, currentYOffset )}%, 0)`; } else { // out objs.messageA.style.opacity = calcValues( values.messageA_opacity_out, currentYOffset ); objs.messageA.style.transform = `translate3d(0, ${calcValues( values.messageA_translateY_out, currentYOffset )}%, 0)`; } if (scrollRatio <= 0.42) { // in objs.messageB.style.opacity = calcValues( values.messageB_opacity_in, currentYOffset ); objs.messageB.style.transform = `translate3d(0, ${calcValues( values.messageB_translateY_in, currentYOffset )}%, 0)`; } else { // out objs.messageB.style.opacity = calcValues( values.messageB_opacity_out, currentYOffset ); objs.messageB.style.transform = `translate3d(0, ${calcValues( values.messageB_translateY_out, currentYOffset )}%, 0)`; } if (scrollRatio <= 0.62) { // in objs.messageC.style.opacity = calcValues( values.messageC_opacity_in, currentYOffset ); objs.messageC.style.transform = `translate3d(0, ${calcValues( values.messageC_translateY_in, currentYOffset )}%, 0)`; } else { // out objs.messageC.style.opacity = calcValues( values.messageC_opacity_out, currentYOffset ); objs.messageC.style.transform = `translate3d(0, ${calcValues( values.messageC_translateY_out, currentYOffset )}%, 0)`; } if (scrollRatio <= 0.82) { // in objs.messageD.style.opacity = calcValues( values.messageD_opacity_in, currentYOffset ); objs.messageD.style.transform = `translate3d(0, ${calcValues( values.messageD_translateY_in, currentYOffset )}%, 0)`; } else { // out objs.messageD.style.opacity = calcValues( values.messageD_opacity_out, currentYOffset ); objs.messageD.style.transform = `translate3d(0, ${calcValues( values.messageD_translateY_out, currentYOffset )}%, 0)`; } break; case 1: // 리스트 break; case 2: // 공개행정 objs.canvas.style.opacity = calcValues( values.canvas_opacity, currentYOffset ); 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.25) { // 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.57) { // 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.83) { // 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: // 참여행정 objs.canvas.style.opacity = calcValues( values.canvas_opacity, currentYOffset ); if (scrollRatio <= 0.25) { // 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.57) { // 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.83) { // 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 4: // 능률 행정 objs.canvas.style.opacity = calcValues( values.canvas_opacity, currentYOffset ); if (scrollRatio <= 0.25) { // 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.57) { // 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.83) { // 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 5: // 마무리 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; } } // "현재 활성시킬 스크롤 씬(세션) 결정하기" // 몇 번째 스크롤 섹션 진행중인지 check // 앞파트 스크롤 섹션의 height 합으로 계산 가능 function scrollLoop() { enterNewScene = false; prevScrollHeight = 0; // 값 누적 방지용 초기화 for (let i = 0; i < currentScene; i++) { prevScrollHeight += sceneInfo[i].scrollHeight; } if (yOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { enterNewScene = true; currentScene++; document.body.setAttribute("id", `show-scene-${currentScene}`); // currentScene에 맞춰서 body에 하나씩 세팅된다 } if (yOffset < prevScrollHeight) { enterNewScene = true; if (currentScene == 0) return; // 브라우저 바운스 효과로 음수 에러 방지 currentScene--; document.body.setAttribute("id", `show-scene-${currentScene}`); // currentScene에 맞춰서 body에 하나씩 세팅된다 } if (enterNewScene) return; playAnimation(); //console.log(currentScene); // 몇 번째 섹션인지 } //window.addEventListener("resize", setLayout); // height에러 check window.addEventListener("scroll", () => { yOffset = window.pageYOffset; //console.log(yOffset); // 스크롤 페이지 값 scrollLoop(); }); // 새로고침 window.addEventListener("load", setLayout); window.addEventListener("resize", setLayout); setLayout(); setCanvasImages(); })();
- 미해결애플 웹사이트 인터랙션 클론!
load함수 관련 궁금합니다.
(() => { })() 현재 코드를 즉시 실행 함수 안에서 작성하고 있는데 즉시 실행 함수가 없이 바로 써도 문제없이 작동 하는거 같은데 왜 저 안에서 쓰는지 궁금합니다.
- 미해결애플 웹사이트 인터랙션 클론!
강의 잘보았습니다 간단한 질문이있는데요!
버그수정 3번에서 강의하신 텍스트 색상이 알록달록하게 변경되어 시인성이 좋게보이는데 이런건 어떻게하는걸까요?? 1코님이랑 똑같이하려면 어떤 익스텐션을 설치해야하는지 궁금합니다! eslint랑 프리티어를 같이쓰고있는데도 문법시인성이 잘눈에 안뛰네요 ㅜㅜ 추가로 Netlify 에서 무료호스팅을 이용해 만든 웹에접속했더니 로딩속도가 애플과 다르게 30초정도 걸립니다 지금 바로 해결은 못하더라도 애플과 로딩시간이 다른 원인은 알고싶네요 현재스크롤 섹션부분만 디스플레이하는 최적화까지 진행했는데 왜 로딩속도에서 큰차이가 날까요?
- 미해결애플 웹사이트 인터랙션 클론!
질문있습니다 ㅠㅠ
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; } 해석 scrollHeight : 지금 현재 씬의 높이 지금 현재씬 높이 /현재 활성화된 씬의 비율 만약 values 값이 3개라면 ex) example : [0, 1, {start : 0.1, end : 0.2}] partScroll start,end(비율..) 는 scrollHeight(지금 현재 씬의 높이)를 곱함 partScrollHeight : start와 end의 중간 값 만약 현재 스크롤 위치가 스타트 위치보다 위에 있고 스크롤 위치가 end보다 작은곳에 있다면? 현재 스크롤 위치 빼기 시작 위치 / (sceneInfo[0~3]의 높이) * (끝값 - 시작값) + 시작값 사이 값이 아닐때 (partScrollStart값보다 작을때)=> 시작값 사이 값이 아닐대(partScrollend 값보다 클때) => 끝값 value값이 3이 아니라면 ..? 이해 못함 정리하면서 공부하고 있는데.. value 값이 3이 아닌건 videoImageCount: 960, imageSequence: [0, 959], 이부분도 3이 아닌데.. 그러면 videoImageCount일때는 values[1]의 값은 무엇인가요?? 그리고 혹시 제가 설명한부분이 틀린부분있으면 고쳐주실수 있으실까요? 아 또 여쭈어볼게 있는데 if (delayedYOffset < prevScrollHeight + sceneInfo[currentScene].scrollHeight) { document.body.classList.remove('scroll-effect-end'); } scroll-effect-end가 바디 클래스 리스트에 붙는다고 하는데 아무리 찾아봐도 안붙는거 가아서요 이부분이 이해가 안가네요 ㅠㅠ 도와주세요
- 미해결애플 웹사이트 인터랙션 클론!
아이폰 스크롤 관련 질문있습니다.
아이폰에서는 스크롤을 내릴때는 상관 없지만 반대 방향으로 갈 경우 주소창이 나와 innerheight 가 달라지게 되는데요. 이때마다 height 가 다시 계산되는 것 같습니다. main-massage는 그때 처리를 해주셔서 height 가 달라져도 위치가 변하지 않는데, 뒤에 있는 이미지 시퀀스는 주소창이 올라올때마다 위치가 주소창 만큼 올라갑니다. 선생님이 주신 예제 파일에도 이와 같은 현상이 일어나고 있습니다. 문제의원인은 알았는데, 해결이 스스로 되지 않아 질문드립니다. 혹시 뒤에 이미지 시퀀스도 주소창에 따라 위치가 안변하게 조정할 수 있을까요?
- 미해결애플 웹사이트 인터랙션 클론!
선생님 질문이있습니다
저가 NEXT js 로 적용하려고하는데 방법은 같을까요 ?
- 해결됨애플 웹사이트 인터랙션 클론!
배경을 다른 색깔로 넣을 시 동영상 삽입
안녕하세요. 웹사이트 전체 배경색을 흰색이 아니라 검은색, 분홍색 등 다른 색깔로 했을 시 동영상 이미지들을 뽑아내려면 동영상 배경이 transparent하게 png로 전부 뽑아내야 하나요? 웹사이트 배경색이 흰색이 아닌 다른 색일 때 동영상 파일을 어떻게 rendering 해야 하는지 궁금합니다.
- 미해결애플 웹사이트 인터랙션 클론!
선생님 안녕하세요!
선생님 안녕하세요 canvas_opacity_out 실행되는 시점에 main.js:299 Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)'. at playAnimation (main.js:299) at scrollLoop (main.js:389) at main.js:400 에러가 출력 됩니다. 혼자 해결해보려고 완성본 파일도 찾아보고 했지만 해결이 어려워 질문 드립니다. 감사합니다. (() => { let yOffset = 0 // window.pageYOffset 대신 사용할 변수 let prevScrollHeight = 0 // 현재 스크롤 위치(yOffset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이값의 합 let currentScene = 0 // 현재 활성하된(눈 앞에 보고 있는 ) 화면 let enterNewScene = false // 새로운 scene이 시작되는 순간 false const sceneInfo = [ { // 0 type: 'sticky', heightNum : 5, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objects: { 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'), videoImages: [] }, values: { videoImageCount: 578, imageSequence: [0, 577], canvas_opacity: [1,0,{ start: 0.9 , end: 1}], messageA_opacity_in: [0, 1, { start: 0.05, end: 0.15 }], messageB_opacity_in: [0, 1, { start: 0.25, end: 0.29 }], messageC_opacity_in: [0, 1, { start: 0.45, end: 0.5 }], messageD_opacity_in: [0, 1, { start: 0.7, end: 0.85 }], 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.1, end: 0.2 }], messageB_opacity_out: [1, 0, { start: 0.3, end: 0.31}], messageC_opacity_out: [1, 0, { start: 0.55, end: 0.6 }], messageD_opacity_out: [1, 0, { start: 0.9, end: 1.0 }], 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, scrollHeight: 0, objects: { container: document.querySelector('#scroll-section-1'), content: document.querySelector('#scroll-section-1 .description') } }, { // 2 type: 'sticky', heightNum : 5, scrollHeight: 0, objects: { 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'), canvas: document.querySelector('#video-canvas-1'), context: document.querySelector('#video-canvas-1').getContext('2d'), videoImages: [] }, values: { videoImageCount: 622, imageSequence: [0, 621], canvas_opacity_in: [0,1,{ start: 0, end: 0.1}], canvas_opacity_out: [1,0,{ start: 0.9, end: 0.95}], messageA_translateY_in: [20, 0, { start: 0.15, end: 0.2 }], messageB_translateY_in: [30, 0, { start: 0.5, end: 0.55 }], messageC_translateY_in: [30, 0, { start: 0.72, end: 0.77 }], messageA_opacity_in: [0, 1, { start: 0.15, end: 0.2 }], messageB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], messageC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], messageA_translateY_out: [0, -20, { start: 0.3, end: 0.35 }], messageB_translateY_out: [0, -20, { start: 0.58, end: 0.63 }], messageC_translateY_out: [0, -20, { start: 0.85, end: 0.9 }], messageA_opacity_out: [1, 0, { start: 0.3, end: 0.35 }], messageB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], messageC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], pinB_scaleY: [0.5, 1, { start: 0.6, end: 0.65 }], pinC_scaleY: [0.5, 1, { start: 0.87, end: 0.92 }], pinB_opacity_in: [0, 1, { start: 0.6, end: 0.65 }], pinC_opacity_in: [0, 1, { start: 0.87, end: 0.92 }], pinB_opacity_out: [1, 0, { start: 0.68, end: 0.73 }], pinC_opacity_out: [1, 0, { start: 0.95, end: 1 }] } }, { // 3 type: 'sticky', heightNum : 5, scrollHeight: 0, objects: { container: document.querySelector('#scroll-section-3'), canvasCaption: document.querySelector('.canvas-caption') }, values: { } } ] const setCanvasImages = () => { let imgElem for( let i = 0; i < sceneInfo[0].values.videoImageCount; i++) { imgElem = new Image() imgElem.src =`./video/IMG_${5555 + i}.jpg` sceneInfo[0].objects.videoImages.push(imgElem) } let imgElem2 for( let i = 0; i < sceneInfo[0].values.videoImageCount; i++) { imgElem2 = new Image() imgElem2.src =`./video/IMG_${6134 + i}.jpg` sceneInfo[2].objects.videoImages.push(imgElem2) } } setCanvasImages() const setLayout = () => { // 각 스크롤 섹션의 높이 세팅 for( let i = 0; i < sceneInfo.length; i++ ) { if( sceneInfo[i].type === 'sticky') { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight sceneInfo[i].objects.container.style.height = `${sceneInfo[i].scrollHeight}px` } else if( sceneInfo[i].type === 'normal') { sceneInfo[i].scrollHeight = sceneInfo[i].objects.container.offsetHeight } sceneInfo[i].objects.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].objects.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})` sceneInfo[2].objects.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})` } const 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 } const playAnimation = () => { const objects = sceneInfo[currentScene].objects const values = sceneInfo[currentScene].values const currentYOffset = yOffset - prevScrollHeight const scrollHeight = sceneInfo[currentScene].scrollHeight const scrollRatio = currentYOffset / scrollHeight switch (currentScene) { case 0: let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)) objects.context.drawImage(objects.videoImages[sequence], 0, 0) objects.canvas.style.opacity = calcValues(values.canvas_opacity, currentYOffset) if (scrollRatio <= 0.22) { // in objects.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objects.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else { // out objects.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset); objects.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.42) { // in objects.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objects.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; } else { // out objects.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objects.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.62) { // in objects.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objects.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; } else { // out objects.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objects.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.82) { // in objects.messageD.style.opacity = calcValues(values.messageD_opacity_in, currentYOffset); objects.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_in, currentYOffset)}%, 0)`; } else { // out objects.messageD.style.opacity = calcValues(values.messageD_opacity_out, currentYOffset); objects.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_out, currentYOffset)}%, 0)`; } break case 2: let sequence2 = Math.round(calcValues(values.imageSequence, currentYOffset)) objects.context.drawImage(objects.videoImages[sequence2], 0, 0) if (scrollRatio <= 0.5) { // in objects.canvas.style.opacity = calcValues(values.canvas_opacity_in, currentYOffset) } else { // out objects.canvas.style.opacity = calcValues(values.canvas_opacity_out, currentYOffset) } if (scrollRatio <= 0.25) { // in objects.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objects.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else { // out objects.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset); objects.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.57) { // in objects.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; objects.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objects.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } else { // out objects.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; objects.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objects.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } if (scrollRatio <= 0.83) { // in objects.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; objects.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objects.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } else { // out objects.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; objects.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objects.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } break case 3: break } } const 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) { if( currentScene === 0) return //모바일 바운스 때문에 enterNewScene = true currentScene -- document.body.setAttribute('id', `show-scene-${currentScene}`) } if( enterNewScene ) return playAnimation() } window.addEventListener('scroll',() => { yOffset = window.pageYOffset scrollLoop() }) window.addEventListener('load', () => { setLayout() sceneInfo[0].objects.context.drawImage(sceneInfo[0].objects.videoImages[0], 0, 0) }) window.addEventListener('resize', setLayout) })()
- 미해결애플 웹사이트 인터랙션 클론!
sticky 와 fixed의 차이를 정확히 모르겠어요;;
기능은 동일한것 같은데 애니메이션을 쓰려면 fixed가 필요하는 건가요?
- 미해결애플 웹사이트 인터랙션 클론!
main-add.js playAnimation 함수 오타
currentYOffset 변수 부분에서 prevScrollHeight -> preScrollHeight 위처럼 수정하셔야 합니다 가벼운 오타가 있어서 남겨둡니다!
- 미해결애플 웹사이트 인터랙션 클론!
선생님.. 강의랑은 관련없는 질문인데요
혹시 vscode 테마 뭐쓰시나요..? 너무 이뻐서 저도 쓰고싶어서 뒤적여 보는데 뭔지 모르겠어서요..
- 미해결애플 웹사이트 인터랙션 클론!
for문에 변수를 생성하는 것과 아닌것의 차이
// for문 밖에서 변수를 한번 생성하는 경우 function setCanvasImages() { let imgElem; for (let i = 0; i < sceneInfo[0].values.videoImageCount; i++) { imgElem = new Image(); imgElem.src = `./video/001/IMG_${6726 + i}.JPG`; sceneInfo[0].objs.videoImages.push(imgElem); } } // for문 안에서 변수를 매번 생성하는 경우 function setCanvasImages () { for (let i = 0; i < sceneInfo[0].values.videoImageCount; i++) { const $img = new Image(); $img.src = `../video/001/IMG_${6726 + i}.JPG`; sceneInfo[0].objs.videoImages.push($img); } } 1. 변수를 한번 생성하는 것과 변수를 매번 생성하는 것에 어떤 차이가 있을까요? 2. 함수가 작동하는 시간을 측정하는 방법 console.time('setCanvasImages') function setCanvasImages () { let $img; for (let i = 0; i < sceneInfo[0].values.videoImageCount; i++) { $img = new Image(); $img.src = `../video/001/IMG_${6726 + i}.JPG`; sceneInfo[0].objs.videoImages.push($img); } } setCanvasImages () console.timeEnd('setCanvasImages') 1번의 답이 혹시 작동 시간이 아닐까 해서 console.Time()을 사용해 봤습니다. 그런데 새로고침(shift + f5)할 때마다 측정속도가 달라서 더 모르겠더라구요; 함수 시간을 체크하는 방법이 잘못된 것일까요?
- 미해결애플 웹사이트 인터랙션 클론!
스크롤 바 calcVaues에서 질문이 있습니당..
function calcValues(values,currentYOffset){ //currentYOffset : 현재 씬에서 얼마나 스크롤 됐는지.. let rv; //현재 씬(스크롤섹션)에서 스크롤된 범위를 비율로 구하기 const scrollHeight=sceneInfo[currentScene].scrollHeight; //각각의 scene의 높이 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]; console.log(123123123); } return rv; } 이부분에 있어 질문이 있습니다 ㅠㅠ values.length를 보게 되면 values:{ messageA_opacity_in : [0,1,{start : 0.1, end:0.2}], // messageB_opacity_in : [0,1,{start : 0.3, end:0.4}], //messageA 투명도 0부터 1까지 messageA_translateY_in:[20,0,{start : 0.1, end:0.2}], //20프로의 높이약간 움직임 messageA_opacity_out : [1,0,{start : 0.25, end:0.3}], messageA_translateY_out : [0,-20,{start : 0.25, end:0.3}], }, 이렇게 되어 있는데 그러면 당연히 갯수가 3개니까 values.length===3일수 밖에 없는데 굳이 이렇게 한 이유가 무엇인가요? 그리고 else 문은 어떻게 작동이 되는 건가용?
- 미해결애플 웹사이트 인터랙션 클론!
heightNum을 모바일과 pc버전에서 다르게 수치를 줄 수 있을까요??
자바스크립트에서 //1 부분 Type:normal로 된 부분을 sticky로 바꿔 사용중인데, 컨텐츠가 다 끝나기전에 다음섹션 영상이 실행되서 heightNum 숫자를 올리니 해결이 되었습니다. 하지만 모바일 디스플레이에서의 화면 비율을 신경쓰다보니 pc버전에서 볼때 스크롤이 너무 길어져서 보기가 안좋아서요..! 이 경우에 혹시 모바일과 PC버전에서 HeightNum을 다르게 줄 수는 없나요??
- 미해결애플 웹사이트 인터랙션 클론!
넓은 화면에서 스크롤시엔 스케일 줄어드는 중점이 이상해요
1920 1080 해상도의 모니터에서 동작시 이렇게 스케일이 적용되는 위치가 아래쪽으로 가서 좀 답답해요 스케일 조정이 완료 되었을때는 아래쪽에 살짝 여백이 있는정도인데 중앙에서 계속 유지될 방법 없을가요?? 완성파일은 좀더 여백이 없는데 제 진행파일에서 타이밍을 좀 다르게 설정해서 여백이 더 생긴거 같아요
- 미해결애플 웹사이트 인터랙션 클론!
선언문 질문이요!
const와 let은 {} 블록 단위여서, 블록 밖에것은 하위 블럭안에 영향을 미치나 하위블록에서 선언된 변수는 상위 블록에서 영향을 미치지 못한다고 배웠는데, 그래서 playanimation 함수 내 전역으로 objs 등이 선언되어서 case 0~3 까지 영향을 미치는건 이해가 됬는데 case 2 안 if 안에서 선언된 objs,values 변수가 왜 playanimation함수내에서 선언된 전역변수랑 다른건지 모르겠어요 덮어씌우는? 그런 느낌일까요? (6분 30초 쯤 부터? 언급하셨어요!)