current position:Home>Vue0.11 source code reading series 6: transition principle

Vue0.11 source code reading series 6: transition principle

2021-08-24 15:14:47 Corner Kobayashi

css transition

First take a look at this version vue css How transitions and animations are applied :

<p class="msg" v-if="show" v-transition="expand">Hello!</p>
<p class="animated" v-if="show" v-transition="bounce">Look at me!</p>
.msg {
    transition: all .3s ease;
    height: 30px;
    padding: 10px;
    background-color: #eee;
    overflow: hidden;
}
.msg.expand-enter, .msg.expand-leave {
    height: 0;
    padding: 0 10px;
    opacity: 0;
}

.animated {
    display: inline-block;
}

.animated.bounce-enter {
    animation: bounce-in .5s;
}

.animated.bounce-leave {
    animation: bounce-out .5s;
}

@keyframes bounce-in {
    0% {
        transform: scale(0);
    }

    50% {
        transform: scale(1.5);
    }

    100% {
        transform: scale(1);
    }
}

@keyframes bounce-out {
    0% {
        transform: scale(1);
    }

    50% {
        transform: scale(1.5);
    }

    100% {
        transform: scale(0);
    }
}

You can see that it is also through instructions , This version only supports two classes , One is added when entering v-enter, The other is added when leaving v-leave.

Take a look at this instruction first :

module.exports = {
    isLiteral: true,//  by true Does not create watcher example 
    bind: function () {
        this.update(this.expression)
    },
    update: function (id) {
        var vm = this.el.__vue__ || this.vm
        this.el.__v_trans = {
            id: id,
            //  This version of vue have access to transitions Options to define JavaScript Animation 
            fns: vm.$options.transitions[id]
        }
    }
}

This instruction will not create watcher, Because the value of the instruction is either css The name of the class , Or JavaScript The name of the animation option , No observation is required . What you do when you bind an instruction is to give el Element adds a custom attribute , The value of the expression is saved , Here is expandJavaScript Animation functions , Here is undefined.

To trigger the animation, you need to modify it if Instructions show Value , Suppose the beginning is false, Let's change it to true, This triggers if The directive update Method , According to part III vue0.11 Version source code reading series 3 : Instruction compilation The last part is right if During the parsing of the instruction procedure, we know that transition.blockAppend(frag, this.end, vm), Called when leaving transition.blockRemove(this.start, this.end, this.vm), Obviously, it will be called here blockAppend

// block It includes if Code snippet of instruction binding element 
// target Is an annotation node , stay if The location of the instruction binding element 
exports.blockAppend = function (block, target, vm) {
    //  Child nodes of code snippets 
    var nodes = _.toArray(block.childNodes)
    for (var i = 0, l = nodes.length; i < l; i++) {
        apply(nodes[i], 1, function () {
            _.before(nodes[i], target)
        }, vm)
    }
}

Traversal element call apply Method :

var apply = exports.apply = function (el, direction, op, vm, cb) {
    var transData = el.__v_trans
    if (
        !transData ||//  No transition data 
        !vm._isCompiled ||//  The current instance has not been called $mount Method is inserted into the page 
        (vm.$parent && !vm.$parent._isCompiled)//  The parent component was not inserted into the page 
    ) {//  Animation is not required for the above situations , Just skip 
        op()
        if (cb) cb()
        return
    }
    var jsTransition = transData.fns
    // JavaScript Animation , Look at the next section 
    if (jsTransition) {
        applyJSTransition(
            el,
            direction,
            op,
            transData,
            jsTransition,
            vm,
            cb
        )
    } else if (
        _.transitionEndEvent &&
        //  No transition if the page is not visible 
        !(doc && doc.hidden)
    ) {
        // css
        applyCSSTransition(
            el,
            direction,
            op,
            transData,
            cb
        )
    } else {
        //  No transition is required 
        op()
        if (cb) cb()
    }
}

