묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
대세는 쿠버네티스 (초급~중급편)
yum update 에러
삭제된 글입니다
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
junit5로 테스트하시는분은 이렇게 하시면 됩니다
제가 실습중인 환경은 SpringBoot 2.3.1.RELEASE여서 error: cannot find symbol 위 오류가 발생했는데, 코드 수정해서 해결했습니다. 참고하시면 도움될것같습니다. package jpabook.jpashop.member; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest public class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Test @Transactional // @Rollback(false) 테스트 종료 후 데이터를 롤배하지 않고 그대로 남겨두는 옵션 public void testMember() throws Exception { //given Member member = new Member(); member.setUsername("1hoon"); //when Long savedId = memberRepository.save(member); Member findMember = memberRepository.find(savedId); //then assertEquals(findMember.getId(), member.getId()); assertEquals(findMember.getUsername(), member.getUsername()); assertEquals(findMember, member); } }
-
미해결[리뉴얼] React로 NodeBird SNS 만들기
게시글의 Comment ordering관련 제보입니다.
저같은 경우에는 댓글의 순서가 이상하게 나옵니다. 1,2,3,4,5,6 이렇게 순서대로 입력한 6개의 댓글들이 새로고침 이후에는 5,6,1,2,3,4 이런 식으로 나열됩니다. 그리고 Comment의 order부분을 삭제해도 결과는 동일하고요. 그래서 Comment의 order부분을 삭제하고 가장 상위의 order 부분을 아래처럼 하니까 정상적으로 동작 하네요. const posts = await Post.findAll({ where, limit: 10, // offset: 20, order: [ ["createdAt", "DESC"], [Comment, "createdAt", "DESC"] ], .... 이게 저만 그런건가요?
-
미해결PHP 7+ 프로그래밍
php 버전 확인이 안되요
php를 xampp 로 설치하고 vsc 도 설치해서 따라하는데 터미널창에 php --version 이라고 해도 php : 'php' 용어가 cmdlet, 함수, 스크립트 파일 또는 실행할 수 있는 프로그램 이름으로 인식되지 않습니다. 이름이 정확한지 확인하고 경로가 포함된 경우 경로가 올바른지 검증한 다음 다시 시도하십시오. 라고나옵니다. 그리고 자동으로 $가 앞에 붙어있지 않습니다 ㅠㅠ
-
해결됨따라하며 배우는 노드, 리액트 시리즈 - 유튜브 사이트 만들기
강사님이 만드신 boiler-plate 로그인에서 아이디저장기능 관련해서 질문 있습니다.
위에서 볼 수있듯이 아이디저장하면 자꾸 undefined로 저장이됩니다. 뭐때문인지 잘모르겠어요. window.localStorage.setItem('rememberMe', values.id); 이부분에서 로컬스토리지에 저장이제대로 안되는건지 한번확인해주실수 있나요 ㅠ?
-
미해결따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
nodemon 에러
안녕하세요. 강의대로 시행시 자꾸 저런 에러가 뜨는데 무엇이 문제일까요? 아무리 찾아봐도 해결방법을 모르겠네요 ㅠㅠ + node cache 등등을 지우고 node 자체를 지웠다 다시 깔아보기 + nodemon 대신 pm2 깔기 등등 해봤는데 다 안 되네요 ㅠ
-
미해결제주코딩베이스캠프 Code Festival: JavaScript 100제
아스키 코드 연산 가능 유무
아스키 코드 연산도 문제에서는 불가능한가요? 저는 문자열을 서로 뺄셈하여 숫자를 구했습니다.
-
해결됨파이썬 알고리즘 문제풀이 입문(코딩테스트 대비)
코드 관련 질문입니다.
문제에서는 'N개의 숫자로 이뤄진 숫자열이 주어지면' 이라고 숫자열 범위의 제한이 명시되어 있는데, 해당 코드로 한다면 범위의 제한이 없는 거 아닌가요??
-
미해결윤재성의 Vue.js 프로젝트를 통한 실무 개발 과정
v-model.number
v-model.number 수식어 부분에서 문자열만 쓰고 버튼을 누르면 강의처럼 string이라고 나옵니다 ex) asdf 문자열+숫자를 쓰고 버튼을 누르면 역시 강의처럼 string이라고 나옵니다 ex)asdf1234 근데 숫자+문자열을 쓰고 버튼을 누르면 number라고 나오고 뒤에 문자열이 지워집니다 ex)1234asdf 이 부분에 대한 설명을 들을 수 있을까요?
-
미해결자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌
게시판을 제작하는 강의는 어디있는건가요 ?
게시판 제작 강의는 어디있는건가요 ?
-
따라하며 배우는 노드, 리액트 시리즈 - 기본 강의
[해결]npm run start 에러
삭제된 글입니다
-
해결됨스프링 프레임워크 핵심 기술
결국 다른 패키지에 있는 빈들을 스캔할려면
MyService같은 다른 패키지에 있는 빈들을 스캔할려면 펑션을 사용한 빈 등록처럼 직접 applicationContext에 다가 빈을 등록하는수 밖에 없나요? 그리고 펑션을 사용한 빈 등록은 인스턴스를 이용해 생성해서 직접 빈 등록해서 구동시 성능을 증가한다고 보면 될까요? 그런데 단점으로는 설정파일이 너무 많아지는점이구요
-
해결됨스프링 프레임워크 핵심 기술
마지막 스프링부트에서 bean관리 하는 방법 설명에서
기존에서는 xml , java로 componentscan으로 @component인 클래스들 빈으로 해서 ApplicationContext에서 관리 했다면 최종단계는 @SpringbootApplication에 componentscan하고 config 다 붙어 있어서 필요 없는걸까요? 스프링부트에서 ApplicationContext도 자동으로 생성하고 scan도 하고 관리도하니까
-
미해결당신을 함께 일하고 싶은 개발자로 만들어 줄 클린 코드 작성법
그냥 지나가려 했는데 뭐가 자꾸 보이네요..
동료에게 혼란만 줄 뿐이다. -> 동료에게 혼란만 줄 뿐입니다 별거 아닌거 지적해서 죄송합니다,,
-
미해결당신을 함께 일하고 싶은 개발자로 만들어 줄 클린 코드 작성법
그밖의 좋은 코드 작성을 위한 Tip2 : Tell, Don't ask 설명이 누락된 것 같습니다
그밖의 좋은 코드 작성을 위한 Tip2 : Tell, Don't ask 에서 나쁜 예를 소개할 때 엘리베이터를 관리하는 ElevatorManager와 Elevator 클래스가 있다고 가정하여 예로 들어보았습니다. 라고 했는데 ElevatorManager 와 Elevator 클래스가 어떤 클래스인지 코드가 나와있지 않습니다. 변화된 모습은 나오는데 변화되기 전의 형상이 없어 왜 tell 방식이 ask방식보다 좋은지 이해가 안되네요
-
미해결트렐로 개발로 배우는 Vuejs, Vuex, Vue-Router 프론트엔드 실전 기술
addEventListener를 사용하였는데 removeEventListener 해주어야 하지 않을까요?
addEventListener가 mounted 라이프사이클 훅에 걸려있어 열고 닫을때마다 addEventListener가 계속 생성될텐데 그대로 둬도 될지 잘 모르겠네용.. ㅠㅠ
-
미해결웹 게임을 만들며 배우는 React
hooks로 변환하니 다음과 같은 에러가 납니다.
제 생각에는 초기 배열이 빈배열이어서 length를 쓸 수 없어서 나는 에러 같은데 어떻게 해결해야 할지 모르겠습니다.
-
미해결애플 웹사이트 인터랙션 클론!
4개의 씬을 5개로 늘려서 응용하려는 데 작동이 잘 안되네요 ㅠㅠ
@charset 'utf-8'; html { font-family: 'Noto Sans KR', sans-serif; font-size: 14px; } body { overflow-x: hidden; color: rgb(29, 29, 31); letter-spacing: -0.05em; background: white; } p { line-height: 1.6; } a { color: rgb(29, 29, 31); text-decoration: none; } body.before-load { overflow: hidden; } .container { /* iPhone 가로 스크롤 방지 */ overflow-x: hidden; } .global-nav { position: absolute; top: 0; left: 0; z-index: 10; width: 100%; height: 44px; padding: 0 1rem; } .local-nav { position: absolute; top: 45px; left: 0; z-index: 11; width: 100%; height: 52px; padding: 0 1rem; border-bottom: 1px solid #ddd; } .local-nav-sticky .local-nav { position: fixed; top: 0; background: rgba(255, 255, 255, 0.1); /* for iPhone */ -webkit-backdrop-filter: saturate(180%) blur(15px); -moz-backdrop-filter: saturate(180%) blur(15px); -o-backdrop-filter: saturate(180%) blur(15px); backdrop-filter: saturate(180%) blur(15px); } .global-nav-links, .local-nav-links { display: flex; align-items: center; max-width: 1000px; height: 100%; margin: 0 auto; } .global-nav-links { justify-content: space-between; } .local-nav-links .product-name { margin-right: auto; font-size: 1.4rem; font-weight: bold; } .local-nav-links a { font-size: 0.8rem; } .local-nav-links a:not(.product-name) { margin-left: 2em; } .scroll-section { position: relative; padding-top: 50vh; border: 3px red solid; } #scroll-section-0 h1 { position: relative; top: -10vh; z-index: 5; font-size: 4rem; text-align: center; } #scroll-section-1 h1 { position: relative; top: -10vh; z-index: 5; font-size: 4rem; text-align: center; } .main-message { display: flex; align-items: center; justify-content: center; top: 35vh; margin: 5px 0; height: 3em; font-size: 2.5rem; opacity: 0; } .main-message p { font-weight: bold; text-align: center; line-height: 1.2; } .main-message small { display: block; margin-bottom: 0.5em; font-size: 1.2rem; } #scroll-section-3 .main-message { font-size: 3.5rem; } .description { max-width: 1000px; margin: 0 auto; padding: 0 1rem; font-size: 1.2rem; color: #888; } .description strong { float: left; margin-right: 0.2em; font-size: 3rem; color: rgb(29, 29, 31); } .desc-message { width: 50%; font-weight: bold; opacity: 0; } .pin { width: 1px; height: 100px; background: rgb(29, 29, 31); } #scroll-section-3 .b { top: 10%; left: 40%; } #scroll-section-3 .c { top: 15%; left: 45%; } .mid-message { max-width: 1000px; margin: 0 auto; padding: 0 1rem; font-size: 2rem; color: #888; } .mid-message strong { color: rgb(29, 29, 31); } .canvas-caption { max-width: 1000px; margin: -24rem auto 0; padding: 0 1rem; font-size: 1.2rem; color: #888; } .footer { display: flex; align-items: center; justify-content: center; height: 7rem; color: white; background: darkorange; } .sticky-elem { display: none; position: fixed; left: 0; width: 100%; } #show-scene-0 #scroll-section-0 .sticky-elem, #show-scene-1 #scroll-section-1 .sticky-elem, #show-scene-2 #scroll-section-2 .sticky-elem, #show-scene-3 #scroll-section-3 .sticky-elem, #show-scene-4 #scroll-section-4 .sticky-elem { { display: block; will-change: transform, opacity; } .sticky-elem-canvas { top: 0; height: 100%; } .sticky-elem-canvas canvas { position: absolute; top: 50%; left: 50%; } #scroll-section-3 { display: flex; flex-direction: column; align-items: center; } .image-blend-canvas.sticky { position: fixed; top: 0; } .loading { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 100; background: white; opacity: 0; transition: 0.5s; } .before-load .container { display: none; } .before-load .loading { opacity: 1; } @keyframes loading-spin { 100% { transform: rotate(360deg); } } @keyframes loading-circle-ani { 0% { stroke-dashoffset: 157; } 75% { stroke-dashoffset: -147; } 100% { stroke-dashoffset: -157; } } .loading-circle { width: 54px; height: 54px; animation: loading-spin 3s infinite; } .loading-circle circle { stroke: black; stroke-width: 4; /* getTotalLength()로 stroke의 길이를 얻어올 수 있음 */ stroke-dasharray: 157; stroke-dashoffset: 0; fill: transparent; animation: loading-circle-ani 1s infinite; /* transition: 1s; */ } /* .loading-circle:hover circle { stroke-dashoffset: -157; } */ @media (min-width: 1024px) { #scroll-section-0 h1 { font-size: 9vw; } #scroll-section-1 h1 { font-size: 9vw; } .main-message { font-size: 4vw; } .description { padding: 0; font-size: 2rem; } .description strong { font-size: 6rem; } #scroll-section-3 .main-message { font-size: 6vw; } .main-message small { font-size: 1.5vw; } .desc-message { width: 20%; } #scroll-section-3 .b { top: 20%; left: 53%; } #scroll-section-3 .c { left: 55%; } .mid-message { width: 1000px; padding: 0; font-size: 4vw; } .canvas-caption { margin-top: -8rem; padding: 0; font-size: 2rem; } } <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>AirMug Pro</title> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;900&display=swap" rel="stylesheet"> <link rel="stylesheet" href="css/default.css"> <link rel="stylesheet" href="css/main.css"> </head> <body class="before-load"> <div class="loading"> <svg class="loading-circle"> <circle cx="50%" cy="50%" r="25"></circle> </svg> </div> <div class="container"> <nav class="global-nav"> <div class="global-nav-links"> <a href="#" class="global-nav-item">Rooms</a> <a href="#" class="global-nav-item">Ideas</a> <a href="#" class="global-nav-item">Stores</a> <a href="#" class="global-nav-item">Contact</a> </div> </nav> <nav class="local-nav"> <div class="local-nav-links"> <a href="#" class="product-name">AirMug Pro</a> <a href="#">개요</a> <a href="#">제품사양</a> <a href="#">구입하기</a> </div> </nav> <section class="scroll-section" id="scroll-section-0"> <h1>영주마실 사과즙 30포</h1> <img src="./images/blend-image-1.jpg" alt=""> </section> <section class="scroll-section" id="scroll-section-1"> <!--<h1>영주마실 사과즙 30포</h1>--> <div class="sticky-elem sticky-elem-canvas"> <canvas id="video-canvas-0" width="1920" height="1080"></canvas> </div> <div class="sticky-elem main-message a"> <p>온전히 빠져들게 하는<br>최고급 세라믹</p> </div> <div class="sticky-elem main-message b"> <p>주변 맛을 느끼게 해주는<br>주변 맛 허용 모드</p> </div> <div class="sticky-elem main-message c"> <p>온종일 편안한<br>맞춤형 손잡이</p> </div> <div class="sticky-elem main-message d"> <p>새롭게 입가를<br>찾아온 매혹</p> </div> </section> <section class="scroll-section" id="scroll-section-2"> <p class="description"> <strong>보통 스크롤 영역</strong> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Beatae est ipsa minima, eligendi error cum vel dolorum pariatur officia facilis ipsam voluptatibus ad quasi porro quod quisquam quidem tempora accusantium accusamus, quaerat aliquam velit exercitationem incidunt? Id vitae quisquam saepe quasi accusantium tempore enim! Aperiam dolorum a vero repellat dolor, inventore ab odit totam molestias expedita? Enim quia dolor maiores veniam ea! Quam illo, est incidunt ipsa reiciendis modi quisquam reprehenderit fuga velit dolorem odit sequi autem blanditiis, ullam commodi quibusdam. Accusamus repellat aperiam quis neque laudantium, dignissimos hic nisi magnam praesentium enim beatae sint architecto cum numquam inventore rerum animi sed nostrum quae delectus, voluptas molestiae placeat aliquid! Vel quaerat error officiis magnam dolorum iste aspernatur at est! Quo, consequuntur? Reiciendis, dolor. Quo at cupiditate in iure obcaecati voluptatum vel ea! Ab vel harum facere hic fuga ducimus sapiente dolore dolorem, nobis sint perferendis cumque esse! Omnis fugiat sint error laborum eveniet labore nam ducimus quisquam in repudiandae impedit excepturi dignissimos tenetur libero placeat rerum maxime tempore, aut nihil. Qui, quam? Voluptate fuga possimus itaque quas nesciunt iste, facilis mollitia illo qui placeat temporibus inventore obcaecati. Recusandae, sequi dignissimos in natus eum maiores dolorem, deleniti nobis accusantium, aspernatur beatae. </p> </section> <section class="scroll-section" id="scroll-section-3"> <div class="sticky-elem sticky-elem-canvas"> <canvas id="video-canvas-1" width="1920" height="1080"></canvas> </div> <div class="sticky-elem main-message a"> <p> <small>편안한 촉감</small> 입과 하나 되다 </p> </div> <div class="sticky-elem desc-message b"> <p> 편안한 목넘김을 완성하는 디테일한 여러 구성 요소들, 우리는 이를 하나하나 새롭게 살피고 재구성하는 과정을 거쳐 새로운 수준의 머그, AirMug Pro를 만들었습니다. 입에 뭔가 댔다는 감각은 어느새 사라지고 오롯이 당신과 음료만 남게 되죠. </p> <div class="pin"></div> </div> <div class="sticky-elem desc-message c"> <p> 디자인 앤 퀄리티 오브 스웨덴,<br>메이드 인 차이나 </p> <div class="pin"></div> </div> </section> <section class="scroll-section" id="scroll-section-4"> <p class="mid-message"> <strong>Retina 머그</strong><br> 아이디어를 광활하게 펼칠<br> 아름답고 부드러운 음료 공간. </p> <canvas class="image-blend-canvas" width="1920" height="1080"></canvas> <p class="canvas-caption"> Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet at fuga quae perspiciatis veniam impedit et, ratione est optio porro. Incidunt aperiam nemo voluptas odit quisquam harum in mollitia. Incidunt minima iusto in corporis, dolores velit. Autem, sit dolorum inventore a rerum distinctio vero illo magni possimus temporibus dolores neque adipisci, repudiandae repellat. Ducimus accusamus similique quas earum laborum. Autem tempora repellendus asperiores illum ex! Velit ea corporis odit? Ea, incidunt delectus. Sapiente rerum neque error deleniti quis, et, quibusdam, est autem voluptate rem voluptas. Ratione soluta similique harum nihil vel. Quas inventore perferendis iusto explicabo animi eos ratione obcaecati. </p> </section> <footer class="footer"> 2020, 1분코딩 </footer> </div> <script src="js/main.js"></script> </body> </html> (() => { let yOffset = 0; // window.pageYOffset 대신 쓸 변수 let prevScrollHeight = 0; // 현재 스크롤 위치(yOffset)보다 이전에 위치한 스크롤 섹션들의 스크롤 높이값의 합 let currentScene = 0; // 현재 활성화된(눈 앞에 보고있는) 씬(scroll-section) let enterNewScene = false; // 새로운 scene이 시작된 순간 true let acc = 0.2; let delayedYOffset = 0; let rafId; let rafState; const sceneInfo = [ { // 0 type: 'normal', //heightNum: 5, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objs:{ container:document.querySelector('#scroll-section-0') } }, { // 1 type: 'sticky', heightNum: 5, // 브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-1'), messageA: document.querySelector('#scroll-section-1 .main-message.a'), messageB: document.querySelector('#scroll-section-1 .main-message.b'), messageC: document.querySelector('#scroll-section-1 .main-message.c'), messageD: document.querySelector('#scroll-section-1 .main-message.d'), canvas: document.querySelector('#video-canvas-0'), context: document.querySelector('#video-canvas-0').getContext('2d'), videoImages: [] }, values: { videoImageCount: 300, imageSequence: [0, 299], canvas_opacity: [1, 0, { start: 0.9, end: 1 }], messageA_opacity_in: [0, 1, { start: 0.1, end: 0.2 }], messageB_opacity_in: [0, 1, { start: 0.3, end: 0.4 }], messageC_opacity_in: [0, 1, { start: 0.5, end: 0.6 }], messageD_opacity_in: [0, 1, { start: 0.7, end: 0.8 }], messageA_translateY_in: [20, 0, { start: 0.1, end: 0.2 }], messageB_translateY_in: [20, 0, { start: 0.3, end: 0.4 }], messageC_translateY_in: [20, 0, { start: 0.5, end: 0.6 }], messageD_translateY_in: [20, 0, { start: 0.7, end: 0.8 }], messageA_opacity_out: [1, 0, { start: 0.25, end: 0.3 }], messageB_opacity_out: [1, 0, { start: 0.45, end: 0.5 }], messageC_opacity_out: [1, 0, { start: 0.65, end: 0.7 }], messageD_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], messageA_translateY_out: [0, -20, { start: 0.25, end: 0.3 }], messageB_translateY_out: [0, -20, { start: 0.45, end: 0.5 }], messageC_translateY_out: [0, -20, { start: 0.65, end: 0.7 }], messageD_translateY_out: [0, -20, { start: 0.85, end: 0.9 }] } }, { // 2 type: 'normal', // heightNum: 5, // type normal에서는 필요 없음 scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-2') } }, { // 3 type: 'sticky', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-3'), messageA: document.querySelector('#scroll-section-3 .a'), messageB: document.querySelector('#scroll-section-3 .b'), messageC: document.querySelector('#scroll-section-3 .c'), pinB: document.querySelector('#scroll-section-3 .b .pin'), pinC: document.querySelector('#scroll-section-3 .c .pin'), canvas: document.querySelector('#video-canvas-1'), context: document.querySelector('#video-canvas-1').getContext('2d'), videoImages: [] }, values: { videoImageCount: 960, imageSequence: [0, 959], canvas_opacity_in: [0, 1, { start: 0, end: 0.1 }], canvas_opacity_out: [1, 0, { start: 0.95, end: 1 }], messageA_translateY_in: [20, 0, { start: 0.15, end: 0.2 }], messageB_translateY_in: [30, 0, { start: 0.6, end: 0.65 }], messageC_translateY_in: [30, 0, { start: 0.87, end: 0.92 }], messageA_opacity_in: [0, 1, { start: 0.25, end: 0.3 }], messageB_opacity_in: [0, 1, { start: 0.6, end: 0.65 }], messageC_opacity_in: [0, 1, { start: 0.87, end: 0.92 }], messageA_translateY_out: [0, -20, { start: 0.4, end: 0.45 }], messageB_translateY_out: [0, -20, { start: 0.68, end: 0.73 }], messageC_translateY_out: [0, -20, { start: 0.95, end: 1 }], messageA_opacity_out: [1, 0, { start: 0.4, end: 0.45 }], messageB_opacity_out: [1, 0, { start: 0.68, end: 0.73 }], messageC_opacity_out: [1, 0, { start: 0.95, end: 1 }], pinB_scaleY: [0.5, 1, { start: 0.6, end: 0.65 }], pinC_scaleY: [0.5, 1, { start: 0.87, end: 0.92 }] } }, { // 4 type: 'sticky', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-4'), canvasCaption: document.querySelector('.canvas-caption'), canvas: document.querySelector('.image-blend-canvas'), context: document.querySelector('.image-blend-canvas').getContext('2d'), imagesPath: [ './images/blend-image-1.jpg', './images/blend-image-2.jpg' ], images: [] }, values: { rect1X: [ 0, 0, { start: 0, end: 0 } ], rect2X: [ 0, 0, { start: 0, end: 0 } ], blendHeight: [ 0, 0, { start: 0, end: 0 } ], canvas_scale: [ 0, 0, { start: 0, end: 0 } ], canvasCaption_opacity: [ 0, 1, { start: 0, end: 0 } ], canvasCaption_translateY: [ 20, 0, { start: 0, end: 0 } ], rectStartY: 0 } } ]; function setCanvasImages() { let imgElem; for (let i = 0; i < sceneInfo[1].values.videoImageCount; i++) { imgElem = new Image(); imgElem.src = `./video/001/IMG_${6726 + i}.JPG`; sceneInfo[1].objs.videoImages.push(imgElem); } let imgElem2; for (let i = 0; i < sceneInfo[3].values.videoImageCount; i++) { imgElem2 = new Image(); imgElem2.src = `./video/002/IMG_${7027 + i}.JPG`; sceneInfo[3].objs.videoImages.push(imgElem2); } let imgElem3; for (let i = 0; i < sceneInfo[4].objs.imagesPath.length; i++) { imgElem3 = new Image(); imgElem3.src = sceneInfo[4].objs.imagesPath[i]; sceneInfo[4].objs.images.push(imgElem3); } } function checkMenu() { if (yOffset > 44) { document.body.classList.add('local-nav-sticky'); } else { document.body.classList.remove('local-nav-sticky'); } } function setLayout() { // 각 스크롤 섹션의 높이 세팅 for (let i = 0; i < sceneInfo.length; i++) { if (sceneInfo[i].type === 'sticky') { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; } else if (sceneInfo[i].type === 'normal') { sceneInfo[i].scrollHeight = sceneInfo[i].objs.container.offsetHeight; } sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; } yOffset = window.pageYOffset; let totalScrollHeight = 0; for (let i = 0; i < sceneInfo.length; i++) { totalScrollHeight += sceneInfo[i].scrollHeight; if (totalScrollHeight >= yOffset) { currentScene = i; break; } } document.body.setAttribute('id', `show-scene-${currentScene}`); const heightRatio = window.innerHeight / 1080; sceneInfo[1].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; sceneInfo[3].objs.canvas.style.transform = `translate3d(-50%, -50%, 0) scale(${heightRatio})`; } function calcValues(values, currentYOffset) { let rv; // 현재 씬(스크롤섹션)에서 스크롤된 범위를 비율로 구하기 const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; if (values.length === 4) { // start ~ end 사이에 애니메이션 실행 const partScrollStart = values[2].start * scrollHeight; const partScrollEnd = values[2].end * scrollHeight; const partScrollHeight = partScrollEnd - partScrollStart; if (currentYOffset >= partScrollStart && currentYOffset <= partScrollEnd) { rv = (currentYOffset - partScrollStart) / partScrollHeight * (values[1] - values[0]) + values[0]; } else if (currentYOffset < partScrollStart) { rv = values[0]; } else if (currentYOffset > partScrollEnd) { rv = values[1]; } } else { rv = scrollRatio * (values[1] - values[0]) + values[0]; } //console.log(scrollHeight); return rv; } //console.log(calcValues); function playAnimation() { const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; const currentYOffset = yOffset - prevScrollHeight; const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / scrollHeight; switch (currentScene) { case 1: console.log(objs.messageA.style.opacity); console.log(objs.messageB.style.opacity); console.log(objs.messageC.style.opacity); console.log(objs.messageD.style.opacity); console.log(currentYOffset); console.log(scrollHeight); // let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); // objs.context.drawImage(objs.videoImages[sequence], 0, 0); objs.canvas.style.opacity = calcValues(values.canvas_opacity, currentYOffset); if (scrollRatio <= 0.22) { // in objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.42) { // in objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.62) { // in objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.82) { // in objs.messageD.style.opacity = calcValues(values.messageD_opacity_in, currentYOffset); objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageD.style.opacity = calcValues(values.messageD_opacity_out, currentYOffset); objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_out, currentYOffset)}%, 0)`; } break; case 3: // console.log('2 play'); // let sequence2 = Math.round(calcValues(values.imageSequence, currentYOffset)); // objs.context.drawImage(objs.videoImages[sequence2], 0, 0); if (scrollRatio <= 0.5) { // in objs.canvas.style.opacity = calcValues(values.canvas_opacity_in, currentYOffset); } else { // out objs.canvas.style.opacity = calcValues(values.canvas_opacity_out, currentYOffset); } if (scrollRatio <= 0.32) { // in objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.67) { // in objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } else { // out objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } if (scrollRatio <= 0.93) { // in objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } else { // out objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } // currentScene 3에서 쓰는 캔버스를 미리 그려주기 시작 if (scrollRatio > 0.9) { const objs = sceneInfo[4].objs; const values = sceneInfo[4].values; const widthRatio = window.innerWidth / objs.canvas.width; const heightRatio = window.innerHeight / objs.canvas.height; let canvasScaleRatio; if (widthRatio <= heightRatio) { // 캔버스보다 브라우저 창이 홀쭉한 경우 canvasScaleRatio = heightRatio; } else { // 캔버스보다 브라우저 창이 납작한 경우 canvasScaleRatio = widthRatio; } objs.canvas.style.transform = `scale(${canvasScaleRatio})`; objs.context.fillStyle = 'white'; objs.context.drawImage(objs.images[0], 0, 0); // 캔버스 사이즈에 맞춰 가정한 innerWidth와 innerHeight const recalculatedInnerWidth = document.body.offsetWidth / canvasScaleRatio; const recalculatedInnerHeight = window.innerHeight / canvasScaleRatio; const whiteRectWidth = recalculatedInnerWidth * 0.15; values.rect1X[0] = (objs.canvas.width - recalculatedInnerWidth) / 2; values.rect1X[1] = values.rect1X[0] - whiteRectWidth; values.rect2X[0] = values.rect1X[0] + recalculatedInnerWidth - whiteRectWidth; values.rect2X[1] = values.rect2X[0] + whiteRectWidth; // 좌우 흰색 박스 그리기 objs.context.fillRect( parseInt(values.rect1X[0]), 0, parseInt(whiteRectWidth), objs.canvas.height ); objs.context.fillRect( parseInt(values.rect2X[0]), 0, parseInt(whiteRectWidth), objs.canvas.height ); } break; case 4: // console.log('3 play'); let step = 0; // 가로/세로 모두 꽉 차게 하기 위해 여기서 세팅(계산 필요) const widthRatio = window.innerWidth / objs.canvas.width; const heightRatio = window.innerHeight / objs.canvas.height; let canvasScaleRatio; if (widthRatio <= heightRatio) { // 캔버스보다 브라우저 창이 홀쭉한 경우 canvasScaleRatio = heightRatio; } else { // 캔버스보다 브라우저 창이 납작한 경우 canvasScaleRatio = widthRatio; } objs.canvas.style.transform = `scale(${canvasScaleRatio})`; objs.context.fillStyle = 'white'; objs.context.drawImage(objs.images[0], 0, 0); // 캔버스 사이즈에 맞춰 가정한 innerWidth와 innerHeight const recalculatedInnerWidth = document.body.offsetWidth / canvasScaleRatio; const recalculatedInnerHeight = window.innerHeight / canvasScaleRatio; if (!values.rectStartY) { // values.rectStartY = objs.canvas.getBoundingClientRect().top; values.rectStartY = objs.canvas.offsetTop + (objs.canvas.height - objs.canvas.height * canvasScaleRatio) / 2; values.rect1X[2].start = (window.innerHeight / 2) / scrollHeight; values.rect2X[2].start = (window.innerHeight / 2) / scrollHeight; values.rect1X[2].end = values.rectStartY / scrollHeight; values.rect2X[2].end = values.rectStartY / scrollHeight; } const whiteRectWidth = recalculatedInnerWidth * 0.15; values.rect1X[0] = (objs.canvas.width - recalculatedInnerWidth) / 2; values.rect1X[1] = values.rect1X[0] - whiteRectWidth; values.rect2X[0] = values.rect1X[0] + recalculatedInnerWidth - whiteRectWidth; values.rect2X[1] = values.rect2X[0] + whiteRectWidth; // 좌우 흰색 박스 그리기 objs.context.fillRect( parseInt(calcValues(values.rect1X, currentYOffset)), 0, parseInt(whiteRectWidth), objs.canvas.height ); objs.context.fillRect( parseInt(calcValues(values.rect2X, currentYOffset)), 0, parseInt(whiteRectWidth), objs.canvas.height ); if (scrollRatio < values.rect1X[2].end) { step = 1; // console.log('캔버스 닿기 전'); objs.canvas.classList.remove('sticky'); } else { step = 2; // console.log('캔버스 닿은 후'); // 이미지 블렌드 // values.blendHeight: [ 0, 0, { start: 0, end: 0 } ] values.blendHeight[0] = 0; values.blendHeight[1] = objs.canvas.height; values.blendHeight[2].start = values.rect1X[2].end; values.blendHeight[2].end = values.blendHeight[2].start + 0.2; const blendHeight = calcValues(values.blendHeight, currentYOffset); objs.context.drawImage(objs.images[1], 0, objs.canvas.height - blendHeight, objs.canvas.width, blendHeight, 0, objs.canvas.height - blendHeight, objs.canvas.width, blendHeight ); objs.canvas.classList.add('sticky'); objs.canvas.style.top = `${-(objs.canvas.height - objs.canvas.height * canvasScaleRatio) / 2}px`; if (scrollRatio > values.blendHeight[2].end) { values.canvas_scale[0] = canvasScaleRatio; values.canvas_scale[1] = document.body.offsetWidth / (1.5 * objs.canvas.width); values.canvas_scale[2].start = values.blendHeight[2].end; values.canvas_scale[2].end = values.canvas_scale[2].start + 0.2; objs.canvas.style.transform = `scale(${calcValues(values.canvas_scale, currentYOffset)})`; objs.canvas.style.marginTop = 0; } if (scrollRatio > values.canvas_scale[2].end && values.canvas_scale[2].end > 0) { objs.canvas.classList.remove('sticky'); objs.canvas.style.marginTop = `${scrollHeight * 0.4}px`; values.canvasCaption_opacity[2].start = values.canvas_scale[2].end; values.canvasCaption_opacity[2].end = values.canvasCaption_opacity[2].start + 0.1; values.canvasCaption_translateY[2].start = values.canvasCaption_opacity[2].start; values.canvasCaption_translateY[2].end = values.canvasCaption_opacity[2].end; objs.canvasCaption.style.opacity = calcValues(values.canvasCaption_opacity, currentYOffset); objs.canvasCaption.style.transform = `translate3d(0, ${calcValues(values.canvasCaption_translateY, currentYOffset)}%, 0)`; } } break; } } function scrollLoop() { enterNewScene = false; prevScrollHeight = 0; for (let i = 0; i < currentScene; i++) { prevScrollHeight += sceneInfo[i].scrollHeight; } if (delayedYOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { enterNewScene = true; currentScene++; document.body.setAttribute('id', `show-scene-${currentScene}`); } if (delayedYOffset < prevScrollHeight) { enterNewScene = true; if (currentScene === 0) return; // 브라우저 바운스 효과로 인해 마이너스가 되는 것을 방지(모바일) currentScene--; document.body.setAttribute('id', `show-scene-${currentScene}`); } if (enterNewScene) return; playAnimation(); } function loop() { delayedYOffset = delayedYOffset + (yOffset - delayedYOffset) * acc; if (!enterNewScene) { if (currentScene === 1 || currentScene === 3) { const currentYOffset = delayedYOffset - prevScrollHeight; const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; let sequence = Math.round(calcValues(values.imageSequence, currentYOffset)); if (objs.videoImages[sequence]) { objs.context.drawImage(objs.videoImages[sequence], 0, 0); } } } // 추가 코드 // home이나 end를 이용해 페이지 끝으로 고속 이동하면 body id가 제대로 인식 안되는 경우를 해결 // home 키로 페이지 맨 위로 갈 경우: scrollLoop와 첫 scene의 기본 캔버스 그리기 수행 /* if (delayedYOffset < 1) { scrollLoop(); sceneInfo[0].objs.canvas.style.opacity = 1; sceneInfo[0].objs.context.drawImage(sceneInfo[0].objs.videoImages[0], 0, 0); }*/ // end 키로 페이지 맨 아래로 갈 경우: 마지막 섹션은 스크롤 계산으로 위치 및 크기를 결정해야할 요소들이 많아서 1픽셀을 움직여주는 것으로 해결 if ((document.body.offsetHeight - window.innerHeight) - delayedYOffset < 1) { let tempYOffset = yOffset; scrollTo(0, tempYOffset - 1); } rafId = requestAnimationFrame(loop); if (Math.abs(yOffset - delayedYOffset) < 1) { cancelAnimationFrame(rafId); rafState = false; } } window.addEventListener('load', () => { document.body.classList.remove('before-load'); setLayout(); //sceneInfo[0].objs.context.drawImage(sceneInfo[0].objs.videoImages[0], 0, 0); let tempYOffset = yOffset; let tempScrollCount = 0; if (tempYOffset > 0) { let siId = setInterval(() => { scrollTo(0, tempYOffset); tempYOffset += 5; if (tempScrollCount > 20) { clearInterval(siId); } tempScrollCount++; }, 20); } window.addEventListener('scroll', () => { console.log(`yOffset: ${yOffset}`); yOffset = window.pageYOffset; scrollLoop(); checkMenu(); if (!rafState) { rafId = requestAnimationFrame(loop); rafState = true; } }); window.addEventListener('resize', () => { if (window.innerWidth > 900) { setLayout(); sceneInfo[4].values.rectStartY = 0; } if (currentScene === 4) { // 추가 코드 // Scene 3의 요소들은 위치나 크기가 미리 정해지지 않고 // 현재 창 사이즈나 스크롤 위치에 따라 가변적으로 변하기 때문에 // 리사이즈에 일일이 대응시키기가 까다롭습니다. // Scene 3에 진입 시점에 요소들의 위치와 크기가 결정이 되는 특징을 이용해서 // 현재 Scene이 3일 경우에는 좀 위로 스크롤이 되도록 해서 // Scene 3의 시작 지점 이전으로 돌리는 식으로 요소들의 레이아웃이 깨지는 현상을 방지해 줍니다. // 시작 지점 이전으로 스크롤을 이동 시키는 동작은 // 바로 위 518 라인의 자동 스크롤 코드를 그대로 활용했습니다. let tempYOffset = yOffset; let tempScrollCount = 0; if (tempYOffset > 0) { let siId = setInterval(() => { scrollTo(0, tempYOffset); tempYOffset -= 50; if (tempScrollCount > 20) { clearInterval(siId); } tempScrollCount++; }, 20); } } }); console.log(sceneInfo.length); window.addEventListener('orientationchange', () => { setTimeout(setLayout, 500); }); document.querySelector('.loading').addEventListener('transitionend', (e) => { document.body.removeChild(e.currentTarget); }); }); setCanvasImages(); })();
-
미해결홍정모의 따라하며 배우는 C++
5:15 예외클래스의 전달 과 캐치
5:15 경에 예외클래스를 전달하고 캐치시에 catch(Exception & e ) -> Exception 클래스의 print 실행 catch(ArrayException & e)-> ArrayException 클래스의 print 실행 로 reference로 받고있는데 이러면 음 .. 강의에서 말씀하시는것처럼 ' 객체잘림 '이라기 보다는 '부모클래스의 print에 virtual이 붙지않아 ,다형성으로 구현할때 오버라이딩이 안됐다. ' 라고 보는게 조금 더 맞지않나 싶은데 ( 12.9 객체잘림과 reference_wrapper 강의를 보면 객체잘림을 설명하실때 객체잘림은 refernce 나 포인터가 아닌 instance 끼리의 대입연산자(=) 를 할때 이루어지는것으로 보여지거든요) 이부분은 어떻게 생각하시나요?
-
해결됨애플 웹사이트 인터랙션 클론!
안녕하세요 '현재 활성시킬 씬 결정하기' 강좌편에서 문의가 있습니다.
안녕하세요 다름이 아니라 해당 '현재 활성시킬 씬 결정하기' 에서 코드를 입력하고 있는데 원하는 위치 스크롤 보다 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; }