如何在Vue中构建可重用的Transition
栏目: JavaScript · 发布时间: 5年前
内容简介:在Vue中的如果我们可以将它们封装到组件中,并且在多个项目中重用,是不是会给我们带来更多的益处呢?在前面两篇文章中,我们了解了在Vue中怎么使用为了节省大家更多的时间,在这篇文章中咱们直接使用Vue Cli的来构建演示项目:
在Vue中的 transition
和 animation
都是一些很棒的东东。它们可以让我们的组件带有一定的动效效果。在《 Vue的 transition
》和《 Vue的 animation
》中分别学习了 transition
和 animation
在Vue组件中的运用。这两个特性可以让Web元元素可以像 animation.css
库中提供的效果一样,实现一些过渡甚至是简单的动画效果。让整个效果看起来很好。
如果我们可以将它们封装到组件中,并且在多个项目中重用,是不是会给我们带来更多的益处呢?在前面两篇文章中,我们了解了在Vue中怎么使用 transition
和 animation
,今天我们来学习几种定义 transtion
的方法,并且深入研究它们可以真正重用的方法。
创建Vue项目
为了节省大家更多的时间,在这篇文章中咱们直接使用Vue Cli的来构建演示项目:
vue create vue-transitions
因为我们将会涉及多个使用 transition
方法,在这里通过相关的分支来区分。
git checkout -b demo1
接下来进入实际的案例中,以来阐述Vue中如何构建可重用的 transition
。
示例项目 Github对应的地址在这里 。
原始的 transition
组件和CSS
在《 Vue的 transition
》一文中,我们了解到,在Vue中定义 transition
最简单的方法是使用Vue中的 <transition>
和 <transition-group>
。这需要为它们定义一个 name
和一些CSS。比如:
<!-- transitionDemo.vue --> <template> <div class="demo"> <div class="toggle toggle--text"> <input type="checkbox" id="toggle--text" class="toggle--checkbox"> <label class="toggle--btn" for="toggle--text" data-label-on="show" data-label-off="hidden" @click="toggle"></label> </div> <transition name="fade"> <p v-if="show">Hello W3cplus (^_^) !!!</p> </transition> </div> </template> <script> export default { name: 'transitionDemo', data () { return { show: true } }, methods: { toggle() { this.show = !this.show } } } </script> <style scoped> .fade-enter-active, .fade-leave-active { transition: opacity .3s; } .fade-enter, .fade-leave-to { opacity: 0; } /* === 默认样式省略 == */ </style>
点击按钮,你将看到的效果如下:
看起来很容易,对吧?然而,这种方法有一个问题。 这个过渡效果不能在另一个项目中重用 。
封装一个 transition
组件
如果我们将前面逻辑封装成一个Vue组件,将会发生什么情况呢?同样的,把Git分支切换到 demo2
中。接着我们创建一个 FadeTransition.vue
组件。
<!-- FadeTransition.vue --> <template> <transition name="fade"> <slot /> </transition> </template> <script> export default { name: 'FadeTransition' } </script> <style scoped> .fade-enter-active, .fade-leave-active { transition: opacity .3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>
该组件很简单,和Vue基本的 <transition>
几乎类似,不同之处是该组件使用了 slot
。在使用 FadeTransition
组件时,可以通过 slot
传递你想要的内容。比如:
<!-- App.vue --> <template> <div id="app"> <div class="toggle toggle--text"> <input type="checkbox" id="toggle--text" class="toggle--checkbox"> <label class="toggle--btn" for="toggle--text" data-label-on="show" data-label-off="hidden" @click="toggle"></label> </div> <FadeTransition> <div class="box" v-if="show"></div> </FadeTransition> </div> </template> <script> import FadeTransition from './components/FadeTransition' export default { name: 'app', components: { FadeTransition }, data () { return { show: true } }, methods: { toggle () { this.show = !this.show } } } </script>
这个时候看到的效果如下:
这个示例比前面的示例稍微好一点,如果想传递其他特定的值( props
)给 transition
,比如 mode
或者一些钩子函数,这就有点难搞了。
幸运的是,Vue中有一个特性,允许我们将用户指定的任何额外的 props
和侦听器传递给组件。如果你还不知道,可以通过 $attrs
来访问额外的 props
,并且结合 v-bind
一起使用,将它们绑定为 props
。
$attrs
:包含了父作用域中不作为 prop
被识别 (且获取) 的特性绑定 ( class
和 style
除外)。当一个组件没有声明任何 prop
时,这里会包含所有父作用域的绑定 ( class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件 —— 在创建高级别的组件时非常有用 。
这种方式同样适用于 $listener
,通常和 v-on
绑定使用传入内部组件。
在 demo2
的分支上,切到 demo3
分支,在 FadeTransition
组件上新增 v-bind="$attrs"
和 v-on="$listeners"
。
<!-- FadeTransition.vue --> <template> <transition name="fade" v-bind="$attrs" v-on="$listeners"> <slot /> </transition> </template> <!-- App.vue --> <template> <div id="app"> <div class="toggle toggle--text"> <input type="checkbox" id="toggle--text" class="toggle--checkbox"> <label class="toggle--btn" for="toggle--text" data-label-on="Box" data-label-off="Circle" @click="toggle"></label> </div> <FadeTransition mode="out-in"> <div key="box" v-if="show" class="box"></div> <div key="circle" v-else class="circle"></div> </FadeTransition> </div> </template> <script> import FadeTransition from './components/FadeTransition' export default { name: 'app', components: { FadeTransition }, data () { return { show: true } }, methods: { toggle () { this.show = !this.show } } } </script>
这个示例实现了一个盒子过渡成一个圆的效果:
现在 FadeTransition
组件能像常规的 <transition>
一样接受事件监听和 props
,这样也让该组件更加可重用。既然如此,我们给组件添加一个 duration
的 props
。
Vue的 <transition>
组件提供了一个 duration
属性,可以用来定制一个显性的过渡持续时间。在很多情况下,Vue可以自动得出过渡效果的完成时机。默认情况下,Vue会等待其在过渡效果的 根元素的第一个 transitionend
或 animationend
事件 。然而也可以不这样设定,比如,我们可以拥有一个精心设计过的过渡效果,其中一些嵌套的内部元素相比于过渡效果的根元素有延迟或更长的过渡效果。 在这个时候,就可以用上 <transition>
组件的 duration
属性 。
在我们的示例中,我们需要通过组件的 props
来控制CSS的 animation
或 transition
。实现起来并不太复杂,我们不在CSS中显式设置 animation-duration
或 transition-duration
样式,而是在Vue组件中,通过组件生命周期的钩子函数来实现。比如:
<!-- FadeTransition.vue --> <template> <transition name="fade" v-bind="$attrs" v-on="hooks" enter-active-class="fadeIn" leave-active-class="fadeOut"> <slot /> </transition> </template> <script> export default { name: 'FadeTransition', props: { duration: { type: Number, default: 300 } }, computed: { hooks() { return { beforeEnter:this.setDuration, afterEnter: this.cleanUpDuration, beforeLeave: this.setDuration, afterLeave: this.cleanUpDuration, ...this.$listeners } } }, methods: { setDuration(el) { el.style.animationDuration = `${this.duration}ms` }, cleanUpDuration(el){ el.style.animationDuration= '' } } } </script> <style scoped> @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fadeIn { animation-name: fadeIn; } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fadeOut { animation-name: fadeOut; } </style> <!-- App.vue --> <template> <div id="app"> <div class="toggle toggle--text"> <input type="checkbox" id="toggle--text" class="toggle--checkbox"> <label class="toggle--btn" for="toggle--text" data-label-on="Box" data-label-off="Circle" @click="toggle"></label> </div> <div class="duration"> <label for="duration">持续时间(Duration):</label> <input type="range" min="100" max="3000" v-model="duration" id="duration" /> <span>{{duration}}ms</span> </div> <FadeTransition mode="out-in" :duration="durationNumber"> <div key="box" v-if="show" class="box"></div> <div key="circle" v-else class="circle"></div> </FadeTransition> </div> </template> <script> import FadeTransition from './components/FadeTransition' export default { name: 'app', components: { FadeTransition }, data () { return { show: true, duration: 300 } }, methods: { toggle () { this.show = !this.show } }, computed: { durationNumber() { return parseInt(this.duration); } } } </script>
更详细的代码,可以把分支切换到 demo4
下。
列表过渡
Vue除了提供了 <transition>
组件之外还另外提供了一个 <transition-group>
组件。而这个组件也常常被称为 列表过渡 。该组件有几个特点:
- 不同于
<transition>
,它会以一个真实元素呈现:默认是一个<span>
,咱们可以通过tag
特性更换为其他元素 - 过渡模式 不可用,因为我们不一在相互切换特有的元素
- 内部元素总是需要提供唯五的
key
属性值
回到我们的话题中来,如果封装的 FadeTransition
组件面对列表这样的过渡效果呢?又应该如何呢?
可以大家会认为最简单的方式,就是重新构建一个Vue组件,比如 FadeTransitionGroup
,并将 <transition>
换成 <transition-group>
即可。这样做事实上并不会有问题,如果我们能做得更好,是不是应该选择更好的方式。比如说,同样维护前面创建的 FadeTransition
组件,而且该组件能让我们在 <transition>
和 <transition-group>
之间进行切换。
幸运的是,在Vue中,我们可以通过 渲染( render
)函数 或借助 component
的 is
属性( 动态组件 )来实现这一点。接下来把分支切换到 demo5
:
<!-- FadeTransition.vue --> <template> <component :is="type" :tag="tag" v-bind="$attrs" v-on="hooks" enter-active-class="fadeIn" leave-active-class="fadeOut" move-class="fade-move"> <slot /> </component> </template> <script> export default { name: 'FadeTransition', props: { duration: { type: Number, default: 300 }, group : { type: Boolean, default: false }, tag: { type: String, default: 'div' } }, computed: { type() { return this.group ? 'transition-group' : 'transition' }, hooks() { return { beforeEnter:this.setDuration, afterEnter: this.cleanUpDuration, beforeLeave: this.setDuration, afterLeave: this.cleanUpDuration, leave: this.setAbsolutePosition, ...this.$listeners } } }, methods: { setDuration(el) { el.style.animationDuration = `${this.duration}ms` }, cleanUpDuration(el){ el.style.animationDuration= '' }, setAbsolutePosition(el) { if (this.group) { el.style.position = 'absolute' } } } } </script> <style scoped> @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fadeIn { animation-name: fadeIn; } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fadeOut { animation-name: fadeOut; } .fade-move { transition: transform .3s ease-out; } </style> <!-- App.vue --> <template> <div id="app"> <div class="toggle toggle--btn" @click="addItem">添加</div> <div class="duration"> <label for="duration">持续时间(Duration):</label> <input type="range" min="100" max="3000" v-model="duration" id="duration" /> <span>{{duration}}ms</span> </div> <div class="tips">提示:点击下面的盒子,对应盒子会立即删除</div> <div class="wrapper"> <FadeTransition group :duration="durationNumber"> <div class="box" v-for="(item, index) in list" @click="remove(index)" :key="item"></div> </FadeTransition> </div> </div> </template> <script> import FadeTransition from './components/FadeTransition' export default { name: 'app', components: { FadeTransition }, data () { return { show: true, duration: 300, list: [1,2,3,4,5] } }, methods: { toggle () { this.show = !this.show }, remove(index) { this.list.splice(index, 1) }, addItem() { let randomIndex = Math.floor(Math.random() * this.list.length) this.list.splice(randomIndex, 0, Math.random()) } }, computed: { durationNumber() { return parseInt(this.duration); } } } </script>
效果如下:
这里有一个小细节需要注意,当元素删除时,我们必须为每个元素的 position
设置为 absolute
,以实现其他元素的平滑移动。另外手动添加了一个 move
类,指定 transform
持续的时间。
再做一些调整,并通过 mixin
中提取JavaScript逻辑,这样就可以将其应用于新的过渡组件。使用的时候,只需要将其放入到下一个项目中即可。
<!-- src/mixins/baseTransition.js --> export default { inheritAttrs: false, props: { duration: { type: [Number, Object], default: 300 }, group: { type: Boolean, default: false }, tag: { type: String, default: 'div' }, origin: { type: String, default: '' }, styles: { type: Object, default: () => { return { animationFillMode: 'both', animationTimingFunction: 'ease-out' } } } }, computed: { componentType() { return this.group ? 'transition-group' : 'transition' }, hooks() { return { beforeEnter: this.beforeEnter, afterEnter: this.cleanUpStyles, beforeLeave: this.beforeLeave, leave: this.leave, afterLeave: this.cleanUpStyles, ...this.$listeners } } }, methods: { beforeEnter(el) { let enterDuration = this.duration.enter ? this.duration.enter : this.duration el.style.animationDuration = `${enterDuration / 1000}s` this.setStyles(el) }, cleanUpStyles(el) { Object.keys(this.styles).forEach(key => { const styleValue = this.styles[key] if (styleValue) { el.style[key] = '' } }) el.style.animationDuration = '' }, beforeLeave(el) { let leaveDuration = this.duration.leave ? this.duration.leave : this.duration el.style.animationDuration = `${leaveDuration / 1000}s` this.setStyles(el) }, leave(el) { this.setAbsolutePosition(el) }, setStyles(el) { this.setTransformOrigin(el) Object.keys(this.styles).forEach(key => { const styleValue = this.styles[key] if (styleValue) { el.style[key] = styleValue } }) }, setAbsolutePosition(el) { if (this.group) { el.style.position = 'absolute' } return this }, setTransformOrigin(el) { if (this.origin) { el.style.transformOrigin = this.origin } return this } } } <!-- src/mixins/index.js --> import baseTransition from './baseTransition' export { baseTransition } <!-- FadeTransition.vue --> <template> <component :is="componentType" :tag="tag" v-bind="$attrs" v-on="hooks" enter-active-class="fadeIn" move-class="fade-move" leave-active-class="fadeOut"> <slot></slot> </component> </template> <script> import {baseTransition} from '../mixins/index.js' export default { name: 'FadeTransition', mixins: [baseTransition] } </script> <style> @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fadeIn { animation-name: fadeIn; } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fadeOut { animation-name: fadeOut; } .fade-move { transition: transform .3s ease-out; } </style>
更详细的您可以fork一份 @BinarCode 的 vue2-transitions 。
另外按照这样的方式,可以把 Animate.css 库中动画效果都封装成对应的Vue组件。感兴趣的同学,不仿一试。
小结
文章中从一个基本的 <transition>
示例开始,最后通过可调用 duration
和 <transition-group>
来创建可重用的 <transition>
组件。文章中的一些技巧只是一些最基本的技巧,你也可以根据自己的需要封装属于自己的过渡组件,如比 @BinarCode 的 vue2-transitions 一样,甚至你还可以将 Animate.css 库按照@BinarCode的方式将所有动效都封装成独立的Vue组件,从而实现可以在多处调用的目标。如果你有更好的方式,欢迎在下面的评论中与我们一起分享。
参考阅读
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Java 代码重用:功能与上下文重用
- 重用和单一职责可能是对立的
- UI技术总结--UITableView重用机制
- 利用Socket重用绕过payload受限
- 通过代码重用攻击绕过现代XSS防御
- asp.net mvc的代码重用
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。