一、指令

  指令是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()语句来定义。例:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    

 

posted on 2018-04-16 11:59  minoz  阅读(271)  评论(0编辑  收藏  举报