B2B회원의 숙소 예약이 확정되면, 입실 시각 전까지는 업무예약과 일반예약을 변경할 수 있는 기능이 추가되었다. 추가된 화면에는 디자인 팀에서 제공한 Lottie(로띠) 파일을 삽입해야 했는데, 여기에서 로띠란 After Effect로 만들어낸 움짤 같은 json 파일을 실행시켜주는 라이브러리다. 대표적인 로띠 라이브러리에는 vue-lottie
와 lottie-vuejs
두 가지가 있는데 나는 둘 중 현재 이용자가 많은 vue-lottie
를 선택해서 적용했다.
로띠 움짤을 스위치기능 말고 다른 용도로 사용해본 적이 없어서 잘은 모르겠지만, 스위치로 쓸 로띠를 적용하는 과정으로는 크게 두 과정으로 나뉜다. 1. 일단 불러와서 움짤이 무한 작동하는지 확인하기, 2. API 문서를 참고하여 내장 메서드로 원하는 기능에 맞게 커스텀하기 이다. 그냥 로띠가 움짤로서의 역할만 하면 되는 상황이라면 전자까지만 해도 될 것 같다.
https://lottiefiles.com/recent
디자인팀이나 인터넷에서 받아 온 로띠 json 파일은 이 곳에서 플레이해볼 수 있다.
https://lottiefiles.com/preview
이렇게만 보면 정신없는데 애초에 짧은 영상 움짤이라 이렇게 무한재생되는 게 맞다.
https://www.npmjs.com/package/vue-lottie
npm install --save vue-lottie
package.json의 dependencies 확인하기.
"vue-lottie": "^0.2.1",
// template
<lottie
:options="options"
:height="31"
:width="51"
@animCreated="handleAnimation"
/>
// script
import Lottie from 'vue-lottie';
import * as animationData from '@/assets/json/switch_blue.json';
export default {
name: 'SwitchLottie',
components: {
'lottie': Lottie
},
data() {
return {
options: {animationData: animationData},
animationSpeed: 1
}
},
methods: {
handleAnimation: function (anim) {
this.anim = anim;
},
...
불러온 로띠 파일은 프레임을 0부터 20까지로 보자면, 0은 스위치가 꺼져 있는 상태, 20은 스위치가 완전히 활성화된 상태다. 따로 활성화된 상태에서 꺼진 상태를 나타내는 로띠 파일을 받지 않았기 때문에 사용자경험 상 스위치로서의 역할을 하려면 스위치를 켤 때는 프레임을 0에서 20으로, 끌 때는 20에서 0으로 자연스럽게 작동하도록 커스텀해야 했다.
이제 불러온 로띠가 정신없이 무한재생하는 것을 확인했으니, 얌전히 스위치 기능만을 수행하도록 만들어줘야 한다. 나는 아래의 문서를 참고하여 작업했다.
https://github.com/airbnb/lottie-web
기업회원이 이전에 숙소를 업무 용도로 예약을 했다면 예약페이지에 처음 접근했을 때 업무 스위치가 켜진 상태로 멈춰있어야 한다. 나중에 다시 일반 업무로 바꾸고 싶으면 스위치를 끌 수 있록 만들어야 하기 때문이다. 반대로 기업회원이 잘 모르고 일반 용도로 예약을 했다가 업무 용도로 바꾸어야 할 때도 마찬가지다. 따라서 로띠 컴포넌트를 import하는 부모 컴포넌트에서 props로 businessTripStatus
상탯값을 넘겨주었다. businessTripStatus
는 부모의 부모 컴포넌트에서 비동기로 받아온 예약데이터의 일부다. 간단히 표현해보자면 아래와 같다.
// 부모 컴포넌트
// template
...
<switch-lottie
:parentStatus="businessTripStatus"
/>
...
// script
props: {
businessTripStatus: Boolean,
isB2bUser: Boolean,
order: {
biztrip: Boolean,
},
},
// 자식(로띠) 컴포넌트
// script
data: () => {
return {
options: {
animationData: animationData.default,
autoplay: false,
loop: false,
},
};
},
props: {
parentStatus: {
type: Boolean,
required: true,
},
},
mounted() {
if (this.parentStatus) {
this.anim.setSpeed(1);
this.anim.play();
}
},
로띠 옵션에 autoplay
와 loop
속성값을 일단 false로 두고, 업무예약 상태일 때만 play를 하도록 만들어야 한다. 부모 컴포넌트에서는 현재 예약 state를 props로 넘겨주었고, 자식인 로띠 컴포넌트에서는 그 props에 따라서 mount되는 시점에 로띠를 1회 play한다. this.anim.setSpeed()
메서드는 json 움짤 프레임이 실행되는 속도를 지정해주는 역할을 한다.
이건 작은 디테일이지만 페이지 마운트 됐을 때 애초부터 프레임 위치를 맨 뒤로 두는 것도 괜찮지만 사용자에게 스위치가 활성화됐음을 인지하게 하고 싶은 생각에 mounted 상태에서 this.anim.play()
가 뿅 하고 실행되도록 만들었다.
// 부모 컴포넌트
// temlate
<switch-lottie
@click="businessOrderToggle"
:parentStatus="businessTripStatus"
:isChanged="isChanged"
/>
클릭기능을 만들기 위해 부모 컴포넌트에 businessOrderToggle
이라는 메서드를 만들어서 자식 컴포넌트로 넘겨준다. 이 메서드에서는 예약정보를 비동기로 변경하고, 응답 결과에 따라 성공과 실패를 처리해주는 역할을 한다. businessOrderToggle
를 클릭했을 때 값이 '변경'이 되었다면 isChanged: true
, 실패했으면 isChanged: false
이다.
// 자식 로띠 컴포넌트
// script
props: {
parentStatus: {
type: Boolean,
equired: true,
},
isChanged: {
type: Boolean,
required: true,
},
},
methods: {
handleAnimation(anim) {
this.anim = anim;
},
toggle() {
this.$emit('click');
},
},
mounted() {
if (this.parentStatus) {
this.anim.setSpeed(1);
this.anim.play();
}
},
watch: {
parentStatus: function (parentStatus) {
if (this.isChanged) {
parentStatus ? this.anim.setSpeed(1) : this.anim.setSpeed(-1);
this.anim.play();
}
},
},
로띠를 클릭했을 때 반대 방향으로 실행하려면 this.anim.setSpeed()
에 -1 값을 넣어야 한다. 음수를 넣으면 방향이 반대가 된다. 방향을 반대로 한 후 play를 하지 않고, 단순히 프레임을 맨 앞으로 두는 this.anim.goToAndStop()
등의 메서드를 쓰면 UX상 스위치를 실행하는 느낌보다는 뚝뚝 끊기는 느낌이 들어서 로띠를 쓰는 이유가 없어지기 때문에 프레임을 반대로 실행하는 this.anim.setSpeed(-1)
를 사용하였다.
여기서 methods 내의 toggle
메서드에서 바로 애니메이션을 클릭하지 않고 특정 상탯값을 지켜보다가 변화가 있을 때 코드를 실행하는 watch를 사용했다. 그 이유는 클릭을 하여 부모의 businessOrderToggle
메서드를 실행시킨다고 해서 꼭 상태의 변화가 있다고 보장할 수 없기 때문이다. 비동기처리를 하다가 실패를 하면 클릭을 했음에도 제대로 된 응답을 받지 못한 상태로 남아있는데 스위치만 실행되는 상황이 발생할 수 있다. 따라서 부모 컴포넌트의 data에 따로 isChanged
를 추가하여, 그 값을 지켜보는 방식을 사용했다.
잘 작동이 된다.