텍스트 애니메이션

데모

사용하고 있는 주요 기능

클래스와 스타일 데이터 바인딩 62페이지

리스트 트랜지션 205페이지

산출 속성(computed) 120페이지

워처(watch) 128페이지

소스 코드

index.vue
<template>
  <div class="example">
    <h3>TextAnime1 <button @click="anime1=!anime1">변경하기</button></h3>
    <TextAnime1 v-if="anime1"/>
    <p>SCSS로 애니메이션 지연 주기</p>
    <hr>
    <h3>TextAnime2 <button @click="anime2=!anime2">변경하기</button></h3>
    <TextAnime2 v-if="anime2"/>
    <p>바인딩으로 애니메이션 지연 주기</p>
    <hr>
    <h3>TextAnime3</h3>
    <p><label><input type="checkbox" v-model="autoplay"> 5초마다 자동으로 문자 변경하기</label></p>
    <TextAnime3 :autoplay="autoplay"/>
    <p><code>transition-group</code><code>v-move</code> 를 사용했습니다.</p>
  </div>
</template>

<script>
import TextAnime1 from './TextAnime1'
import TextAnime2 from './TextAnime2'
import TextAnime3 from './TextAnime3'
export default {
  components: {
    TextAnime1,
    TextAnime2,
    TextAnime3
  },
  data() {
    return {
      anime1: true,
      anime2: true,
      autoplay: true
    }
  }
}
</script>

TextAnime1

문자 수가 적어서 스타일을 조합해서 애니메이션을 만들고자 하는 경우에는 이를 활용하는 것이 좋습니다.

TextAnime1.vue
<template>
  <div class="TextAnime1">
    <span v-for="(t, index) in text" :key="index" v-text="t" class="item delay-anime"/>
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: '고양이도 할 수 있는 Vue.js'
    }
  }
}
</script>

<style lang="stylus" scoped>
@keyframes text-in {
  0% {
    transform: translate(0, -20px);
    opacity: 0;
  }
}

.item {
  display: inline-block;
  min-width: 0.3em;
  font-size: 2rem;
  animation: text-in 0.8s cubic-bezier(0.22, 0.15, 0.25, 1.43) 0s backwards;
}

for co in 0 .. 12 {
  .delay-anime:nth-child({co + 1}) {
    animation-delay: co * 100ms + 200ms;
  }
}
</style>

당연히 고정 문자열이라면 v-for가 아니라 정적 콘텐츠를 바로 넣어서 사용하는 것이 성능면에서 더 낫습니다.

TextAnime2

문자 수가 많고, 변화가 많을 수 있는 경우라면 이를 활용하는 것이 좋습니다.

TextAnime2.vue
<template>
  <div class="TextAnime1">
    <span
      v-for="(t, index) in text"
      :key="index"
      class="item"
      :style="{animationDelay: index*100+'ms'}"
      v-text="t"
      />
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: '고양이도 할 수 있는 Vue.js'
    }
  }
}
</script>

<style scoped>
@keyframes text-in {
  0% {
    transform: translate(0, -20px);
    opacity: 0;
  }
}
.item {
  display: inline-block;
  min-width: 0.3em;
  font-size: 2rem;
  animation: text-in .8s cubic-bezier(0.22, 0.15, 0.25, 1.43) 0s backwards;
}
</style>

TextAnime3

키가 같다면 v-move가 적용되는 것을 사용해서 문자와 인덱스를 조합해서 키를 생성하고 있습니다.

문자 수가 많으면 비용이 약간 높아질 수 있으므로 인스턴스 초기화와 메시지 편집이 일어날 때, 미리 분자열을 분석하고 키를 생성해 두는 점을 주목해 주세요.

TextAnime3.vue
<template>
  <div class="TextAnime1">
    <textarea v-model.lazy="editor" style="width:80%;height:40px;"></textarea>
    <transition-group tag="div" class="title">
      <span v-for="el in text" :key="el.id" class="item" v-text="el.text"/>
    </transition-group>
  </div>
</template>

<script>
export default {
  props: {
    autoplay: Boolean
  },
  data() {
    return {
      timer: null,
      index: 0,
      // 원본 메시지
      original: [
        '기능별로 Vue.js를 설명하는 입문서입니다. Vue.js를 한 번도 다루어 본 적이 없는 분과 이미 Vue.js를 사용하고 있는 분 모두 즐겁게 책을 볼 수 있을 것입니다.',
        'Vue.js는 직관적으로 사용할 수 있는 내용이 많습니다. 그래서 어떻게든 작동이 되니 자세한 이해 없이 코드를 작성하는 경우가 많습니다. 많이들 실수하는 코드를 살펴보며 어떠한 장점과 단점이 있는지 분석하는 내용도 담고 있습니다.',
        'Vue.js는 직관적으로 사용할 수 있는 내용이 많습니다. 그래서 어떻게든 작동이 되니 자세한 이해 없이 코드를 작성하는 경우가 많습니다. 많이들 실수하는 코드를 살펴보며 어떠한 장점과 단점이 있는지 분석하는 내용도 담고 있습니다.'
      ],
      // 분해한 메시지
      messages: [],
      text: ''
    }
  },
  computed: {
    editor: {
      get() { return this.text.map(e => e.text).join('') },
      set(text) { this.text = this.convText(text) }
    }
  },
  watch: {
    autoplay(val) {
      clearTimeout(this.timer)
      if (val) {
        this.ticker()
      }
    }
  },
  methods: {
    // 데모 전용 타이머
    ticker() {
      this.timer = setTimeout(() => {
        if (this.autoplay) {
          this.index = this.index < this.messages.length-1 ? this.index + 1 : 0
          this.text = this.messages[this.index]
          this.ticker()
        }
      }, 5000)
    },
    // 텍스트를 분리해서 객체로 리턴하기
    convText(text) {
      const alms = {}
      const result = text.split('').map(el => {
        alms[el] = alms[el] ? ++alms[el] : 1
        return { id: `${el}_${alms[el]}`, text: el }
      })
      return Object.freeze(result) // 감시하지 않음
    }
  },
  created() {
    this.messages = this.original.map(el => this.convText(el))
    this.text = this.messages[0]
    this.ticker()
  }
}
</script>

<style scoped>
.title {
  font-size: 2rem;
}
.item {
  display: inline-block;
  min-width: 0.3em;
}
/* 트랜지션 전용 스타일 */
.v-enter-active,
.v-leave-active,
.v-move {
  transition: all 1s;
}
.v-leave-active {
  position: absolute;
}
.v-enter,
.v-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
</style>