一、指令
指令是Vue.js中一个重要的特性,主要提供了一种机制将数据的变化映射为DOM行为。当数据变化时,指令会依据设定好的操作对DOM进行修改,这样就可以只关注数据的变化,而不用去管理DOM的变化和状态,使得逻辑更加清晰,可维护性更好。
1、内置指令
1.1、v-bind
v-bind主要用于动态绑定DOM元素属性(attribute),即元素属性实际的值由vm实例中的data属性提供的。例:
<img v-bind:src='avatar' /> new Vue({ data: { avatar: 'http://...' } });
v-bind可以简写为:,即可简写为<img :src='avatar' />。
v-bind还拥有三种修饰符,分别为.sync、.once、.camel,作用分别如下:
.sync:用于组件props属性,进行双向绑定,即父组件绑定传递给子组件的值,无论在哪个组件中对其进行了修改,其他组件中的这个值也会随之更新。但一般不推荐子组件直接修改父组件数据,这样会导致耦合且组件内的数据不容易维护。
.once:同.sync一样,用于组件props属性,但进行的是单次绑定。和双向绑定刚好相反,单次绑定是将绑定数据传递给子组件后,子组件单独维护这份数据,和父组件的数据再无关系,父组件的数据发生变化不影响子组件中的数据。
.camel:将绑定的特性名字转回驼峰命名。只能用于普通HTML属性的绑定,通常会用于svg标签下的属性。例:
<svg width='400' height='300' :view-box.camel='viewBox'></svg> 输出结果即为: <svg width='400' height='300' viewBox='...'></svg>
1.2、v-model
v-model指令主要用于input、select、textarea标签中,具有lazy、number、debounce(2.0废除)、trim(2.0新增)这些修饰符。
1.3、v-if/v-else/v-show
v-if/v-else/v-show这三个指令主要用于根据条件展示对应的模板内容。v-if在条件为false的情况下并不进行模板的编译,而v-show则会在模板编译好之后将元素隐藏掉。v-if的切换消耗要比v-show高,但初始条件为false的情况下,v-if的初始渲染要稍快。
1.4、v-for
v-for也是用于模板渲染的指令,在Vue.js 2.0中做了细微调整,大致包含:
1、参数顺序变化
当包含参数index或key时,对象参数修改为(item, index)或(value, key),这样与JSArray对象的新方法forEach和map,以及一些对象迭代器(例如lodash)的参数能保持一致。
2、v-bind:key
属性track-by被b-bind:key代替。
<div v-for="item in items" track-by="id"></div> 需改为 <div v-for="item in items" v-bind:key="item.id"></div>
3、n in 10
v-for="n in 10"中的n由原来的0~9迭代变成0~10迭代。
1.5、v-on
v-on指令主要用于事件绑定。例:
<button v-on:click="onClick"></button> v-on可以简写为: <button @click="onClick"></button>
修饰符包括.stop、.prevent、.capture、.self以及指定按键.{keyCode | keyAlias}。
在Vue.js 2.0中,在组件上使用v-on指令只监听自定义时间,即使用$emit触发的事件;如果要监听原生事件,需要使用修饰符.native,例如<my-component v-on:click.native="onClick"></my-component>。
1.6、v-text
v-text,参数类型为String,作用是更新元素的textContent。{{}}文本插值本身也会被编译成textNode的一个v-text指令。与{{}}不同,v-text需要绑定在某个元素上,能避免未编译前的闪现问题。例:
<span v-text="msg"></span>
1.7、v-HTML
v-HTML,参数类型为String,作用为更新元素的innerHTML,接受的字符串不会进行编译等操作,按普通HTML处理。同v-text类似,{{{}}}插值也会编译为节点的v-HTML指令,v-HTML也需要在某个元素上且能避免编译前闪现问题。例:
<div>{{ HTML }}</div> <div v-HTML="HTML"></div>
1.8、v-el
v-el指令为DOM元素注册了一个索引,使得我们可以直接访问DOM元素。语法上来说,可以通过所属实例的$els属性调用。例:
<div v-el:demo>there is a el demo</div> vm.$els.demo.innerText // there is a el demo
或者在vm内部通过this进行调用。
另外,由于HTML不区分大小写,在v-el中如果使用了驼峰式命名,系统会自动转成小写。但可以使用“-”来连接你期望大写的字母。例:
<div v-el:camelCase>There is a camelcase</div> <div v-el:camel-case>There is a camelCase</div> vm.$els.camelcase.innerText // There is a camelcase vm.$els.camelCase.innerText // There is a camelCase
1.9、v-ref
v-ref指令与v-el类似,只不过v-ref作用域组件上,实例可以通过$refs访问子组件。命名方式也类似,想使用驼峰式命名的话用“-”来做连接。例:
<message v-ref:title content="title"></message> <message v-ref:sub-title content="subTitle"></message> var Message = Vue.extend({ props: ['content'], template: '<h1>{{ content }}</h1>' }); Vue.component('message', Message);
从理论上来说,可以通过父组件对子组件进行任意的操作,但实际上尽量还是会采用props数据绑定,用组件间通信的方式去进行逻辑上的交互,尽量让组件只操作自己内部的数据和状态,如果组件间有通信,也通过调用组件暴露出来的接口进行通信,而不是直接跨组件修改数据。
1.10、v-pre
v-pre指令相对简单,就是跳过编译这个元素和子元素,显示原始的{{}}Mustache标签,用来减少编译时间。例:
<div v-pre>{{ uncompiled }}</div> var vm = new Vue({ el: '#app', data: { uncompiled: 'This is an uncompiled element' } });
最后输出:
<div>{{ uncompiled }}</div>
1.11、v-cloak
v-cloak指令相当于在元素上添加了一个[v-cloak]的属性,直到关联的实例结束编译。官方推荐可以和css规则[v-cloak]{display: none; }一起使用,可以隐藏未编译的Mustache标签直到实例准备完毕。例:
<div v0cloak>{{ msg }}</div>
1.12、v-once
v-once指令是Vue.js 2.0中新增的内置指令,用于标明元素或组件只渲染一次,即使随后发生绑定数据的变化或更新,该元素或组件及包含的子元素都不会被再次编译和渲染。使用方式:
<span v-once>{{ msg }}</span> <my-component v-once:msg="msg"></my-component>
2、自定义指令基础
除了内置指令外,Vue.js也提供了方法让我们可以注册自定义指令,以便封装对DOM元素的重复处理行为,提高代码复用率。
2.1、指令的注册
通过Vue.directive(id, definition)方法注册一个全局自定义指令,接收参数id和定义对象。id是指令的唯一标识,definition则是指令的相关属性及钩子函数。例:
Vue.directive('global-directive', definition); // 只注册了这个命令,并没有赋予这个指令任何功能
可以在模板中这样使用:
<div v-global-directive></div>
而除了全局注册指令外,我们也可以通过在组件的directives选项注册一个局部的自定义指令。例:
var comp = new Vue({ directives: { 'localDirective': {} // 可以采用驼峰式命名 } });
该指令就只能在当前组件内通过v-local-directive的方式调用,而取法被其他组件调用。
2.2、指令的定义对象
在注册指令的同时,可以传入definition对象,对指令赋予一些特殊的功能。这个定义对象主要包含三个钩子函数:bind、update和unbind。
bind:只被调用一次,在指令第一次绑定到元素上时调用。
update:指令在bind之后以初始值为参数进行第一次调用,之后每次当绑定值发生变化时调用,update接收到的参数为newValue和oldValue。
unbind:指令从元素上解绑时调用,只调用一次。
<div v-if="isExist" v-my-directive="param"></div> Vue.directive('my-directive', { bind: function() { console.log('bind', arguments); }, update: function(newValue, oldValue) { console.log('update', newValue, oldValue); }, unbind: function() { console.log('unbind', arguments); } }); var vm = new Vue({ el: '#app', data: { param: 'first', isExist: true } });
在控制台先后输入vm.param = 'second'和vm.isExist = false,整体输出如下:
bind [] update first undefined vm.param = 'second' update first second 'second' vm.isExist = false unbind [] false // 手打运行结果,具体可自己运行看输出
另外,如果我们只需要使用update函数时,可以直接传入一个函数代替定义对象:
Vue.directive('my-directive', function(value) { // 该函数即为update函数 });
上述例子中,可以使用my-directive指令绑定的值是data中的param属性。也可以直接绑定字符串常量,或使用字面修饰符,但这样的话需要注意update方法将只调用一次,因为普通字符串不能响应数据变化。例:
<div v-my-directive="constant string"></div> //value为undefined,因为data中没有对应的属性 <div v-my-directive="'constant string'"></div> //value为constant,绑定字符串需要加单引号 <div v-my-directive.literal="constant string"></div> //value为constant,利用字面修饰符后无需使用单引号
除了字符串外,指令也接受对象字面量或任意合法的JavaScript表达式。例:
<div v-my-directive="{ title : 'Vue.js', author: 'You' }"></div> <div v-my-directive="isExist ? 'yes' : 'no'"></div>
注意此时对象字面量不需要用单引号括起来,这和字符串常量不一样。
2.3、指令实例属性
除了了解指令的生命周期外,还需要知道指令中能调用的相关属性,以便对相关DOM进行操作。在指令的钩子函数内,可以通过this来调用指令实例。
el:指令绑定的元素。
vm:该指令的上下文ViewModel,可以为new Vue()的实例,也可以为组件实例。
expression:指令的表达式,不包括参数和过滤器。
arg:指令的参数。
name:指令的名字,不包括v-前缀。
modifiers:一个对象,包含指令的修饰符。
descriptor:一个对象,包含指令的解析结果。
<duv v-my-msg:console.log="content"></div> Vue.directive('my-msg', { bind: function() { console.log('~~~~~~~~bind~~~~~~~~~~~'); console.log('el', this.el); console.log('name', name); console.log('vm', this.vm); console.log('expression', this.expression); console.log('arg', this.arg); console.log('modifiers', this.modifiers); console.log('descriptor', this.descriptor); }, update: function(newValue, oldValue) { var keys = Object.keys(this.modifiers); window[this.arg][keys[0]](newValue); }, unbind: function() { } }); var vm = new Vue({ el: '#app', data: { content: 'there is the content' } });
2.4、元素指令
元素指令是Vue.js的一种特殊指令,普通指令需要绑定在某个具体的DOM元素上,但元素指令可以单独存在,从使用方式上看更像是一个组件,但本身内部的实例属性和钩子函数是和指令一样的。例:
<div v-my-directive></div> // 普通指令使用方式 <my-directive></my-directive> // 元素指令使用方式
元素指令的注册方式和普通指令类似,也有全局注册和局部注册两种。
Vue.elementDirective(;my-ele-directive'); // 全局注册方式 var Comp = Vue.extend({ ... // 省略了其他参数 elementDirective: { 'eleDirective': {} } }); Vue.component('comp', Comp);
元素指令不能接受参数或表达式,但可以读取元素特性从而决定行为。而且当编译过程中遇到一个元素指令时,Vue.js将忽略该元素及其子元素,只有元素指令本身才可以操作该元素及其子元素。
Vue.js 2.0中取消了这个特性,推荐使用组件来实现需要的业务。
3、指令的高级选项
Vue.js指令定义对象中除了钩子函数外,还有一些其他选项。
3.1、params
定义对象中可以接受一个params数组,Vue.js编译器将自动提取自定义指令绑定元素上的这些属性。例:
<div v-my-advance-directive a="paramA"></div> Vue.directive('my-advance-directive', { params: ['a'], bind: function() { console.log('params', this.params);
除了直接传入数值外,params支持绑定动态数据,并且可以设定一个watcher监听,但是护具变化时,会调用这个回调函数。例:
<div v-my-advance-directive v-bind:a="a"></div> // 当然可以简写成<div v-my-advance-directive :a="a"></div> Vue.directive('my-advance-directive', { params: ['a'], paramWatchers: { a: function(val, oldVal) { console.log('watcher:', val, oldVal); } }, bind: function() { console.log('params', this.params); } }); var vm = new Vue({ el: '#app', data: { a: 'dynamic data' } });
输出结果为:
params Object{a: "dynamic data"} vm.a = 123 watcher:123 dynamic data 123
3.2、deep
当自定义指令作用域一个对象上时,可以使用deep选项来监听对象内部发生的变化。例:
<div v-my-deep-directive="obj"></div> <div v-my-nodeep-directive="obj"></div> Vue.directive('my-deep-directive', { deep: true, update: function(newValue, oldValue) { console.log('deep', newValue.a.b); } }); Vue.directive('my-nodeep-directive', { update: function(newValue, oldValue) { console.log('nodeep', newValue.a.b); } }); var vm = new Vue({ el: '#app', data: { obj: { a: { b: 'inner' } } } });
运行后,在控制台中输入vm.obj.a.b = 'inner changed',只有my-deep-directive调用了update函数,输出了改变后的值。
输出结果为:
deep inner nodeep inner vm.obj.a.b = 'inner changed' deep inner changed
Vue.js 2.0中废弃了该选项。
3.3、twoWay
在自定义指令中,如果需要向Vue实例写回数据,就需要在定义对象中使用twoWay: true,这样可以在指令中使用this.set(value)来写回数据。
<input type="text" v-my-twoway-directive="param" /> Vue.directive('my-twoway-directive', { twoWay: true, bind: function() { this.handler = function() { console.log('value changed:', this.el.value); this.set(this.el.value); }.bind(this) this.el.addEventListener('input', this.handler) }, unbind: function() { this.el.removeEventListener('input', this.handler) } }); var vm = new Vue({ el: '#app', data: { param: 'first' } });
此时在input中输入文字,然后在控制台中输入vm.param即可观察到实例的param属性已被改变。
需要注意的是, 如果没有设定twoWay: true,就在自定义指令中调用this.set(),Vue.js会抛出异常。
3.4、acceptStatement
选项acceptStatement: true可以允许自定义指令接受内联语句,同时update函数接收的值是一个函数,在调用该函数时,它将在所属实例作用域内运行。
<div v-my-directive="i++"></div> Vue.directive('my-directice', { acceptStatement: true, update: function(fn) { } }); var vm = new Vue({ el:'#app', data: { i: 0 } });
如果在update函数中,运行fn(),则会执行内联语句i++,此时vm.i = 1.但更改vm.i并不会触发update函数。
需要当心的是,如果此时没有设定acceptSatement: true,该指令会陷入一个死循环中。v-my-directive接受到i的值 每次都在变化,会重复调用update函数,最终导致Vue.js抛出异常。
3.5、terminal
选项terminal的作用是阻止Vue.js便利这个元素及其内部元素,并由该指令本身去编译绑定元素及其内部元素。内置的指令v-if和v-for都是terminal指令。
使用terminal选项是一个相对复杂的过程,需要对Vue.js的编译过程有一定的了解。
<div id="modal"></div> ... <div v-inject:modal> <h1>header</h1> <p>body</p> <p>footer</p> </div>
var FragmentFactory = Vue.FragmentFactory // Vue.js全局API,用来创造fragment的工厂函数,fragment中包含了具体的scope和DOM元素,可以看成一个独立的组件或者实例。 var remove = Vue.util.remove // Vue.js工具类函数,移除DOM元素 var createAnchor = Vue.util.createAnchor // 创建锚点,锚点在debug模式下是注释节点,非debug模式下是文本节点,主要作用是标记DOM元素的插入和移除 Vue.directive('inject', { terminal: true, bind: function() { var container = document.getElementById(this.arg) // 获取需要注入到的DOM元素 this.anchor = createAnchor('v-inject') // 创建v-inject锚点 container.appendChild(this.anchor) // 锚点挂载到注入节点中 remove(this.el) // 移除指令绑定的元素 var factory = new FragmentFactory(this.vm, this.el) // 创建fragment this.frag = factory.create(this._host, this._scope, this._frag) // this._host 用于表示存在内容分发时的父组件 // this._scope 用于表示存在v-for时的作用域 // this._frag 用于表示该指令的父fragment this.frag.before(this.anchor) }, unbind: function() { this.frag.remove() remove(this.anchor) } });
3.6、priority
选项priority即为指定指令的优先级。普通指令默认是1000,terminal指令默认为2000.同一元素上优先级高的指令会比其他指令处理的早一些,相同优先级则按出现顺序依次处理。以下为内置指令的优先级顺序:
export const ON = 700 export const MODEL = 800 export const BIND = 850 export const TRANSITION = 1100 export const EL =1500 export const COMPONENT = 1500 export const PARTIAL = 1750 export const IF = 2100 export const FOR = 2200 export const SLOT = 2300
4、 指令在Vue.js 2.0中的变化
指令在Vue.js2.0中发生了较大的变化。总的来说,Vue.js2.0中的指令功能更为单一,很多和组件重复的功能和作用都进行了删除,指令也更专注于本身作用域的操作,而尽量不去影响指令外的DOM元素及数据。
4.1、新的钩子函数
钩子函数增加了一个componentUpdated,当整个组件都完成了update状态后即所有的DOM都更新后调用该钩子函数,无论指令接受的参数是否发生变化。
4.2、钩子函数实例和参数变化
在Vue.js2.0中取消了指令实例这一概念,即在钩子函数中的this并不能指向指令的相关属性。指令的相关属性均通过参数的形式传递给钩子函数。
Vue.directive('my-directive', { bind: function(el, binding, vnode) { console.log('~~~~~~~~~~~~~~~~~bind~~~~~~~~~~'); console.log('el', el); console.log('binding', binding); console.log('vnode', vnode); }, update: function(el, binding, vnode, lodVNode) { ... }, componentUpdated(el, binding, vnode, oldVNode) { ... }, unbind: function(el, binding, vnode) { ... } });
在Vue.js1.0中的实例中的属性大部分都能在binding中找到,vnode则主要包含了节点的相关信息,有点类似于fragment的作用。
4.3、update函数触发变化
钩子函数update对比Vue.js1.0也有以下两个变化:
(1)指令绑定bind函数执行后不直接调用update函数。
(2)只要组件发生重绘,无论指令接受的值是否发生变化,均会调用update函数。如果需要过滤不必要的更新,则可以使用binding.value == binding.olbValue来判断。
4.4、参数binding对象
钩子函数接受的参数binding对象为不可更改,强行设定binding.value的值并不会引起实际的改动。如果非要通过这种方式进行修改的话,只能通过el直接修改DOM元素。
二、过滤器
Vue.js允许在表达式后面添加可选的过滤器,以管道符表示,例:
{{ message | capitalize }}
过滤器的本质是一个函数,接受管道符前面的值作为初始值,同事也能接受额外的参数,返回值为经过处理后的输出值。多个过滤器也可以进行串联。例:
{{ message | filterA 'arg1' 'arg2' }}
{{ message | filterA | filterB }}
1、过滤器注册
Vue.js提供了全局方法Vue.filter()注册一个自定义过滤器,接受过滤器ID和过滤器函数两个参数。例:
Vue.filter('date', function(value) { if(!value instanceof Date) return value; return value.toLocaleDateString(); });
这样注册之后,就可以在vm实力的模板中使用这个过滤器了。
<div> {{ date | date }} </div> var vm = new Vue({ el: '#app', data: { date: new Date() } });
除了初始值之外,过滤器也能接受任意数量的参数。例:
Vue.filter('date', function(value, format) { var o = { "M+":value.getMonth() +1, // 月份 "d+":value.getDate(), // 日 "h+":value.getHours(), // 小时 "m+":value.getMinutes(), // 分 "s+":value.getSeconds(), // 秒 }; if(/(y+)/.test(format)) format = format.replace(RegExp.$1, (value.getFullYear() + "").substr(4 - RegExp.$1.length)); for(var k in o) if(new RegExp("(" + k + ")").test(format)) format = format.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return format; });
使用方式即为:
<div> {{ date | date 'yyyy-MM-dd hh:mm:ss' }} // -> 2018-04-25 10:46:46 即可按格式输出当前时间 </div>
2、双向过滤器
之前提及的过滤器都是在数据输出到视图之前,对数据进行转化显示,但不影响数据本身。Vue.js也提供了在改变视图中数据的值,写回data绑定属性中的过滤器,称为双向过滤器。例:
<input type="text" v-model="price | cents" /> // 该过滤器的作用是处理价钱的转化,一般数据库中保存的单位都为分,避免浮点运算 Vue.filter('cents', { read: function(value) { return (value / 100).toFixed(2); }, write: function(value) { return value * 100; } }); var vm = new Vue({ el: '#app', data: { price: 150 } });
3、动态参数
过滤器除了能接受单引号('')括起来的参数外,也支持接受在vm实例中绑定的数据,称之为动态参数。使用区别就在于不需要单引号将参数括起来。例:
<input type="text" v-model="price" /> <span>{{ data | dynamic price }}</span> Vue.filter('dynamic', function(data, price) { return date.toLocaleDateString() + ':' + price; }); var vm = new Vue({ el: '#app', data: { date: new Date(), price: 150 } });
过滤器中接受到的price参数即为vm.price。
4、过滤器在Vue.js2.0中的变化
(1)取消了所有内置过滤器,即capitalize,json等。建议尽量使用单独的插件来按需假如你所需要的过滤器。
(2)取消了对v-model和v-on的支持,过滤器只能使用在{{}}标签中。
(3)修改了过滤器参数的使用方式,采用函数的形式而不是空格来标记参数。例如:{{ date | date('yyyy-MM-dd') }}。
三、过渡
过渡系统是Vue.js为DOM动画效果提供的一个特性,它能在元素从DOM中插入或移除时触发CSS过渡(transition)和动画(animation),也就是说在DOM元素发生变化时为其添加特定的class类名,从而产生过渡效果。除了CSS过渡外,Vue.js的过渡系统也支持javascript的过渡,通过暴露过渡系统的钩子函数,可以在DOM变化的特定时机对其进行属性的操作,产生动画效果。
1、CSS过渡
1.1、CSS过渡的用法
<div v-if="show" transition="my-startup"></div> var vm = new Vue({ el: '#app', data: { show: false } });
首先在模板中用transition绑定一个DOM元素,并且使用v-if指令元素先处于未被编译状态。然后在控制台内手动调用vm.show = true,就可以看到DOM元素最后输出为:
<div class="my-startup-transition"></div>
DOM元素完成编译后,过渡系统自动给元素添加了一个my-startup-transition的class类名。为了让这个效果更明显一点,可以提前给这个类名添加一点CSS样式:
.my-startup-transition { transition: all 1s ease; width: 100px; height: 100px; background: black; opacity: 1; }
此时再重新刷新并手动运行vm.show = true,发现最终样式效果是加载上去了,但并没有出现transition效果。这是由于在编译v-if后,div直接挂载到body并添加my-startup-transition类名这两个过程中浏览器仅进行了一次重绘,这对于div来说并没有产生属性的更新,所以没有执行css transition的效果。为了解决这个问题,Vue.js的过渡系统给元素插入及移除时分别添加了2个类名:*-enter和*-leave,*即为transition绑定的字符串,本例中即为m-startup。所以在上述例子中,还需要添加两个类名样式,即my-startup-enter,my-startup-leave:
.my-startup-enter, .my-startup-leave { height: 0; opacity: 0; }
此时再重复之前的操作,就可以看到过渡效果了。需要注意的是,这两个类名的优先级要高于.my-startup-transition,不然被my-startup-transition覆盖后就失效了。
同样,也可以通过CSS的animation属性来实现过渡的效果,例:
<style> .my-animation-transition { animation: increase 1s ease 0s 1; width: 100px; height: 100px; background: black; } .my-animation-enter, .my-animation-leave { height: 0px; } @keyframes increase { from { height: 0px; } to { height: 100px; } } </style>
<div v-if="animation" transition="my-animation">animation</div> var vm = new Vue({ el: '#app', data: { animation: false } });
同样,更高vm.animation为true后即可看到过渡效果。
除了直接在元素上添加transition = "name"外,Vue.js也支持动态绑定CSS名称,可用于多个元素需要多个过渡效果的场景。例:
<div v-if="show" v-bind:transition="transitionName"></div> // 也可以简写成‘ <div v-if="show" :transition="transitionName"></div> var vm = new Vue({ el: '#app', data: { show: false, transitionName: 'fade' } });
Vue.js本身并不提供内置的过渡CSS样式,仅仅是提供了过渡需要使用的加载或移除时机,这样更便于灵活地按需去设计过渡样式。
1.2、CSS过渡钩子函数
Vue.js提供了在插入或DOM元素时类名变化的钩子函数,可以通过Vue.transition('name', {})的方式来执行具体的函数操作。例:
Vue.transition('my-startup', { beforeEnter: function(el) { console.log('boforeEnter', el.className); }, enter: function(el) { console.log('enter', el.className); }, afterEnter: function(el) { console.log('afterEnter', el.className); }, enterCancelled: function(el) { console.log('enterCancelled', el.className); }, beforeEnter: function(el) { console.log('boforeEnter', el.className); }, enter: function(el) { console.log('enter', el.className); }, afterEnter: function(el) { console.log('afterEnter', el.className); }, enterCancelled: function(el) { console.log('enterCancelled', el.className); }
在控制台里执行vm.show = true,输出结果如下:
vm.show = true beforeEnter my-startup-transition enter my-startup-transition my-startup-enter true afterEnter my-startup-transition
这样,我们能很清楚地看到钩子函数执行的顺序以及元素类名的变化。同样的,还可以再次更改vm.show的值置为false,结果如下:
vm.show = false beforeLeave my-startup-transition leave my-startup-transition my-startup-leave false afterLeave my-startup-transition
由于元素在使用CSS的transition和animation时,系统的流程不完全一样。所以先以transition为例,总结下过渡系统的流程。
当vm.show = true时,
(1)调用beforeEnter函数。
(2)添加enter类名到元素上。
(3)将元素插入到DOM中。
(4)调用enter函数。
(5)强制reflow一次,然后移除enter类名,触发过渡效果。
(6)如果此时元素被删除,则触发enterCancelled函数。
(7)监听transitionend事件,过渡结束后调用afterEnter函数。
当vm.show = false时,
(1)调用beforeLeave函数。
(2)添加v-leave类名,触发过渡效果。
(3)调用leave函数。
(4)如果此时元素被删除,则触发leaveCancelled函数。
(5)监听transitionend事件,删除元素及*-leave类名。
(6)调用afterLeave函数。
如果使用animation作为过渡的话,在DOM插入时,*-enter类名不会立即删除,而是在animationend事件触发时删除。
另外,enter和leave函数都有第二个可选的毁掉参数,用于控制过渡何时结束,而不是监听transitionend和animationend事件,例:
<style> .my-done-transition { transition: all 2s ease; width: 100px; height: 100px; background: black; opacity: 1; } .my-done-enter, .my-done-leave { height: 0; opacity: 0; } </style>
Vue.transition('my-done', { enter: function(el, done) { this.enterTime = new Date(); setTimeout(done, 500); }, afterEnter: function(el) { console.log('afterEnter', new Date() - this.enterTime); } }); var vm = new Vue({ el: '#app', data: { done: false } });
输出结果如下:
vm.done = true true afterEnter 500
此时afterEnter函数执行的事件就不是my-done-transition样式中的2s之后,而是done调用的500ms之后。需要注意的是 ,如果在enter和leave中声明了形参done,但没有调用,则不会触发afterEnter函数。
1.3 显示声明过渡类型
Vue.js可以指定过渡元素监听的结束事件的类型,例:
Vue.transition('done-type', { type: 'animation' });
此时Vue.js就只监听元素的animationend事件,避免元素上还存在transition时导致的结束事件触发不一样。
1.4 自定义过渡类名
除了使用默认的类名*-enter、*-leave外,Vue.js也允许我们自定义过渡类名,例:
Vue.transition('my-startup', { enterClass: 'fadeIn', leaveClass: 'fadeOut' });
我们可以通过上述钩子函数的例子,观测元素的类名变化:
vm.show = true beforeEnter my-startup-transition enter my-startup-transition my-startup-enter true afterEnter my-startup-transition vm.show = false beforeLeave my-startup-transition leave my-startup-transition my-startup-leave false afterLeave my-startup-transition
Vue.js官方推荐了一个CSS动画库,animate.css,配合自定义过渡类名使用,可以达到非常不错的效果。只需要引入一个CSS文件,http://cdn.bootcss.com/animate.css/3.5.2/animate.min.css,就可以使用里面的预设动画。例:
Vue.transition('bounce', { enterClass: 'bounceIn', leaveClass: 'bounceOut' });
<div v-if="animateShow" class="animated" transition="bounce">bounce effect</div>
在使用animate.css时,需要先给元素附上animated类名,然后再添加预设的动效类名,例如上例中的bounceIn、bounceOut,这样就能看到动画效果。这个库提供了多种强调展示(例如弹性、抖动)、渐入渐出、翻转、旋转、放大缩小等效果。所有的效果可以访问官网地址http://daneden.github.io/animate.css/在线观看。
2. JavaScript过渡
Vue.js也可以和一些JavaScript动画库配合使用,这里只需要调用JavaScript钩子函数,而不需要定义CSS样式。transition接受选项css: false,将直接跳过CSS检测,避免CSS规则干扰过渡,而且需要在enter和leave钩子函数中调用done函数,明确过渡结束事件。此处将引入Velocity.js来配合使用JavaScript过渡。
2.1 Velocity.js
Velocity.js是一款搞笑的动画引擎,可以单独使用也可以配合jQuery使用。它拥有和jQuery的animate一样的api接口,但比jQuery在动画处理方面更强大、更流畅,以及模拟了一些现实世界的运动,例如弹性动画等。
Velocity.js可以当做jQuery的插件使用,例:
$element.velocity({ left: "100px" }, 500, "swing", function() { console.log("done"); }); $element.velocity({ left: "100px" }, { duration: 500, easing: "swing", complete: function() { console.log("done"); } });
也可以单独使用,例:
var el = document.getElementById(id); Velocity(el, { left: '100px' }, 500, 'swing', done);
2.2 JavaScript过渡使用
可以通过以下方式注册一个自定义的JavaScript过渡:
<style> .my-velocity-transition { position: absolute; top: 0; width: 100px; height: 100px; background: black; } </style>
<div v-if="velocity" transition="my-velocity"></div>
Vue.transition('my-velocity', { css: false, enter: fumction(el, done) { Velocity(el, { left: '100px' }, 500, 'swing', done); }, enterCancelled: function(el) { Velocity(el, 'stop'); }, leave: fumction(el, done) { Velocity(el, { left: '0px' }, 500, 'swing', done); }, leaveCancelled: function(el) { Velocity(el, 'stop'); } });
运行上述代码,在设置vm.velocity = true后,过渡系统即会调用enter钩子函数,通过Velocity对DOM操作展现动画效果,然后强制调用done函数,明确结束过渡效果。
3.过渡系统在Vue.js 2.0中的变化
过渡系统在Vue.js 2.0中也发生了比较大的变化,借鉴了ReactJS CSSTransitionGroup的一些相关设定和命名。
3.1 用法变化
新的过渡系统中取消了v-transition这个指令,新增了名为transition的内置标签,用法变更为:
<transition name="fade"> <div class="content" v-if="show">content</div> </transition>
transition标签为一个抽象组件,并不会额外渲染一个DOM元素,仅仅是用于包裹过渡元素及触发过渡行为。v-if、v-show等指令仍旧标记在内容元素上,并不会作用于transition标签上。
transition标签能接受的参数与Vue.js 1.0中注册的transition接受的选项类似。
1.name
同v-transition中接受的参数,自动生成对应的name-enter,name-enter-active类名。
2.appear
元素首次渲染的时候是否启用transition,默认值为false。即v-if绑定值初始为true时,首次渲染时是否调用transition效果。在Vue.js 1.0中,v-if如果初始值为true的话,首次渲染时无法使用transition效果的,只有v-show能使用。
3.css
同Vue.js 1.0的CSS选项,如果设置为true,则只监听钩子函数的调用。
4.type
同Vue.js 1.0的type选项,设置监听的CSS动画结束事件的类型。
5.mode
控制过渡插入/移除的先后顺序,主要用于元素切换时。可供选择的值有“out-in”、“in-out”,如果不设置,则同时调用。例:
<transition name="fade" mode="out-in"> <p :key="ok">{{ ok }}</p> // 这里的:key="ok"主要用于强制替换元素,展现出in-out/out-in效果 </transition>
当ok在true和false切换时,mode="out-in"决定先移除<p>false</p>,等过渡结束后,再插入<p>true</p>元素,mode="in-out"则相反。
6.钩子函数
enterClass、leaveClass、enterActiveClass、leaveActiveClass、appearClass、appearActiveClass,可以分别自定义各阶段的class类名。
总的来说,在Vue.js 2.0中我们可以直接使用transition标签并设定其属性来定义一个过渡效果,而不需要像在Vue.js 1.0中通过Vue.transition()语句来定义。例: