话不多说先看下demo的GIF:



1.首先引入animate.css,可以直接在index.html中cdn引入;

2.其次在开发这种动画较多的页面我觉得还是引用jquery比较方便,操作dom稍多,
我这里没有使用jquery,就想复习复习原生js。可以npm安装,在build/webpack.base.conf.js中定义插件:
    var webpack = require('webpack '); 
    在module.exports的对象中加入:
    plugins:[
      new webpack.ProvidePlugin({
         $:"jquery",
         jQuery:"jquery",
         "windows.jQuery":"jquery"
      })
    ],

接下来直接上该页面代码,就是一个vue组件:

<template>
  <div class="home">
		<div class="header " id="header" :class="{fixed: isFixed}">
		     <div class=" fixed-width clearfix">
		        <div class="header-title fl">我是顶部</div>
		        <div class="header-buy fr"  >我也是顶部</div>
		     </div>
		</div>
		<div class="content">
			<div class="banner"></div>
			<div class="animate-box slide">
				<h2 class="animate text-h2" data-ani="fadeInUpBig">LuckLin520 Written in Chengdu</h2>
				<p class="animate text-p" data-ani="rollIn" data-delay="1000">Thank you for reading and I will continue to work hard! —————2018/08/08</p>
			</div>
			<div class="animate-box bounce">
				<div class="animate yellow" data-ani="bounceInLeft"></div>
				<div class="animate red" data-ani="bounceInRight"></div>
				<div class="animate green" data-ani="bounceInLeft"></div>
				<div class="animate blue" data-ani="bounceInRight"></div>
			</div>
			<div class="animate-box slide">
				<h2 class="animate text-h2" data-ani="fadeInUpBig">做真实的自己,一切都会好起来的</h2>
				<p class="animate text-p" data-ani="slideInRight" data-delay="1000">Be true to yourself and everything will be fine</p>
			</div>
			<div class="animate-box zoom">
				<div class="animate one" data-ani="bounceInLeft"></div>
				<div class="animate two" data-ani="bounceInRight"></div>
				<div class="animate three" data-ani="bounceInLeft"></div>
				<div class="animate four" data-ani="bounceInRight"></div>
			</div>
		</div>
  </div>
</template>

<script>
	export default {
	  data () {
	    return {
	    	isFixed: 0
	    }
	  },
	  methods: {
	  	handleScroll() {
	  		let top = pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
	  		if(top > 250){
	  			this.isFixed = 1;
	  		}else if(top < 200){
	  			this.isFixed = 0;
	  		}
	  	},
	  	handleAnimate() {
	  		let top = pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
	  		let vh = document.documentElement.clientHeight;
	  		let dom = document.querySelectorAll(".animate");
	  		[].slice.call(dom).forEach(v => {
	  			if(top + vh > v.offsetTop){
	  				var delay = v.dataset.delay;
	  				if(delay){
	  					setTimeout(() => {
	  						v.style.opacity = 1;
		  					v.classList.add(v.dataset.ani)
		  				}, delay)
	  				}else{
	  					v.style.opacity = 1;
	  					v.classList.add(v.dataset.ani)
	  				}
	  			}else{
	  				v.classList.remove(v.dataset.ani)
	  				v.style.opacity = 0;
	  			}
	  		})

	  	}
	  },
	  mounted() {
	  	this.$nextTick(() => {
	  		this.handleAnimate()//初始化第一次加载时在视口内就执行动画
	  		addEventListener('scroll', this.handleScroll);
	  		addEventListener('scroll', this.handleAnimate);

	  	})
	  	
	  },
	  destroyed() {
	  	removeEventListener('scroll', this.handleScroll);//避免影响其他页面
	  	removeEventListener('scroll', this.handleAnimate);
	  }
	}
</script>

