😊

「Vue+TypeScriptでランダムな言葉を表示する方法」

に公開

どうしてランダムテキストを?

仕事などしていて文字をランダムに表示して最後特定の文字にしたい、という要望を目にして自分で実装ではなかったものの、最近Vueの勉強してるので勉強も込めて作ってみようかな、と思い製作しました。

実装

Vue 3 + Composition API + TypeScriptで書いています。

<template>
  <div>
    <p class="slot-text" ref="slotText" :class="{'is-animating': isAnimating}">{{ displayText }}</p>
  </div>
</template>

<script setup lang="ts">
  import { ref, onMounted, onBeforeUnmount } from 'vue';

  const isAnimating = ref(false);

  const finalText: string = 'Moomin';
  // 英数字からランダム文字を生成
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  
  const displayText = ref<string>('');
  let intervalId: number | null = null;

  function getRandomChar(length: number): string {
    let result = '';
    for (let i = 0; i < length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
  }

  // スロットのアニメーションを開始
  function startSlot(): void {
    let count  = 0;
    intervalId = window.setInterval(() => {
      if( count < 15) {
        displayText.value = getRandomChar(finalText.length); // ランダム文字を表示
        triggerAnimation();
        count++;
      } else {
        clearInterval(intervalId!);
        displayText.value = finalText;
      }
    }, 100);
  }

  function triggerAnimation(){
    isAnimating.value = false;
    requestAnimationFrame(() => {
      isAnimating.value = true;
    });
  }

  onMounted(() => {
    startSlot();
  });
  // コンポーネントがアンマウントされるときにインターバルをクリア
  onBeforeUnmount(() => {
    if (intervalId !== null) {
      clearInterval(intervalId)
    }
  })
</script>

<style scoped>
.slot-text {
  font-size: 28px;
  font-weight: bold;
  font-family: monospace;
  letter-spacing: 0.05em;
  white-space: nowrap;
  transition: all 0.3s ease;
  opacity: 0;
  transform: translateY(0);
}
.is-animating {
  opacity: 1;
  transform: translateY(-4px);
}
</style>

getRandomChar()関数で、finalText.length分のランダムな文字列を生成しています。
これを一定時間ごとに切り替えて、最後に目的の文字列を表示します。

function getRandomChar(length: number): string {
    let result = '';
    for (let i = 0; i < length; i++) {
      result += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return result;
  }

requestAnimationFrameでclass切り替え

クラス(.is-animating)の切り替えはrefを使って管理しています。
requestAnimationFrame()を使うことで、次のフレームでアニメーションが自然に適用されるようにしています。
これにより、「フワッ」とした動きになります。

onBeforeUnmount()とは

このコンポーネントではsetInterval()を使っているため、コンポーネントが破棄されるときに必ずクリアしておく必要があります。

onBeforeUnmount(() => {
  if (intervalId !== null) {
    clearInterval(intervalId)
  }
})

もしこの処理をしないと、コンポーネントが表示されていないのに動作が継続してしまい、バグやメモリリークの原因になります。

これはaddEventListener()などを使っている場合にも同様で、onMounted()で追加したイベントは、onBeforeUnmount()でちゃんと削除するのが基本です。
お作法として覚えておきます。

実際の動作

GitHub Pagesで公開しています
https://ocotank.github.io/daily-practice/#/random/

終わりに

やってみると意外とシンプルに作れるんだなと思いました。
ただ、JavaScriptやVueに対する苦手意識があるので、かなり時間はかかってしまいました。
でも、こういう「ちょっと気になる動き」から作ってみることで、楽しく学べるんだなと感じています!

Discussion