[Vue.js] Vue 애니메이션(Animate) #3 - GSAP 3
하수도키
·2021. 5. 1. 23:17
[Vue.js] Vue 애니메이션(Animate) #3 - GSAP 3
- 이번 포스팅은 Vue 애니메이션에 대해 알아보자.
- 애니메이션을 이용해 사용자 경험을 향상할 수 있다.
- 버튼 같은 곳에 마우스 호버 상태 일 때 클릭률을 높이거나
- 강조해야 되는 문구, 영역 등에 애니메이션을 적용해 집중력을 높일 수 있다.
목차
- Transition
- Page Transition
- Transition Group
- Javascript Hooks + Velocity
- GSAP(이번 포스팅)
개요
이전 포스팅에서는 Velocity.js
라이브러리를 사용했다.
이번 포스팅에선 GSAP(Green Sock Animation Platform)
강력하고 인기 있는 애니메이션 라이브러리를 Vue
에 적용하고 사용하는 법을 알아보자.
GSAP이란?(What's GSAP?)
GSAP은 높은 성능과 굳건한 라이브러리이다. 커뮤니티에서 애니메이션 관련 검색하면 쉽게 찾을 수 있다.
이 전 라이브러리는 4개의 섹션(TweenLite, TweenMax, TimelineLite, TimelienMax)
쪼개졌으나 GSAP3이 나오면서 1개의 섹션으로 구성되어 있다.
작동방식은 애니메이션이 될 타겟을 지정하고 애니메이션 옵션도 같이 객체로 넘기면 된다.
말로는 그만 설명하고 코드를 보면서 살펴보자.
첫 GSAP 애니메이션(Our First GSAP Animation)
GSAP
를 하기 전에 우선 기본 예제 코드를 살펴보자.
<template>
<transition appear @before-enter="beforeEnter" @enter="enter" :css="false">
<div class="card"></div>
</transition>
</template>
<script>
export default {
methods: {
beforeEnter(el) {
// starting style
},
enter(el, done) {
// style to transition to once entered
},
},
};
</script>
<style scoped>
.card {
display: block;
margin: 0 auto 0 auto;
height: 6.5em;
width: 6.5em;
border-radius: 1%;
background-color: #16c0b0;
box-shadow: 0.08em 0.03em 0.4em #ababab;
}
</style>
transition
컴포넌트를 사용하고card
div를 감싼다.appear
속성을 사용해 첫 로드시에도 애니메이션을 작동한다.css:false
로adding/removing
클래스 속성이 추가 안되게 한다.Javascript Hooks
beforeEnter, enter
를 사용- 자세한 내용은 이전 포스팅을 참고하자.
- 예제 코드 : https://codesandbox.io/s/dreamy-ramanujan-7kyfo?file=/src/components/Stagger.vue
이제 GSAP을 적용해보자.
GSAP
을import
beforeEnter
에 보이기 전 스타일 정의enter
에gsap
을 이용해 보일 때 애니메이션 정의
<script>
import gsap from 'gsap'
export default {
...,
methods: {
beforeEnter(el) {
el.style.opacity = 0
el.style.transform = 'scale(0,0)'
},
enter(el, done) {
gsap.to(el, {
duration: 1,
opacity: 1,
scale: 1,
onComplete: done
})
}
}
</script>
gsap
을 사용한 코드를 보면 to
메서드를 사용했다.
첫 번째 인자는 애니메이션이 될 타겟 element
를 넣었고, 두 번째 인자에는 타겟이 종료될 때 스타일과 애니메이션 관련 속성들, 그리고 애니메이션이 완료될 때 실행할 메서드를 전달할 수 있는 속성(onComplete
)도 있습니다. 이를 통해 transition
컴포넌트가 이번 Lifecycle
이 완료되었음을 알고 다음 hook
으로 이동할 수 있다.
지금도 애니메이션 잘 작동하지만 ease
속성을 이용해 카드가 튀어 오르는 효과를 넣어보자.
enter(el, done) {
gsap.to(el, {
duration: 1,
opacity: 1, s
cale: 1,
ease: 'bounce.out', onComplete: done
})
}
easing 옵션은 여기 링크를 참조하자.
https://greensock.com/docs/v2/Easing
Stagger Elements
Stagger 효과를 내는 법을 알아보자. 모르겠는데 휘청휘청한 효과라고 한다.
라이브러리를 사용해 애니메이션을 구축할 땐, transition/transition-group
컴포넌트를 굳이 사용하지 않아도 된다.
이번 예제는 mounted
에서 처리해보자.
<template>
<div id="container">
<div v-for="card in cards" :key="card.id" class="card"></div>
</div>
</template>
<script>
import gsap from "gsap";
export default {
data() {
return {
cards: [
{ id: 1298 },
{ id: 8748 },
{ id: 4919 },
{ id: 5527 },
{ id: 9428 },
{ id: 7103 },
],
};
},
mounted() {
// stagger cards into position
gsap.from(".card", {
duration: 0.5,
opacity: 0,
scale: 0,
y: 200,
ease: "power1",
stagger: 0.1,
});
},
};
</script>
<style scoped>
#container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
}
.card {
height: 6.5em;
width: 6.5em;
border-radius: 1%;
background-color: #16c0b0;
box-shadow: 0.08em 0.03em 0.4em #ababab;
padding-top: 1em;
margin-top: 0.5em;
margin-right: 0.5em;
}
</style>
gsap.from
을 사용한 이유는 보이기 전 시작할 때 정보를 제공한다.
첫 화면에 보일 때 스타일 지정하고 마지막은 기본 스타일로 보인다.
gsap
에 애니메이트 될 타깃하고 애니메이션 속성들을 전달해주면 된다.
stagger
속성은 다음 카드가 애니메이션 될 때 지연되는 시간을 입력하면 된다.
여기까지는 평범한 stagger
좀 더 다양한 효과를 추가해보자.
예제 코드 : https://codesandbox.io/s/dreamy-ramanujan-7kyfo?file=/src/components/Stagger.vue
mounted() { gsap.from('.card', { duration: 0.5, opacity: 0, scale: 0, y: 200,
ease: 'power1', stagger: { from: 'edges', each: 0.1 } }) }
위와 같이 stagger
속성에 객체로 더 추가하면 다양한 효과를 낼 수 있다.
아래 링크를 통해 다양한 속성을 알 수 있다.
https://codepen.io/GreenSock/full/vYBRPbO
상태 변경에 따른 GSAP 활용하기(State with GSAP)
실시간 점수 업데이트 또는 회사 주가 업데이트가 될 경우 데이터 상태에 따라 멋지게 보이는 방법을 살펴보자. GSAP 내부적으로 tweens
를 사용한다.
(트위닝이란 애니메이션의 시작과 끝 장면의 그림이 있으면 그 중간 장면들은 자동으로 생성하여 연결되는 방식입니다.)
예를 들면 1에서 10까지 값이 있으면, 2,3,4,5,6,7,8,9 값들이 중간에 존재한다. GSAP을 이용해 이 값 사이를 트위닝 할 수 있다.
예제 코드를 보면서 살펴보자.
<template>
<div>
<div :style="{ width: number + 'px' }" class="bar">
<span>{{ number }}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
number: 0,
};
},
methods: {
randomNumber() {
this.number = Math.floor(Math.random() * (800 - 0));
},
},
created() {
setInterval(this.randomNumber, 1500);
},
};
</script>
<style scoped>
.bar {
padding: 5px;
background-color: #2c3e50;
border: 1px #16c0b0 solid;
min-width: 20px;
}
.bar span {
color: white;
}
</style>
created
라이프 사이클에 setInterval
설정을 해서 1.5초마다 number
를 갱신한다.
number
를 div
style
에 바인딩한다.
number
가 변경될 때마다 div
width
값이 변경된다.
지금은 애니메이션 적용이 되지 않은 상태이다. 이제 GSAP
를 적용해 부드러운 애니메이션 효과를 넣어보자.
완성된 예제 코드를 먼저 살펴보고 차례차례 알아보자
<template>
<div>
<div :style="{ width: tweenedNumber.toFixed(0) + 'px' }" class="bar">
<span>{{ tweenedNumber.toFixed(0) }}</span>
</div>
</div>
</template>
<script>
import { gsap } from "gsap";
export default {
data() {
return {
number: 0,
tweenedNumber: 0,
};
},
watch: {
number(newValue) {
gsap.to(this.$data, {
duration: 1,
ease: "circ.out",
tweenedNumber: newValue,
});
},
},
methods: {
randomNumber() {
this.number = Math.floor(Math.random() * (800 - 0));
},
},
created() {
setInterval(this.randomNumber, 1500);
},
};
</script>
<style scoped>
.bar {
padding: 5px;
background-color: #2c3e50;
border: 1px #16c0b0 solid;
min-width: 20px;
}
.bar span {
color: white;
}
</style>
우선 gsap
를 import
한다.
그 다음, watch
를 사용해 number
가 업데이트 될때마다 필요한 로직을 작성하자.
[gsap.to](http://gsap.to)
를 사용해 data
에 정의된 tweenedNumber
을 업데이트 하면 tweenedNumber
가 bar
width
가 변경되어 애니메이션이 된다.
watch: { number(newValue) { gsap.to(this.$data, {}) } },
기존에는 첫번째 전달 인자에 애니메이션이 될 element
를 넣었는데 여기서는 this.$data
를 넣었다. 이제 data
에 접근도 가능하고 업데이트도 가능하다.(여기서는 tweendNumber
를 업데이트한다.) 그리고 두 번째 전달 인자에는 기존과 같이 애니메이션 정보를 객체 형식으로 넣어주면 된다.
예제 코드 : https://codesandbox.io/s/crazy-sara-q0b8g?file=/src/components/HelloWorld.vue:0-856
GSAP을 이용해 Timelines 구현하기(Timelines with GSAP)
GSAP을 사용하면서 점점 애니메이션 복잡성이 올라갈 때가 있다.(다수의 엘리먼트들이 순서대로 애니메이션 효과를 보여줘야 할 때)
이때, GSAP에 timeline 기능을 사용하면 구현이 가능하다.
완성된 예제 코드를 우선 보고 하나하나씩 살펴보자.
<template>
<div>
<img
class="runner first"
width="300px"
src="https://image.freepik.com/free-icon/running-man_318-1564.jpg"
alt="runner"
/>
<img
class="runner second"
width="300px"
src="https://image.freepik.com/free-icon/running-man_318-1564.jpg"
alt="runner"
/>
<img
class="runner third"
width="300px"
src="https://image.freepik.com/free-icon/running-man_318-1564.jpg"
alt="runner"
/>
</div>
</template>
<script>
import gsap from "gsap";
export default {
mounted() {
// timeline will go here
let tl = gsap.timeline();
tl.to(".first", { x: 700, duration: 2, ease: "expo.out" });
tl.to(".second", { x: 700, duration: 2, ease: "expo.out" });
tl.to(".third", { x: 700, duration: 2, ease: "expo.out" });
},
};
</script>
<style scoped>
.runner {
display: block;
height: 5em;
width: 5em;
margin-top: 1.5em;
}
</style>
우선 mounted
에서 타임라인 로직을 넣어 dom
이 모두 렌더링이 되었을때 실행되게 하자.
mounted() {
// timeline will go here
let tl = gsap.timeline(); // gsap timeline을 호출한다.
tl.to(".first", { x: 700, duration: 2, ease: "expo.out" });
tl.to(".second", { x: 700, duration: 2, ease: "expo.out" });
tl.to(".third", { x: 700, duration: 2, ease: "expo.out" }); },
to
메소드 첫 번째 인자의 해당 element
를 넣고 두번째 인자의 애니메이션 옵션을 넣는다.
x
는 시작 위치에서 x축으로 700px 이동하라는 속성이고, duration
지속시간이다.
위 예제를 실행시키면 순서대로 차례차례 애니메이션 이동된다.
.first
요소가 움직이고 그 다음 .second
요소가 움직이고 마지막으로 .third
요소가 움직인다.
총 6초가 소요된다.
이제 순서대로 말고, 애니메이션 시작하는 시간을 변경하는 방법을 알아보자.
2가지 방법이 있다. Absolute Position
과 Relative Position
이 있다.
(CSS에서 사용하는 용어랑 같아서 혼란이 있을 수 있다. 전혀 다른 거니깐 연관 짓지 말자.)
mounted() {
...
tl.to('.first', { x: 200, duration: 2, ease: 'expo.out' })
tl.to('.second', { x: 400, duration: 2, ease: 'expo.out' }, 0.5) // now with an absolute position
tl.to('.third', { x: 600, duration: 2, ease: 'expo.out' }) }
우선 Absolute Position
예제를 보면 0.5
즉, 0.5초 뒤에 실행한다는 뜻이다.
어떤 조건이든 상관없이 mounted
되자마자 0.5초뒤에 .second
애니메이션 시작이다.
mounted() {
...
tl.to('.first', { x: 200, duration: 2, ease: 'expo.out' })
tl.to('.second', { x: 400, duration: 2, ease: 'expo.out' }, '-=.75')
tl.to('.third', { x: 600, duration: 2, ease: 'expo.out' }) }
이번에는 Relative Position
이다. 첫번째 .first
요소가 애니메이션 끝나기전 0.75
초전에 second
요소 애니메이션이 실행된다.
이번에는 .first
요소가 끝나거나 시작할때 .second
요소도 같이 애니메이션을 시작하고 싶다.
여태까지 살펴본 내용으로 .first
랑 .second
동시에 시작하기 위한 코드는 아래와 같이 작성한다.
mounted() {
... tl.to('.first', { x: 200, duration: 2, ease: 'expo.out' })
tl.to('.second', { x: 400, duration: 2, ease: 'expo.out' }, '-=2')
tl.to('.third', { x: 600, duration: 2, ease: 'expo.out' }) }
작동은 잘된다. 하지만 .first
에 duration
값이 변경될 경우 그에 맞게 다시 -=2
이쪽 부분을 수정해야 한다. 너무너무 번거롭다.
이때 <
이걸 사용하면 손쉽게 해결이 된다.
mounted() {
...
tl.to('.first', { x: 200, duration: 2, ease: 'expo.out' })
tl.to('.second', { x: 400, duration: 2, ease: 'expo.out' }, '<') // starts when first tween starts
tl.to('.third', { x: 600, duration: 2, ease: 'expo.out' }, '<') // starts when second tween starts, which is when first tween starts }
<0.5
이렇게 사용하면 0.5
초 뒤에 실행한다.
mounted() {
...
tl.to('.first', { x: 200, duration: 2, ease: 'expo.out' })
tl.to('.second', { x: 400, duration: 2, ease: 'expo.out' }, '<0.5') // What will this do?
tl.to('.third', { x: 600, duration: 2, ease: 'expo.out' }) }
.first
가 실행되고 0.5초뒤에 .second
가 실행된다.
타임라인에 대한 다양한 position 옵션을 살펴보려면 이 링크를 참조하자.https://greensock.com/docs/v3/GSAP/Timeline
타임라인 반복 기능은 repeat
속성을 사용해 정의한다.
gsap.timeline({ repeat: 2 }) // 2번 반복
gsap.timeline({ repeat: -1 }) // 무한 반복
gsap.timeline({ repeat: -1, repeatDelay: 1 }) // 반복시 딜레이 1초
마지막으로, 여러 개의 타임라인 애니메이션을 사용하는 방법을 살펴보자.
완성된 예제 코드부터 우선 살펴보자.
<template>
<div>
<div id="container">
<img class="paws" id="paw1" src="../assets/paws.png" alt="fox-paws" />
<img class="paws" id="paw2" src="../assets/paws.png" alt="fox-paws" />
<img class="paws" id="paw3" src="../assets/paws.png" alt="fox-paws" />
<img class="paws" id="paw4" src="../assets/paws.png" alt="fox-paws" />
<img id="fox" src="../assets/fox.png" alt="fox-logo" />
</div>
<button @click="play">Play</button>
</div>
</template>
<script>
import gsap from "gsap";
let masterTL = gsap.timeline();
export default {
methods: {
play() {
masterTL.add(this.pawsTL());
masterTL.add(this.foxTL());
masterTL.play();
},
pawsTL() {
let tl = gsap.timeline();
tl.to("#paw1", {
opacity: 4,
scale: 1,
duration: 0.5,
ease: "bounce.out",
});
tl.to(
"#paw2",
{ opacity: 1, scale: 1, duration: 0.5, ease: "bounce.out" },
"<.3"
);
tl.to(
"#paw3",
{ opacity: 1, scale: 1, duration: 0.5, ease: "bounce.out" },
"<.3"
);
tl.to(
"#paw4",
{ opacity: 1, scale: 1, duration: 0.5, ease: "bounce.out" },
"<.3"
);
return tl;
},
foxTL() {
let tl = gsap.timeline();
tl.to(
"#fox",
{
opacity: 1,
filter: "blur(0)",
scale: 1,
duration: 0.4,
ease: "slow",
},
"<.2"
);
return tl;
},
},
};
</script>
<style scoped>
#container {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 5em;
}
#fox {
height: 8em;
width: 8em;
opacity: 0;
filter: blur(2px);
}
.paws {
transform: scale(0);
width: 2.5em;
height: 2.5em;
margin-top: 50px;
margin-right: 0.8em;
opacity: 0;
}
button {
margin-top: 5em;
}
</style>
.paws
타임라인이랑 #fox
타임라인 2개를 사용할때 코드이다.
masterTL
이라는 타임라인을 생성하고 거기에 add
메서드를 이용해 타임라인을 추가하면 된다.
play() {
masterTL.add(this.pawsTL())
masterTL.add(this.foxTL())
masterTL.play()
},
이 부분이 핵심이다.
각 타임라인을 추가한 다음 play()
메서드를 사용해 실행한다.
이렇게 하면 모듈화가 가능하고 재사용성도 좋고 확장성도 좋아진다.
결론
이 포스팅을 마지막으로 Vue 애니메이션 살펴보기를 마친다.
애니메이션 관련된 기초적인 부분들로만 다뤘고 좀 더 깊은 내용을 살펴보려면 해당 공식 사이트를 참고하여 살펴보는 것을 추천합니다.
이 포스팅을 바탕으로 좋은 애니메이션을 Vue에서 잘 구현하길 바랍니다.
'개발일기 > Vue.js' 카테고리의 다른 글
[Vue.js] 이벤트 전파(버블링, 캡쳐링) 살펴보기 (0) | 2021.08.25 |
---|---|
[Vue.js] Vue 애니메이션(Animate) #2 - Javascript Hooks, Velocity (0) | 2021.04.19 |
[Vue.js] Vue 애니메이션(Animate) #1 - Transition (1) | 2021.04.11 |
[Vue.js] Portal-Vue 라이브러리 사용하기 (0) | 2021.03.26 |
[Vue.js] Vue3 + TypeScript #6 마지막 커스텀 타입 정의하기(Type Assertion, data, props, computed, methods) (0) | 2021.03.14 |