1-1) 라인 애니메이션
1-2) 백그라운드 무한루프 애니메이션
1-3) 백그라운드 canvas 리퀘스트 애니메이션
1-4) 인트로 애니메이션 gsap
html
<section class="sc-visual">
<div class="txt-area">
<h2 class="sc-title">
<span class="tit-msg">S</span>
<span class="tit-msg">t</span>
<span class="tit-msg">r</span>
<span class="tit-msg">y</span>
<span class="tit-msg">d</span>
<span class="tit-msg">s</span>
</h2>
<div class="line gradient-line6 end">
<div class="guage"></div>
</div>
<p class="visual-desc">
<span class="desc-txt1">We're <span class="gradient-txt1">all</span> in</span>
<span class="desc-txt2 pc">Health + Wellness + Accountability.</span>
<span class="desc-txt2 mobile">Health / Wellness / Accountability.</span>
</p>
<div class="line gradient-line1 start">
<div class="guage"></div>
</div>
<strong class="gradient-txt2">Mind</strong>
<div class="line gradient-line2 end">
<div class="guage"></div>
</div>
<strong class="gradient-txt3">Heart</strong>
<div class="line gradient-line3 start">
<div class="guage"></div>
</div>
<strong class="gradient-txt4">Body</strong>
<div class="line gradient-line4 end">
<div class="guage"></div>
</div>
</div>
<figure class="thumb-area"></figure>
</section>
라인의 위치를 고정시키고 기준점을 주어 애니메이션을 실행하기위해, 게이지에 라인이라는 부모요소를 한번 감싸주었다 => .line > .guage
scss
.line{
display: flex;
width: 100%;
height: 0.2vh;
margin: 5.5vh 0;
.guage {
width: inherit;
height: inherit;
}
&.end{
justify-content: flex-end;
}
&.start{
justify-content: flex-start;
}
&.center{
justify-content: center;
}
}
.gradient-line1 {
&>* {
@include gradi-Blue1;
}
}
.gradient-line2 {
&>* {
@include gradi-Blue2;
}
}
.gradient-line3 {
&>* {
@include gradi-Pink;
}
}
.gradient-line4 {
&>* {
@include gradi-Yellow;
}
}
.gradient-line5 {
&>* {
@include gradi-Purple;
}
}
.gradient-line6 {
&>* {
@include gradi-Green;
}
}
.line => 라인의 크기 및 위치
.gradient-lineN > * => 라인의 컬러
.end,.start,.center => 움직이는 라인의 기준점 설정
j-query gsap
textMotion = gsap.timeline({
scrollTrigger:{
trigger:'.sc-visual .gradient-txt2',
start:'top 90%',
end:'150% -100%',
scrub:1
}
})
textMotion.addLabel('a')
.from('.sc-visual .gradient-line6 .guage',{width:"0%",duration:2},'a-=0.1')
.from('.sc-visual .gradient-line1 .guage',{width:"0%",duration:2},'a+=0.3')
.from('.sc-visual .gradient-line2 .guage',{width:"0%",duration:2},'a+=0.5')
.from('.sc-visual .gradient-line3 .guage',{width:"0%",duration:2},'a+=0.7')
.from('.sc-visual .gradient-line4 .guage',{width:"0%",duration:2},'a+=0.9')
gsap.from을 활용하여 width값을 0 -> 100% 설정해주면 게이지가 차오르는 애니메이션 효과를 줄 수 있다. css에서 라인의 기준점을 주었기때문에, .start는 왼쪽에서 오른쪽으로, end는 오른쪽에서 왼쪽으로, .center는 가운데의 기준점이 되어 애니메이션이 실행된다.
html
백그라운드를 코딩하고, 똑같은 백그라운드 코드를 복제한 뒤, 부모요소로 묶어 백그라운드 위치를 고정 및 백그라운드 배치를 한 후, 키프레임 애니메이션과 translateX를 활용하여 무한 루프 애니메이션을 구현한다.
scss
.send-bg-wrap{
position: absolute;
top: 0;
left: 0;
// 배경이니까 absolute
z-index: -1;
display: flex;
width: 100%;
height: 100vh;
animation: rolling 20s infinite linear;
}
.send-bg {
position: relative;
display: flex;
flex: 1 0 100%;
& > * {
flex: 1;
}
.ic-img {
flex-basis: 50%;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
z-index: 3;
display: inline-block;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.89);
}
}
// 백그라운드 css
@keyframes rolling {
0% {
transform: translateX(0%);
}
100% {
transform: translateX(-100%);
}
}
// 백그라운드 애니메이션
스크롤 시, 마치 동영상처럼 움직이는 이미지를 구현하기 위해서 html canvas
태그를 사용하고, 스크립트로 움직임을 구현해야한다.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d'); // 2d이미지
canvas.width = 575;
canvas.height = 696; // 캔버스크기
const frameCount = 45; // 프레임카운트 45장
const currentFrame = (idx) => {
return `https://www.apple.com/105/media/us/airpods-max/2020/996b980b-3131-44f1-af6c-fe72f9b3bfb5/anim/turn/large_2x/large_2x_${idx.toString().padStart(4, '0')}.jpg`; // 자릿수치환 0001~
}; // 리턴 필수
const images = [];
const card = {
frame: 0,
};
for (let i = 0; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i + 1);
images.push(img);
} // 반복문 통해서 프레임 수 증가
gsap.to(card, {
frame: frameCount - 1,
snap: 'frame',
ease: 'none',
scrollTrigger: {
trigger: '.sc-together',
scrub: 1,
start: 'top top',
end: 'bottom bottom', //+=500% 내 영역이 5배만큼 가상스크롤
//markers:true,
// pin:true -> 고정
// 리퀘스트 애니메이션
},
onUpdate: render,
});
images[0].onload = render;
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(images[card.frame], 0, 0 ,575,696);
}
const introAni2 = gsap.timeline({
paused:true
})
introAni2.addLabel('a')
.from('.sc-visual .sc-title .tit-msg',{opacity:0, yPercent:103, stagger:0.2,duration:0.6},'a')
.from('.sc-visual .thumb-area',{top:"-70%",duration:1},'a') // top컨트롤
.from('.header',{opacity:0,duration:1.2},'a')
gsap.set('.loading .guage',{width:"100%"})
const introAni = gsap.timeline({
onComplete:function(){
introAni2.play();
}
})
introAni.addLabel('a')
.to('.loading .guage', {width:0,delay:1},'a')
.to('.loading',{opacity:0,delay:1.5,duration:1,display:'none'},'a')
introAni -> introAni2 순서로 인트로 애니메이션을 구현할 때,
introAni2를 먼저 선언하고, paused:true를 통해 멈춤상태로 유지하다가
introAni를 선언하고 introAni의 애니메이션이 끝나면 introAni2를 실행하기 위해 play()를 사용한다.