[애플]AirPods Pro

sealkim·2023년 1월 13일
4

Portfolio

목록 보기
2/4
post-thumbnail

📚 Overview

사이트명: 애플 에어팟 프로
작업 기간: 10일 소요
라이브러리: gsap, jquery
유형: PC 적응형, 클론 코딩
특징: gsap를 활용해 다양한 스크롤 이벤트를 구현한 동적인 페이지입니다.

✔️ What's the point?

  1. 이미지 시퀀스 Canvas태그
  2. 스크롤에 따른 텍스트 투명도 조절 ScrollTrigger & forEach
  3. 고정된 위치에서 스크롤 애니메이션 sticky & GSAP timeline
  4. 동영상 스크롤 컨트롤 setInterval
  5. 동영상 재생 버튼 class

✏️작업 전 공부 기록

  1. velog.io/@kimheewon/[GSAP] 애니메이션 사용법
  2. velog.io/@kimheewon/Image Sequence 2가지 버전



1. 이미지 시퀀스

◻️ HTML

<div id="img_sequence">
	<canvas width="1440" height="810" id="screen"></canvas>
</div>

◻️ SCRIPT

var canvas = document.getElementById('screen');
var context = canvas.getContext('2d');
var scrollYPos = 0;
var img = new Image();

img.src = "./assets/images/canvas/0.png";//스크롤 전 첫 이미지

window.addEventListener('scroll', function(e){
    scrollYPos = Math.round(window.scrollY/12);

    if(scrollYPos > 64) {
        scrollYPos = 64;
    }
    player(scrollYPos);
})

function player(num) {
    img.src = "./assets/images/canvas/" + num + ".png";
}

img.addEventListener('load', function(e) {
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
    context.drawImage(img, 0, 0);
});

✍ 코드 분석

  • var scrollYPos = 0; 스크롤 내렸을때 변하는 scrolly포지션 값의 비율을 조절하기 위해 변수를 선언하였습니다.
  • scrollYPos = Math.round(window.scrollY/12); 64장의 이미지를 스크롤 하였을때 부드러운 모션이 되게 하기 위해 scrollY값을 20분의 1로 나누었고 이때Math.round()를 사용하여 정수가 되도록 설정해주었습니다.
  • function player(num){~} scrollYPos의 값에 따라 이미지를 불러오기 위해 이미지 명을 불러오는 함수를 사용했습니다.
  • 마지막으로 addEventListener() 메서드를 사용하여 'load'이벤트가 발생하면 캔버스가 빠르게 지워지고 다시 그려져 이미지가 움직이는 듯한 애니메이션을 구현하였습니다.

2. 스크롤에 따른 텍스트 투명도 조절

◻️ HTML

<ul class="content_list">
	<li class="content_item">AirPods Pro가 더욱 풍부한 오디오 경험을 선사하도록 새롭게 설계되었습니다.</li>
	<li class="content_item">한 차원 더 강력해진 액티브 노이즈 캔슬링 및 외부 소음을 선별적으로 줄여주는 적응형 주변음 허용 모드.</li>
	<li class="content_item">놀라운 수준의 개인 맞춤형 몰입감을 선사하는 공간 음향.</li>
	<li class="content_item">스와이프로 음량을 조절할 수 있게 해주는 터치 제어.</li>
	<li class="content_item">그리고 한 번의 충전으로 6시간까지 사용 가능한 배터리 성능의 도약까지.</li>
</ul>

◻️ SCRIPT

/** 
* @i = 인덱스
* @l = 엘리먼트
*/
valueContentEl = document.querySelectorAll('.sc_value .content_item');
let i = 0;
valueContentEl.forEach(l => {
    const tl4 = gsap.timeline({scrollTrigger: {
        trigger: l,
        start: "0% 70%",
        end: "130% 70%",
        scrub: 1,
    }});
    if(i === 4){//마지막 엘리먼트는 opacity:1 유지
        tl4.to(l, {opacity:1},"-=0.2")
    }else{
        tl4.to(l, {opacity:1},"-=0.2")
           .to(l, {duration:0.2, opacity:0.15})
    }
    i++;
});

✍ 코드 분석

📋 처음엔 gsap 타임라인에서 딜레이를 주어 각 리스트 오파시티를 조절 했으나 복잡해지고 반복되는 부분이 많아 foreach 반복문으로 변경해 좀 더 간결한 코드를 작성했습니다.

  • 반복이 될 여러 요소를 가져오기 위해 querySelectorAll()로 불러오고, 태그 갯수 만큼 각각 반복을 하기 위해 foreach()를 사용해 주었습니다.
  • 마지막 엘리먼트는 스크롤 해도 opacity:1을 유지하기 위해 i++; i는 증가하도록 설정해주고 if(i === 4){~}라는 조건을 주어 i가 4일 경우에만 opacity를 유지시켰습니다.

    ❔ foreach문

    forEach() 메서드는 배열에 활용이 가능한 메서드로, 주어진 함수를 배열 요소 각각에 대해 실행하는 메서드이다.


3. 고정된 위치에서 스크롤 애니메이션

◻️ CSS

.sc_audio .group_xray {
    position: relative;
    height: 300vh;
}
.sc_audio .group_xray .sticky_inner {
    position: sticky;
    height: 100vh;
    top: 30px;
}

◻️ SCRIPT

