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

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

我们来实现一个还比较不错的前端显示效果。目前已完成一个简单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,进我们前端交流群。我会不定期直播,一起交流。

相关推荐
web130933203983 小时前
vue elementUI form组件动态添加el-form-item并且动态添加rules必填项校验方法
前端·vue.js·elementui
supermapsupport4 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
m0_748254885 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
苹果醋36 小时前
Golang的文件加密工具
运维·vue.js·spring boot·nginx·课程设计
关你西红柿子6 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
济南小草根7 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
m0_748256568 小时前
Vue - axios的使用
前端·javascript·vue.js
慢知行8 小时前
Vite 构建 Vue3 组件库之路:工程基础搭建与目录结构优化
前端·vue.js
阿克苏的滚滚馕8 小时前
alioss 批量断点续传 开箱即用
javascript·vue.js
Simaoya9 小时前
【vue】圆环呼吸灯闪烁效果(模拟扭蛋机出口处灯光)
javascript·css·vue.js