<style scoped lang="scss">
	.header{
		background: green;
		height: 50px;
	}
	.fixed{
        position: fixed;
        top: 0px;
        z-index: 4;
        width: 100%;
        animation: slideInDown .5s;
    }
    .content{
    	height: 2000px;
    	background: pink;
    	overflow: hidden;
    	.banner{
			width: 80%;
			height: 400px;
			background:orange;
			margin: 80px auto;
    	}
    	.slide{
    		font-size: initial;
    		height: 100px;
    	}
    	.bounce{
    		width: 80%;
    		margin:0 auto 80px;
    		display: flex;
    		flex-wrap: wrap;
    		justify-content: space-between;
    		&>div{
    			height: 120px;
    			float: left;
    			width: 45%;
    		}
    		.yellow{
    			background:yellow;
    			margin-bottom: 40px;
    		}
    		.red{
    			background:red;
    		}
    		.green{
    			background:green;
    		}
    		.blue{
    			background:blue;
    		}
    	}
    	.zoom{
    		width: 80%;
    		margin:0 auto 80px;
    		display: flex;
    		justify-content: space-between;
    		&>div{
    			width: 23%;
    			height: 263px;
    		}
    		.one{
    			background:url(http://www.codingke.com/themes/codingnew-1/img/study/python/python_block1_img1.jpg) no-repeat center/100%;
    		}
    		.two{
    			background:url(http://www.codingke.com/themes/codingnew-1/img/study/python/python_block1_img2.jpg) no-repeat center/100%;
    		}
    		.three{
    			background:url(http://www.codingke.com/themes/codingnew-1/img/study/python/python_block1_img3.jpg) no-repeat center/100%;
    		}
    		.four{
    			background:url(http://www.codingke.com/themes/codingnew-1/img/study/python/python_block1_img4.jpg) no-repeat center/100%;
    		}
    	}
    }

 //animate classs
 .animate{
 	opacity: 0;
 }
 .fadeInUpBig{
 	animation: fadeInUpBig 1s;
 }
 .rollIn{
 	animation: rollIn 1s;
 }
 .slideInRight{
 	animation: slideInRight 1s;
 }
 .bounceInLeft{
	animation: bounceInLeft 2s ease-in;
 }
 .bounceInRight{
	animation: bounceInRight 2s ease-in;
 }
</style>

不难看得出核心代码就是那个handleAnimate方法,实际运用中肯定多个页面会有动画效果,这个函数可以抽离成公共代码,其实他可以只需要接收一个所有运动元素的统一选择器(这里的".animate"),而该方法通过这些".animate"元素传入的自定义属性ani来判断该元素被指定了哪种动画类型,通过动态添加该类型名称的class来定义animation动画,在最初我的思路是将animation-delay动画延迟同样用该类型的class在css中定义,最后我又考虑到如果不止一个元素必须用同一个动画类型,但是有的需要有的不需要延迟呢,那不是该属性出现耦合了?所以最后把所有动画延迟定义在该元素自身—data-delay,没错又是自定义属性,把需要有延迟时间的元素就加一个自定义属性delay,执行中判断是否有该属性,有的话就用setTimeout来延迟该属性的值的时间添加动画class,这样就让动画样式和动画延迟互不影响了,动画类型样式完全复用,同时,如果直接定义animation-delay或者直接animation以简写的形式来定义动画延迟的话,当被添加上动画class时该元素就会立马显示出来,就是说在延迟期间(元素动起来之前)它也是处于显示状态的,可以用js操作用setTimeout来延迟元素显示出来,不过比较麻烦了,因为还得js获取animate-delay的值才能知道延迟时间,所以我上面最后用的是自定义属性通过延迟添加类来处理延迟动画,比较好操作。这些便是核心思路。

---------------------------------20240509更新-------------------------------

import type { DirectiveBinding } from 'vue'

type AnimateValue = { ani: string; duration?: number; hook?: string; delay?: number }
const animationClassName = 'animate'
export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.vueApp.directive('animate', {
    mounted(el, binding: DirectiveBinding<AnimateValue>) {
      if (!binding.value.ani) {
        return
      }
      el.classList.add(animationClassName)
      el.dataset.ani = binding.value.ani
      if (binding.value.delay) {
        el.dataset.delay = binding.value.delay
      }
      const currentAni = `
        .${binding.value.ani} {
          animation: ${binding.value.ani} ${binding.value.duration || 2}s ${binding.value.hook || 'ease-in'};
        }
      `
      const style = document.querySelector('style.v-animate')
      if (style) {
        style.innerHTML += currentAni
      } else {
        const newStyle = document.createElement('style')
        newStyle.setAttribute('class', 'v-animate')
        newStyle.innerHTML = currentAni
        document.head.appendChild(newStyle)
      }
    }
  })
})
export const useScrollAnimation = () => {
  const handleAnimate = () => {
    const top = document.documentElement.scrollTop || document.body.scrollTop
    const vh = document.documentElement.clientHeight
    const dom: any = document.querySelectorAll(`.${animationClassName}`)
    Array.from(dom).forEach((v: any) => {
      if (top + vh > v.offsetTop) {
        const delay = v.dataset.delay
        if (delay) {
          setTimeout(() => {
            v.style.opacity = 1
            v.classList.add(v.dataset.ani)
          }, delay)
        } else {
          v.style.opacity = 1
          v.classList.add(v.dataset.ani)
        }
      } else {
        v.classList.remove(v.dataset.ani)
        v.style.opacity = 0
      }
    })
  }
  onMounted(() => {
    handleAnimate()
    addEventListener('scroll', handleAnimate)
  })
  onUnmounted(() => {
    removeEventListener('scroll', handleAnimate)
  })
}

在vue3中使用自定义指令加Hooks实现,使用更简单,此处是Nuxt3中实例。

------------------------------------------------------------------

附一个header导航滚动过渡透明效果代码:

let header = document.querySelector('.'+styles.header)
    document.onscroll = ()=>{
    let doc = document.documentElement || document.body
    let y = doc.scrollTop || doc.scrollTop
    let h = Math.max(doc.scrollHeight,doc.scrollHeight);
    let transparent = (1 - y/h).toFixed(2)
    if(transparent < '0.4') transparent = '0.4';
    header.style.background = `rgba(0, 14, 51,${transparent})`
}

Logo

前往低代码交流专区

更多推荐