基于JavaScript实现的打字机效果

前言
在网上经常看到很酷炫的打字机动画,很好奇怎么实现的,今天就来尝试实现一下。

最终效果是这样的:
在这里插入图片描述

html代码

部分代码

<template>
  <div class="page-list">
    <div class="answer-content" v-html="content"></div>
     <div class="search-block" ref="searchBlock">
      <div class="search-content">
        <input
          type="text"
          v-ios-focus
          placeholder="描述您想问的问题"
          class="search-keyword"
          @keyup.enter="searchInfo"
          v-model="question"
        />
        <img @click="searchInfo" src="@/assets/img/icon/input-send.png" alt="" class="icon-search" />
      </div>
    </div>
  </div>
</template>

data函数

data() {
	return {
	// 测试数据
 	testHtml: `<h1>Styled Table Example</h1>
      <table style="width: 80%; margin: 20px auto; border-collapse: collapse; font-family: Arial, sans-serif;">
        <thead>
          <tr>
            <th style="border: 2px solid #4CAF50; padding: 12px; text-align: center; background-color: #4CAF50; color: white;">Header 1</th>
            <th style="border: 2px solid #4CAF50; padding: 12px; text-align: center; background-color: #4CAF50; color: white;">Header 2</th>
            <th style="border: 2px solid #4CAF50; padding: 12px; text-align: center; background-color: #4CAF50; color: white;">Header 3</th>
          </tr>
        </thead>
        <tbody>
          <tr style="background-color: #f2f2f2;">
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 1</td>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 2</td>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 3</td>
          </tr>
          <tr>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 4</td>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 5</td>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 6</td>
          </tr>
          <tr style="background-color: #f2f2f2;">
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 7</td>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 8</td>
            <td style="border: 2px solid #4CAF50; padding: 12px; text-align: center;">Data 9</td>
          </tr>
        </tbody>
      </table>`,
      content: '', // 展示的内容
      getContentFn: null, 
      printInterval: null
    }
  }

思路分析

我们想要的效果是内容部分逐个展示出来,我们会想到通过获取字符串长度,设置定时器,字符累加显示到页面,这样有个问题,我们要展示的内容包含了html标签,字符累加标签会在页面闪现,这不是我们想要的效果,标签字符(例如<div style=“height: 20px;”)不需要逐个展示,而是全部累加,累加过程请看下图:
在这里插入图片描述

引入的方法

 
/**
 * @description: 判断是否匹配到
 * @regArr : 匹配数组
 * @queryMsg : 匹配的字符串
 */
function hasMatch(regArr, queryMsg) {
  let index = 0
  const isMatch = regArr.some((reg, i) => {
    if (reg.test(queryMsg)) {
      index = i
      return true
    }
    return false
  })
  return {
    index,
    isMatch
  }
}

/**
 * @description: 替换对应字符
 * @queryMsg : 要解析的字符串
 * @content : 展示的字符串
 */
export function getDomText() {
  // 这里是定义的一些正则,用来匹配标签
  const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/
  const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`
  const qnameCapture = `((?:${ncname}\\:)?${ncname})`
  // 标签开始的前半部分('</div')
  const startTagOpen = new RegExp(`^<${qnameCapture}`)
  // 标签开始的后半部分('>'或者'/>')
  const startTagClose = /^\s*(\/?)>/
  // 闭合标签('</div>')
  const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
  const arr = [startTagOpen, startTagClose, endTag]
  // 这里定义一个标识,代表标签开始的后半部分已经添加('>'或者'/>')
  let isClosed = true

  return function getContent(queryMsg, content, isReset) {
    if(isReset) {
    // 重新加载内容时重置标识
      isClosed = true
    }
    if (!queryMsg) {
    // 内容为空时返回
      return {
        queryMsg,
        content
      }
    }
    // 获取匹配到标签的索引和是否匹配到标识
    let { index = 0, isMatch } = hasMatch(arr, queryMsg)
    // 匹配到标签
    if (isMatch) {
      const startArr = queryMsg.match(arr[index])
      let leng = startArr[0].length
      content += startArr[0]
      queryMsg = queryMsg.slice(leng)
      if (index === 0) {
      	// 匹配到标签开始的前半部分(比如'</span'),说明需要继续累加下一个字符
        isClosed = false
      }
      if (index === 1) {
        isClosed = true
      }
    } else {
      const str = queryMsg.slice(0, 1)
      content += str
      queryMsg = queryMsg.slice(1)
      // 如果已经存在关闭标签'>',则说明截取的是文本内容,直接返回
      if (isClosed && str !== ' ') {
        return {
          queryMsg,
          content
        }
      }
    }
    // 递归累加字符,如果当前添加的是标签字符,需要继续添加下一个字符,一直到添加的是文本字符则跳出函数
    // 说明: '<span style="width: 30px;">文本字符内容</span>' 
    // 其中 '<span style="width: 30px;">'是标签字符, '文本字符内容'是文本字符
    const obj = getContent(queryMsg, content)
    return {
      queryMsg: obj.queryMsg,
      content: obj.content
    }
  }
}

/**
 * @description: 定时器
 * @delay : 延时时间
 * @fn 执行的回调函数
 */
export function printInterval(delay = 40) {
 let timer = null
  return function(fn) {
    let context = this
    clearInterval(timer)
    timer = setInterval(() => {
     // 注意这里fn函数需要指定this指向,并且需要有返回值,返回值为ture说明字符已经加载完成,然后清除定时器
      const success = fn.call(context)
      if (success) {
      // 内容加载完成清除定时器
        clearInterval(timer)
      }
      console.log('success===>', success)
    }, delay)
  }
}

vue中方法

 mounted() {
    this.getContentFn = getDomText()
    this.printIntervalFn = printInterval()
  },
  beforeDestroy () {
    // 方法使用了闭包,这里释放一下内存
    this.getContentFn = null
    this.printIntervalFn = null
  },
  methods: {
   searchInfo() {
   	  this.myPrint(this.testHtml)
   },
    // 滚动到聊天内容底部
    scrollToBottom() {
      this.$nextTick(() => {
      	// 滚动页面到底部
      	// 由于页面过长,questionContent 对应的标签没有贴出来
        const scrollDom = this.$refs.questionContent
        scrollDom.scrollIntoView({
          behavior: 'smooth',
          block: 'end'
        })
      })
    },
     // 实现打字机效果
    myPrint(queryMsg) {
      let currentLen = 0
      const rowNum = 15 // 每15个字符调用一下滚动方法
      this.printIntervalFn.call(this, function() {
        if (!this.getContentFn) {
          // 离开当前页面,方法被注销,需要清除定时器
          return true
        }
        const contentObj = this.getContentFn(queryMsg, this.content, true)
        // console.log('contentObj=>', contentObj.content)
        this.content = contentObj.content
        queryMsg = contentObj.queryMsg || ''
        // 换行
        currentLen++
        if (currentLen >= rowNum) {
          this.scrollToBottom()
          currentLen = 0
        }
        // 加载完成,清除定时器
        return queryMsg === ''
      })
    },
  }

总结

本文叙述了使用js实现打字机效果的一种思路,通过实现打字机效果加深了对正则、递归、闭包、call等知识点的理解,网上还有很多种实现方式,例如使用css,但是这种实现方法只限使用文本字符串,除此之外还有typed.js,typed.js库实现原理跟本文叙述类似。如果小伙伴们有更好的实现方式,欢迎在评论区告知,我也去学习一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值