实现一个好看的滚动文字渐入效果

实现一个好看的滚动文字渐入效果

我们来实现一个还比较不错的前端显示效果。目前已完成一个简单demo,大家可以在我的个人博客blog.jimmyxuexue.top中切换切换文章,大家可以滚动下看看。

前言

在上次直播时,猫哥给我看了一个网站,说这个效果挺不错的,直播现在也没啥人看,要不研究一下怎么实现它给博客也加一下吧。

效果网站 isux.tencent.com/articles/in...

效果就是随着我们滚动鼠标向下滚动时,文字会由下往上的有个缓慢的渐出效果,在这种文字比较多的网站里,这种效果还是相对比较优雅的。所以我们今天也来实现一下。

🐱哥是一个群友,欢迎大家加入我的一个前端群呀~

我这实现的是按照我的思路来实现的,可能不能做到1比1还原效果,不过应该是八九不离十

思路

主要有这么几个需要思考的点

动画

首先文字渐出的效果这个应该是一个动画,所以我们得实现这个动画。

第一想法是这个东西我们找个动画库就好了,看看有没有类似的,用一下就好了,于是我上了 Animate.css ,找到了好几个类似的效果,最终我们选择 animate__fadeInUp 这个效果。

当我们想要有这个效果的元素进入可视窗口时,加上这个动画类

实现

  • scroll

    监听滚动条事件,判断元素在视口了就加上这个类

    这个方案被比较早就被排除了,因为滚动时触发比较频繁的事件,性能会比较差,如果加上了防抖或者节流,整个效果又延迟,所以不行。

  • IntersectionObserver

    使用js比较新的观察者api,api优雅,且性能更高

综合下来使用这个IntersectionObserver 这个api是最佳选择。

image-20231023110210478

怎么介入 vitepress 工程

因为博客是基于vitepress来实现的,它最终的打包效果是将每个markdown文件都打包成单独的html,最糟糕的视线方式是给打包后的每个html文件都加上一些脚本。

上面的方案肯定是不可行的,麻烦,且耗时,不益于后续的扩展。

利用vue的机制

Vitepress 也是有基于vue3进行开发的,所以我们只要能有接口来介入vue的工程里面,就可以比较高效的视线了。思路大概就是:

  • 页面初始化时,执行脚本,页面元素在滚动到视口时加上我们动画类。

    onMounted

  • 当路由变化时,再次执行脚本,最新的页面元素在滚动到视口时加上我们动画类。

    Watch

我们在docs/.vitepress/theme/index.js 这个文件下进行代码注入即可。

这个文件是vitepress暴露给我们的接口,可以写我们写setup()函数,有了它就能用 onMountedwatch

哪些元素是我们需要效果的呢

这里我们就需要分析一下网站的dom结构了。

image-20231023112200288

经过我们分析,发现其实就是 .vp-doc > div 下的所有子元素是需要加这个效果的,所以我们只需要给这些元素加上观察者即可,当元素滚动到视口时,加上我们的动画类。

代码

具体的一些代码实现

一些工具函数

  • 检查元素是否在视口

    dart 复制代码
    const isElementInViewport = element => {
      var rect = element.getBoundingClientRect()
      const isInViewport =
        rect.top >= 0 &&
        rect.bottom <=
          (window.innerHeight || document.documentElement.clientHeight)
      return isInViewport
    }

    刚打开页面时,本来就在视口的元素是不需要加入动画类的,只有通过滚动才出现的元素才需要加上这个类

  • 检查元素是否需要加动画类

    kotlin 复制代码
    const checkHasAttribute = element => {
      return !!element.getAttribute('snow_is_show')
    }

    这里我们通过给元素添加自定义属性的方式,来实现,当元素有这个属性了,就不需要加动画类了。

    这个操作主要是为了防止一直上线滚动,频繁出现动画的问题,动画只需要加一次就好。

全部代码

javascript 复制代码
const observers = [] // 用于存储所有观察者 -> 收集起来主要是为了当路由变化时效果之前的观察者。

export default {
 // ... 省略
 setup() {
  const route = useRoute()
  onMounted(() => {
   initFirstScreen() // 初始化 -> 给首次渲染就在视口的元素加上自定义属性,这些元素永远不用加动画类
   animateFn() // 执行核心脚本
  })
  
    // 元素是否在视口
  const isElementInViewport = element => {
   var rect = element.getBoundingClientRect()
   const isInViewport =
    rect.top >= 0 &&
    rect.bottom <=
     (window.innerHeight || document.documentElement.clientHeight)
   return isInViewport
  }
 
    // 检查是否有自定义属性
  const checkHasAttribute = element => {
   return !!element.getAttribute('snow_is_show')
  }
  
    // 初始化函数
  const initFirstScreen = () => {
   const main = document.querySelector('.vp-doc>div') || []
   const paragraphs = [...(main?.children || [])]
   paragraphs.forEach(item => {
    if (isElementInViewport(item)) {
     item.setAttribute('snow_is_show', true)
    }
   })
  }
  
    // 核心脚本 
  const animateFn = () => {
   const main = document.querySelector('.vp-doc>div') || []
   const paragraphs = [...(main?.children || [])]
   paragraphs.forEach(item => {
    const observer = new IntersectionObserver(entries => {
     entries.forEach(entry => {
      if (entry.isIntersecting && !checkHasAttribute(item)) {
       // 元素进入视口
       item.classList.add('animate__animated')
       item.classList.add('animate__fadeInUp')
       item.setAttribute('snow_is_show', true)
      }
     })
    })
    observer.observe(item)
    observers.push(observer)
   })
  }
  
    // 清空所有 observer 的函数
  const destructionObserver = () => {
   observers.forEach(observe => {
    observe.disconnect()
   })
   observers.length = 0
  }
    
  watch(
   () => route.path,
   () =>
    nextTick(() => {
     destructionObserver() // 先清空所有的观察者
     initFirstScreen() // 再初始化一次 类似onMounted
     animateFn() // 再次执行核心函数
    })
  )
 },
 Layout,
}

总结

效果我们已经实现完成啦,其实也还是有优化点的:

  • 初始化时可以加一些其他的动画
  • 直接获取.vp-doc > div 的所有一级子元素有点粗暴,可能它的下面还有子元素,细分下能让动画能加优雅一点
  • ......

欢迎大家加我的vx:ysh15120,进我们前端交流群。我会不定期直播,一起交流。

相关推荐
GIS程序媛—椰子7 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
半开半落1 小时前
nuxt3安装pinia报错500[vite-node] [ERR_LOAD_URL]问题解决
前端·javascript·vue.js·nuxt
麦麦大数据1 小时前
基于vue+neo4j 的中药方剂知识图谱可视化系统
vue.js·知识图谱·neo4j
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
理想不理想v1 小时前
vue经典前端面试题
前端·javascript·vue.js
小阮的学习笔记2 小时前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜2 小时前
Vue实现登录功能
前端·javascript·vue.js
杨荧2 小时前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka