Vue 动画

本文详细介绍了Vue.js中实现动画和过渡的各种方法,包括使用CSS动画、过渡效果、动态组件动画、过渡模式、GSAP库以及transition-group的使用。通过示例代码展示了如何控制元素的显示、隐藏、动画效果以及交错过渡,帮助读者深入理解Vue.js的动画机制。
摘要由CSDN通过智能技术生成

00. 传统写法

动画,核心思路就是控制类型的使用与否

<template>
    <div id="app">
        <span
            :class="{
                animation
            }"
            style="display: inline-block;"
            >App</span
        >
        <p>
            <button @click="animation = !animation">click</button>
        </p>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            animation: false,
            transition: false
        };
    }
};
</script>

<style>
#app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
}
/* 动画 */
@keyframes animation {
    0% {
        transform: translateX(100px);
    }
    50% {
        transform: translateX(50px);
    }
    100% {
        transform: translateX(0);
    }
}
.animation {
    animation: animation 3s linear;
}
</style>

过渡,核心思路:提前准备好 transition 和初始状态,点击按钮切换为另一种状态

<template>
    <div id="app">
        <span
            class="transition"
            :style="{
                background: transition ? 'red' : 'blue'
            }"
            style="display: inline-block;"
            >App</span
        >
        <p>
            <button @click="transition = !transition">click</button>
        </p>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            transition: false
        };
    }
};
</script>

<style>
#app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
}
.transition {
    transition: 3s linear;
}
</style>

01. 过渡效果

<template>
    <div id="app" style="text-align: center;margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <transition>
            <!-- 这里可以是单个的元素或组件 -->
            <div v-if="isShow">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    }
};
</script>

<style>
.v-enter-from,
.v-leave-to {
    opacity: 0;
}
.v-enter-active,
.v-leave-active {
    transition: opacity 3s ease;
}
.v-enter-to,
.v-leave-from {
    opacity: 1;
}
</style>

指定动画类名的前缀

<template>
    <div id="app" style="text-align: center;margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <transition name="ifer">
            <!-- 这里可以是单个的元素或组件 -->
            <div v-if="isShow">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    }
};
</script>

<style>
.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
}
.ifer-enter-active,
.ifer-leave-active {
    transition: opacity 3s ease;
}
.ifer-enter-to,
.ifer-leave-from {
    opacity: 1;
}
</style>

动画类简写

.ifer-enter-from 只会在 DOM 被插入之前生效,插入之后马上移除,移除之后 opacity 自然就是 1,所以 .ifer-enter-toopacity: 1 可以省略

.ifer-leave-from 在离开过渡触发时(此时 opacity 也是 1)立即成效,下一帧移除

<style>
.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
}
.ifer-enter-active,
.ifer-leave-active {
    transition: opacity 3s ease;
}
</style>

02. 动画效果

.ifer-enter-active {
    animation: bounce 1s ease;
}
.ifer-leave-active {
    animation: bounce 1s ease reverse;
}

@keyframes bounce {
    0% {
        transform: scale(0);
    }
    50% {
        transform: scale(1.2);
    }
    100% {
        transform: scale(1);
    }
}

03. 过渡和动画共存

<template>
    <div id="app" style="text-align: center;margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <hr />
        <transition name="ifer">
            <!-- 这里可以是单个的元素或组件 -->
            <div v-if="isShow" style="display: inline-block;">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    }
};
</script>

<style>
/* 过渡 */
.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
}
.ifer-enter-active,
.ifer-leave-active {
    transition: opacity 1s ease;
}

/* 动画 */
.ifer-enter-active {
    animation: bounce 1s ease;
}
.ifer-leave-active {
    animation: bounce 1s ease reverse;
}

@keyframes bounce {
    0% {
        transform: scale(0);
    }
    50% {
        transform: scale(1.2);
    }
    100% {
        transform: scale(1);
    }
}
</style>

04. 更建议做法

.ifer-enter-active {
    animation: bounce 1s ease;
}
.ifer-leave-active {
    animation: bounce 1s ease reverse;
}

@keyframes bounce {
    0% {
        opacity: 0;
        transform: scale(0);
    }
    50% {
        transform: scale(1.2);
    }
    100% {
        opacity: 1;
        transform: scale(1);
    }
}

05. 两个动画的切换模式

默认是同时进行的

<template>
    <div id="app" style="text-align: center; margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <hr />
        <transition name="ifer">
            <div v-if="isShow" style="display: inline-block;">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
            <div v-else style="display: inline-block;">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    }
};
</script>

<style>
.ifer-enter-active {
    animation: bounce 1s ease;
}
.ifer-leave-active {
    animation: bounce 1s ease reverse;
}

@keyframes bounce {
    0% {
        opacity: 0;
        transform: scale(0);
    }
    50% {
        transform: scale(1.2);
    }
    100% {
        transform: scale(1);
    }
}
</style>

mode="out-in" 先离开动画,离开动画完了再执行进入动画