This method will determine whether it is an application JavaScript Animation or css Animation , And distributed to different functions to handle . Function dolls , Again applyCSSTransition Method :

module.exports = function (el, direction, op, data, cb) {
    var prefix = data.id || 'v'//  Here is expand
    var enterClass = prefix + '-enter'// expand-enter
    var leaveClass = prefix + '-leave'// expand-leave
    if (direction > 0) { //  Get into 
        //  Add the incoming class name to the element 
        addClass(el, enterClass)
        // op Namely _.before(nodes[i], target) operation , This step will add elements to the page 
        op()
        push(el, direction, null, enterClass, cb)
    } else { //  Leave 
        //  Add the left class name to the element 
        addClass(el, leaveClass)
        push(el, direction, op, leaveClass, cb)
    }
}

You can see that there is a difference between entering and leaving , This time we are putting show Change the value of to true, So I will go direction > 0 The branch of , First add the incoming class name to the element , Then actually insert the element into the page , Last call push Method ;

If you leave, you will first add the left class name to the element , And then call push Method ;

to glance at push Method :

var queue = []
var queued = false
function push (el, dir, op, cls, cb) {
    queue.push({
        el  : el,
        dir : dir,
        cb  : cb,
        cls : cls,
        op  : op
    })
    if (!queued) {
        queued = true
        _.nextTick(flush)
    }
}

Add this task to the queue , An asynchronous callback is registered and executed in the next frame , About nextTick For detailed analysis, please go to vue0.11 Version source code reading series 5 : How is batch update done .

addClass and op It's all synchronization tasks , Will be executed immediately , If there are more than one by this at the moment if The elements controlled by the instruction are added to the queue in turn , The result is that these elements will be added to the page , But because what we set for the incoming style is height: 0;opacity: 0;, So it's invisible , After these synchronous tasks are executed, they will go to the asynchronous queue to register flush Method is pulled out for execution :

function flush () {
  //  This method is used to trigger forced backflow , Make sure we add expand-enter The style can take effect , But I've tried to work without backflow 
  var f = document.documentElement.offsetHeight
  queue.forEach(run)
  queue = []
  queued = false
}

flush Method just added to queue Task object call in run Method , Because of asynchronous batch update , Therefore, if there are multiple element animations at the same time, only one reflow will be triggered :

function run (job) {
    var el = job.el
    var data = el.__v_trans
    var cls = job.cls
    var cb = job.cb
    var op = job.op
    // getTransitionType Method to get yes transition Transition or animation Animation , The principle is to judge elements style Object or getComputedStyle() Method in the style object obtained by the transitionDuration or animationDuration Whether the attribute exists and whether it is 0s
    var transitionType = getTransitionType(el, data, cls)
    if (job.dir > 0) { //  Get into 
        if (transitionType === 1) {// transition transition 
            //  because v-enter The style of is the style of the hidden element , In addition, because the element is set transition: all .3s ease, So as long as you delete this class, the transition effect will be applied naturally 
            removeClass(el, cls)
            //  Listen only when there is a callback transitionend event 
            if (cb) setupTransitionCb(_.transitionEndEvent)
        } else if (transitionType === 2) {// animation Animation 
            // animation Animation as long as you add v-enter Class triggers itself , All you need to do is listen animationend Event deletes this class after the animation 
            setupTransitionCb(_.animationEndEvent, function () {
                removeClass(el, cls)
            })
        } else {
            //  There is no transition 
            removeClass(el, cls)
            if (cb) cb()
        }
    } else { //  Leave 
        //  Leaving animation is easy , Both, just add v-leave Class can trigger the animation 
        //  All you have to do is listen for the event at the end of the animation, delete the element from the page and delete the class name from the element 
        if (transitionType) {
            var event = transitionType === 1
            ? _.transitionEndEvent
            : _.animationEndEvent
            setupTransitionCb(event, function () {
                op()
                removeClass(el, cls)
            })
        } else {
            op()
            removeClass(el, cls)
            if (cb) cb()
        }
    }
}

