打字机效果文本

冬天吃雪糕2022年8月23日
大约 2 分钟

打字机效果文本

|

代码

详情
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'

const TimeoutPromise = async (ms?: number) => {
  return new Promise<void>((resolve) => {
    setTimeout(() => {
      resolve()
    }, ms)
  })
}

interface Props {
  texts: string[]
  speed?: number | 'fast' | 'slow'
  loop?: boolean
  clearMode?: 'clear' | 'backspace'
  delay?: 'default' | number
}

const props = withDefaults(defineProps<Props>(), {
  speed: 300,
  loop: false,
  clearMode: 'backspace',
  delay: 'default'
})

const curTextId = ref(0)
const curTextPosition = ref(0)
let speed = 300

const cursor = ref('|')

const content = computed(() => {
  return `${props.texts[curTextId.value]
    .slice(0, curTextPosition.value)
    .split('<')
    .join('<br />')}${cursor.value}`
})

const print = () => {
  if (curTextPosition.value < props.texts[curTextId.value].length) {
    curTextPosition.value += 1
  }
}

const printBack = async () => {
  if (curTextPosition.value > 0) {
    if (props.clearMode === 'backspace'){
      await TimeoutPromise(speed / 2)
    }
    curTextPosition.value -= 1
    await printBack()
  }
}

// 模拟电脑光标闪动,0.53秒闪一次
const cursorFlash = () => {
  setTimeout(() => {
    cursor.value = '&nbsp;'
  }, 100)
  setTimeout(() => {
    cursor.value = '|'
  }, 630)
}

const printLoop = async () => {
  // 打字
  await new Promise<void>((resolve) => {
    const printInterval = setInterval(() => {
      print()
      if (curTextPosition.value >= props.texts[curTextId.value].length) {
        clearInterval(printInterval)
        resolve()
      }
    }, speed)
  })
  // 打字结束后光标闪动
  // 设置setInterval前调用一次,立即执行一次,之后定期循环执行
  cursorFlash()
  const cursorFlashInterval = setInterval(cursorFlash, 1060)
  let delay = (props.texts[curTextId.value].length - 1) * speed
  if (!Number.isNaN(Number(props.delay))) {
    delay = Number(props.delay)
  }
  await TimeoutPromise(delay)
  // 切换到下一句或结束循环
  if (curTextId.value < props.texts.length - 1) {
    clearInterval(cursorFlashInterval)
    await printBack()
    curTextId.value += 1
    curTextPosition.value = 0
    printLoop()
  } else if (props.loop) {
    clearInterval(cursorFlashInterval)
    await printBack()
    curTextId.value = 0
    printLoop()
  }
}

onMounted(async () => {
  if (Number.isNaN(Number(props.speed))) {
    if (props.speed === 'fast') {
      speed = 100
    } else if (props.speed === 'slow') {
      speed = 500
    }
  } else {
    speed = Number(props.speed)
  }
  printLoop()
})
</script>

<template>
  <p v-html="content"></p>
</template>

使用

示例

// texts(显示内容)必须设置,类型为字符串数组,多段文本依次显示
<TypeText :texts="['这是第一段文字','这是第二段']" />

// 在显示内容中使用<换行
<TypeText :texts="['这是<多行文字']" />

// 循环显示
<TypeText :texts="['这是第一段文字','这是第二段']" :loop="true" />

// 切换显示段落时使用清屏模式擦除现有文字
<TypeText :texts="['这是第一段文字','这是第二段']" clearMode="clear" />

// 等待1s后切换显示段落
<TypeText :texts="['这是第一段文字','这是第二段']" :delay="1000" />

// 设置显示速度
<TypeText :texts="['这是第一段文字','这是第二段']" speed="fast" />

API

参数说明类型默认值
texts显示内容string[]-
loop是否循环显示booleanfalse
clearMode切换显示段落的模式'clear' | 'backspace''backspace'
delay切换显示段落的延迟,单位为msnumber | 'default''default'
speed显示速度,'fast''slow'对应的速度值分别为100500number | 'fast' | 'slow'300
上次编辑于: 2022/8/23 06:32:29
贡献者: WingSnow