<transition name="ifer" mode="out-in"></transition>

06. 动态组件动画

<template>
  <div id="app">
    <button @click="isShow = !isShow">click</button>
    <hr />
    <!-- 先出后进 -->
    <!-- appear: true,一上来就携带动画效果 -->
    <transition name="ifer" mode="out-in" :appear="true">
      <component :is="isShow ? 'home' : 'about'"></component>
    </transition>
  </div>
</template>

<script>
import Home from '@/components/Home.vue';
import About from '@/components/About.vue';
export default {
  name: 'App',
  components: {
    Home,
    About
  },
  data() {
    return {
      isShow: true
    };
  }
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

/* 动画 */
.ifer-enter-active {
  animation: bounce 1s ease;
}
.ifer-leave-active {
  animation: bounce 1s ease reverse;
}

@keyframes bounce {
  0% {
    opacity: 0;
    transform: scale(0);
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}
</style>

07. animation.css

安装

npm i animate.css

应用

<template>
    <div id="app" style="text-align: center; margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <hr />
        <transition name="ifer">
            <div v-if="isShow" style="display: inline-block;">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    }
};
</script>

<style>
.ifer-enter-active {
    animation: bounceInUp 1s ease-in;
}
.ifer-leave-active {
    animation: bounceInUp 1s ease-in reverse;
}
</style>

08. 通过属性指定 class

<template>
    <div id="app" style="text-align: center; margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <hr />
        <transition
            enter-active-class="animate__animated animate__fadeInDown"
            leave-active-class="animate__animated animate__fadeInDown reverse"
        >
            <div v-if="isShow" style="display: inline-block;">
                <img alt="Vue logo" src="./assets/logo.png" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    }
};
</script>

<style>
.reverse {
    animation-direction: reverse;
}
</style>

09. 动画的生命周期钩子

<template>
    <div style="text-align: center; margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <hr />
        <transition
            enter-active-class="animate__animated animate__fadeInDown"
            leave-active-class="animate__animated animate__fadeInDown reverse"
            @before-enter="beforeEnter"
            @enter="enter"
            @after-enter="afterEnter"
            @enter-cancelled="enterCancelled"
            @before-leave="beforeLeave"
            @leave="leave"
            @after-leave="afterLeave"
            @leave-cancelled="leaveCancelled"
        >
            <div v-if="isShow">
                <img src="./assets/logo.png" alt="" />
            </div>
        </transition>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    },
    methods: {
        beforeEnter() {
            console.log('#1 beforeEnter');
        },
        enter() {
            console.log('#2 enter');
        },
        afterEnter() {
            console.log('#3 afterEnter');
        },
        enterCancelled() {
            // 进入动画被终止时
            console.log('#4 enterCancelled');
        },
        beforeLeave() {
            console.log('#5 beforeLeave');
        },
        leave() {
            console.log('#6 leave');
        },
        afterLeave() {
            console.log('#7 afterLeave');
        },
        leaveCancelled() {
            // 离开动画被终止时
            console.log('#8 leaveCancelled');
        }
    }
};
</script>
<style>
.reverse {
    animation-direction: reverse;
}
</style>

10. GSAP

tweenmax

<template>
    <div style="text-align: center; margin-top: 60px;">
        <button @click="isShow = !isShow">click</button>
        <hr />
        <!-- css: false 跳过 css 动画的检测,提高性能 -->
        <transition @enter="enter" @leave="leave" :css="false">
            <div v-if="isShow">
                <img src="./assets/logo.png" alt="" />
            </div>
        </transition>
    </div>
</template>

<script>
import gsap from 'gsap';
export default {
    name: 'App',
    data() {
        return {
            isShow: true
        };
    },
    methods: {
        enter(el, done) {
            // 从哪里进入
            gsap.from(el, {
                scale: 0,
                x: -200,
                onComplete: done
            });
        },
        leave(el, done) {
            // 要抵达哪里
            gsap.to(el, {
                scale: 0,
                x: 200,
                onComplete: done
            });
        }
    }
};
</script>

11. 数字累加动画

<template>
    <div style="text-align: center; margin-top: 60px;">
        <h1>{{ showNumber.toFixed(0) }}</h1>
        <hr />
        <input type="number" step="100" v-model="count" />
    </div>
</template>

<script>
import gsap from 'gsap';
export default {
    name: 'App',
    data() {
        return {
            count: 1,
            showNumber: 1
        };
    },
    watch: {
        // 监听 count 的变化,改变 showNumber
        count(newValue) {
            gsap.to(this, {
                duration: 1,
                showNumber: newValue
            });
        }
    }
};
</script>

12. transition-group

1. 默认,不会渲染一个元素的包裹器,但是你可以通过 tag 属性指定元素进行渲染

2. mode 过渡模式不可用,因为我们不在相互切换特有的元素

3. 内部元素总是需要提供唯一的 key attribute

4. CSS 过渡的类名将会应用在内部的元素中,而不是这个组/容器本身