Now look at when show The value of is determined by true Change to false Called when blockRemove Method :

// start and end Are two annotation nodes , Surrounded the if All elements controlled by the instruction 
exports.blockRemove = function (start, end, vm) {
    var node = start.nextSibling
    var next
    while (node !== end) {
        next = node.nextSibling
        apply(el, -1, function () {
            _.remove(el)
        }, vm, cb)
        node = next
    }
}

Traversal elements are also called apply Method , It's just that the parameters are passed -1 It means leaving .

Here's a summary vue Of css transition :

1. Get into

Add to the element first v-enter class , Then insert the element into the page , Finally, create a task and add it to the queue , If there are more than one element, it will be completed at one time , Then perform the task just added at the next frame :

1.1css transition

v-enter Styles in class names are generally used to hide elements , For example, set the width and height of the element to 0、 Transparency set to 0 wait , It's right to be invisible anyway , To trigger the animation, you need to delete the class name , So the task here is to remove the elements v-enter Class name , Then the browser will apply the transition effect itself .

1.2css Animation

animation Dissimilarity ,v-enter The style of a class is generally defined animation The attribute value , such as :animation: bounce-out .5s;, Just add the class name , The animation will begin , So the task here is to listen to the animation end event to remove the element v-enter Class name .

2. Leave

css Transition and animation are the same when they leave , Is to add one to the element v-leave Class is OK ,v-leave Class to set the general and v-enter It's the same , Unless the in and out effect is different , Otherwise, it is necessary to make the element invisible , Then add a task , Because the style is not visible, but the element is actually on the page , So the final task is to listen to the animation end event and really remove the element from the page , Of course , Corresponding v-leave Class is also important Removed from element .

JavaScript Animation

To use in this version JavaScript For animation transitions, you need to use the declare transition option :

Vue.transition('fade', {
  beforeEnter: function (el) {
    //  The element is called before it is inserted into the document. , For example, extraction makes elements invisible , Otherwise, there will be a flash screen problem 
  },
  enter: function (el, done) {
    //  Element already inserted into DOM, After the animation is completed, you need to call... Manually done Method 
    $(el)
      .css('opacity', 0)
      .animate({ opacity: 1 }, 1000, done)
    //  Returns a function called when the animation is cancelled 
    return function () {
      $(el).stop()
    }
  },
  leave: function (el, done) {
    $(el).animate({ opacity: 0 }, 1000, done)
    return function () {
      $(el).stop()
    }
  }
})

Is to define three hook functions , Defined JavaScript Transition options , stay transition The directive update Method can get , This will lead to the above apply In the method jsTransition Branch , call applyJSTransition Method :

module.exports = function (el, direction, op, data, def, vm, cb) {
    if (data.cancel) {
        data.cancel()
        data.cancel = null
    }
    if (direction > 0) { //  Get into 
        //  call beforeEnter hook 
        if (def.beforeEnter) {
            def.beforeEnter.call(vm, el)
        }
        op()//  Insert element into page dom
        //  call enter hook 
        if (def.enter) {
            data.cancel = def.enter.call(vm, el, function () {
                data.cancel = null
                if (cb) cb()
            })
        } else if (cb) {
            cb()
        }
    } else { //  Leave 
        //  call leave hook 
        if (def.leave) {
            data.cancel = def.leave.call(vm, el, function () {
                data.cancel = null
                //  Leaving the animation is over, removing elements from the page 
                op()
                if (cb) cb()
            })
        } else {
            op()
            if (cb) cb()
        }
    }
}

Follow css Transition comparison ,JavaScript The transition is simple , Entering the transition is to execute the following initialization method before the element is actually inserted into the page , Then insert the element into the page , Next call enter The hook moves the elements as you like , Adjust it after the animation vue Injection method vue The animation is over , Before leaving the transition, adjust your exit hook , Delete elements from the page after your animation , The logic is simple .

copyright notice
author[Corner Kobayashi],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210824151338193B.html

Random recommended