vue中结合animate.css实现元素动画入场
话不多说先看下demo的GIF:1.首先引入animate.css,可以直接在index.html中cdn引入;2.其次在开发这种动画较多的页面我觉得还是引用jquery比较方便,操作dom稍多,我这里没有使用jquery,就想复习复习原生js。可以npm安装,在build/webpack.base.conf.js中定义插件:var webpack = require('...
话不多说先看下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})`
}
更多推荐
所有评论(0)