13. 动画

<template>
    <div style="text-align: center; margin-top: 60px;">
        <button @click="add">add</button>
        <button @click="remove">remove</button>
        <hr />
        <transition-group tag="p" name="ifer">
            <span v-for="item in numbers" class="item" :key="item">{{
                item
            }}</span>
        </transition-group>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            numberCounter: 10
        };
    },
    methods: {
        add() {
            this.numbers.splice(this.randomIndex(), 0, this.numberCounter++);
        },
        remove() {
            this.numbers.splice(this.randomIndex(), 1);
        },
        randomIndex() {
            return Math.floor(Math.random() * this.numbers.length);
        }
    }
};
</script>

<style>
.item {
    margin-right: 10px;
    /* 注意:inline-block 才会对 translate 生效 */
    display: inline-block;
}

.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
    transform: translateY(30px);
}

.ifer-enter-active,
.ifer-leave-active {
    transition: transform 1s ease;
}

/* 添加时周围列表具有动画效果 */
.ifer-move {
    transition: transform 1s ease;
}
/* 移除时周围列表具有动画效果 */
.ifer-leave-active {
    /* 注意不要使用 text-align 进行居中 */
    position: absolute;
}
</style>

14. 洗牌

<template>
    <div style="margin-top: 60px;">
        <button @click="add">add</button>
        <button @click="remove">remove</button>
        <button @click="shuff">shuff</button>
        <hr />
        <transition-group tag="p" name="ifer">
            <span v-for="item in numbers" class="item" :key="item">{{
                item
            }}</span>
        </transition-group>
    </div>
</template>

<script>
import { shuffle } from 'lodash';
export default {
    name: 'App',
    data() {
        return {
            numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
            numberCounter: 10
        };
    },
    methods: {
        add() {
            this.numbers.splice(this.randomIndex(), 0, this.numberCounter++);
        },
        remove() {
            this.numbers.splice(this.randomIndex(), 1);
        },
        randomIndex() {
            return Math.floor(Math.random() * this.numbers.length);
        },
        shuff() {
            this.numbers = shuffle(this.numbers);
        }
    }
};
</script>

<style>
.item {
    margin-right: 10px;
    display: inline-block;
}

.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
    transform: translateY(30px);
}

.ifer-enter-active,
.ifer-leave-active {
    transition: all 1s ease;
}

/* 位移动画,妙 */
.ifer-leave-active {
    /* 注意不要使用 text-align 进行居中 */
    position: absolute;
}
.ifer-move {
    transition: transform 1s ease;
}
</style>

15. 交错过渡动画

太生硬

<template>
    <div style="text-align: center; margin-top: 60px;">
        <input type="text" v-model="keyword" />
        <hr />
        <transition-group tag="ul" name="ifer">
            <li v-for="item in showNames" :key="item">{{ item }}</li>
        </transition-group>
    </div>
</template>

<script>
export default {
    name: 'App',
    data() {
        return {
            names: ['ifer', 'elser', 'xxx', 'sherry', 'moka', 'delta'],
            keyword: ''
        };
    },
    computed: {
        showNames() {
            return this.names.filter(
                (item) => item.indexOf(this.keyword) !== -1
            );
        }
    },
    methods: {}
};
</script>

<style>
.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
}

.ifer-enter-active,
.ifer-leave-active {
    transition: opacity 1s ease;
}
</style>

进化

<template>
    <div style="text-align: center; margin-top: 60px;">
        <input type="text" v-model="keyword" />
        <hr />
        <transition-group
            tag="ul"
            name="ifer"
            :css="false"
            @enter="enter"
            @leave="leave"
            @before-enter="beforeEnter"
        >
            <li
                v-for="(item, index) in showNames"
                :key="item"
                :data-index="index"
            >
                {{ item }}
            </li>
        </transition-group>
    </div>
</template>

<script>
import gsap from 'gsap';
export default {
    name: 'App',
    data() {
        return {
            names: ['ifer', 'elser', 'xxx', 'sherry', 'moka', 'delta'],
            keyword: ''
        };
    },
    computed: {
        showNames() {
            return this.names.filter(
                (item) => item.indexOf(this.keyword) !== -1
            );
        }
    },
    methods: {
        beforeEnter(el) {
            el.style.opacity = 0;
            el.style.height = 0;
        },
        enter(el, done) {
            gsap.to(el, {
                opacity: 1,
                height: '30px',
                delay: el.dataset.index * 0.5, // 牛批
                onComplete: done
            });
        },
        leave(el, done) {
            gsap.to(el, {
                opacity: 0,
                height: 0,
                delay: el.dataset.index * 0.5,
                onComplete: done
            });
        }
    }
};
</script>

<style>
.ifer-enter-from,
.ifer-leave-to {
    opacity: 0;
}

.ifer-enter-active,
.ifer-leave-active {
    transition: opacity 1s ease;
}
</style>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

当铺鬼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值