const xrayScrollAni = gsap.timeline({scrollTrigger: {
    defaults:{
        ease:'none'
    },
    trigger: ".group_xray",
    start: "0% top",
    end: "100% 150%",
    scrub: true,
}});
gsap.set('.group_xray .desc01,.group_xray .desc02,.group_xray .desc03',{y:150, opacity:0})
xrayScrollAni
   .addLabel('a')
   .to(".group_xray .desc01",{y:-50, opacity:1},'a')
   .to(".group_xray .desc01", {y:-200,opacity:0},'a+1')
   .to(".group_xray .desc02",{y:-50, opacity:1},'a+1.5')
   .to(".group_xray .desc02", {y:-200, opacity:0},'a+2')
   .to(".group_xray .lockup_left img", {duration:2,scale:0.9},'a')
   .to(".group_xray .lockup_left img", {opacity:0},'a+2')
   .addLabel('b')
   .to(".group_xray .lockup_right img", {opacity:1, scale:0.95},'b')
   .to(".group_xray .lockup_right img", {scale:0.86},'b+0.6')
   .to(".group_xray .lockup_right img", {opacity:0},'b+0.6')
   .to(".group_xray .desc03",{y:-50, opacity:1},'b')
   .to(".group_xray .desc03", {y:-200,opacity:0},'b+0.6')
   .addLabel('c')
   .to(".group_xray .glitter_area img", {opacity:1},'c-=0.6')
   .to(".group_xray .glitter_area img", {duration:2, scale:0.78},'c')   
   .to(".group_xray .glitter_area video", {opacity:1, scale:1},'c-=0.1')
   .to(".group_xray .glitter_area .btn_control", {opacity:1},'c-=0.1');

✍ 코드 분석

  • 정해진 위치까지 fixed처럼 고정된 효과를 내주는 position: sticky 속성을 이용해 '.sticky_inner'가 30px에 도달하면 고정되도록 하였습니다.
  • addlabel 속성을 이용해 크게 모션을 3그룹으로 나누었고, 그 안에서도 디테일하게 딜레이를 주었는데 'img'의 사이즈가 작아지는 동안 'desc'요소 들이 움직이는 애니메이션으로 'img'에만 duration:2를 주어 움직임 속도를 맞추었습니다.
  • 스크롤 했을때 애니메이션이 빠르게 움직이는 느낌이 있어 end값을 150%로 길게 주었습니다.

💡 sticky 속성 이용시 주의점

상위 요소에 oveflow 속성이 있으면 동작하지 않음
top, left 등 임계값을 설정해야 함


4. 동영상 스크롤 컨트롤

📋 스크롤에 따라 동영상이 재생되도록 구현은 했으나 동영상이 시작되어야 할 위치가 아닌 페이지 제일 상단부터 동영상이 재생되는 이슈를 해결하기 위해 ScrollTrigger 이용해 코드 수정을 하였습니다.

◻️ SCRIPT 수정 전

const vid = document.getElementById('case_video');
 
window.onscroll = function(){
    vid.pause();
};

setInterval(function(){
    vid.currentTime = window.pageYOffset/400;
}, 40);

◻️ SCRIPT 수정 후

const vid2 = document.getElementById('case_video');
ScrollTrigger.create({
    trigger:".sc_case .group_case",
    start:"0% 0%",
    end:"70% 50%",
    scrub:1,
    onEnter:function(self){
        setInterval(function(){
            vid2.currentTime = self.progress.toFixed(3)*5.5;
        }, 40);
    },
});

✍ 코드 분석

  • getElementById()메서드로 id가case_video인 요소를 가져와 ScrollTrigger.create()로 강제 스크롤 이벤트를 생성하였습니다.
  • 위치에 도달 했을때 동영상을 실행시키기 위해 onEnter를 사용하였고, setInterval() 함수로 0.04초 간격으로 진행되도록 하였습니다.
  • vid2.currentTime = self.progress.toFixed(3)*5.5;기본은 1초의 애니메이션 진행이기 때문에 동영상 총길이인 5.5초를 곱해주었습니다.

💡 toFixed(): 소수자리 표현

toFixed(3) -> 소수점 3자리수 까지


4. 동영상 재생 버튼

📋 페이지 내 대부분의 동영상 우측 하단에 동영상 재생/일시정지 버튼이 있습니다. 공통 영역으로 코드를 작성해보았습니다.

◻️ HTML

<div class="video_inner video_frame">
	<div class="video_area">
		<video src="./assets/images/value_video.mp4" poster="./assets/images/value_video.jpg" autoplay muted loop></video>
	</div>
	<button class="btn_control" aria-label="동영상 일시정지"></button>
</div>

◻️ CSS

.btn_control::after {
    content: "";
    font-family: "SF Pro Icons";
    display: inline-block;
}
.btn_control.on::after {
    content: "";
}

◻️ SCRIPT

$('.btn_control').click(function(){
    if($(this).hasClass('on')){
        $(this).parents('.video_frame').find('video').get(0).play();
        $(this).attr('aria-label','정지').removeClass('on')
    }else{
        $(this).parents('.video_frame').find('video').get(0).pause();
        $(this).attr('aria-label','재생').addClass('on')
    }
});

✍ 코드 분석

  • 버튼의 부모 요소에 video_frame클래스를 주었고, 해당 클래스를 가지고 있는 부모 안에 video를 찾아 play 또는 pause를 시킨다.
    hasClass(),removeClass()로 'on'이라는 클래스를 재생, 일시정지에 맞춰 가상요소 after선택자에 넣어준 content를 바꾸어 준다.
  • 텍스트가 없는 버튼이라aria-label을 사용하여 버튼의 동작을 간략하게 설명하여 웹 접근성을 고려하였고, 위의 조건문에 함께 작성하여 버튼 클릭시 "정지"/"재생"으로 설명이 바뀔수 있도록 하였다.

💡 ::after 사용시 주의점

'content' 속성에 값을 꼭 지정해 주어야 한다.

profile
📚 Coding Notes

0개의 댓글