Skip to content

Commit

Permalink
css transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Aug 20, 2014
1 parent 9255cba commit 9ea6071
Show file tree
Hide file tree
Showing 17 changed files with 355 additions and 98 deletions.
16 changes: 15 additions & 1 deletion changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,18 @@ By default the callback only fires when the value changes. If you want it to be
``` js
vm.$watch('a', callback, true)
// callback is fired immediately with current value of `a`
```
```

## Simplified Transition API

- no more distinctions between `v-transition`, `v-animation` or `v-effect`;
- no more configuring enter/leave classes in `Vue.config`;
- `Vue.effect` has been replaced with `Vue.transition`, the `effects` option has also been replaced by `transitions`.

With `v-transition="my-transition"`, Vue will:

1. Try to find a transition definition object registered either through `Vue.transition(id, def)` or passed in with the `transitions` option, with the id `"my-transition"`. If it finds it, it will use that definition object to perform the custom JavaScript based transition.

2. If no custom JavaScript transition is found, it will automatically sniff whether the target element has CSS transitions or CSS animations applied, and add/remove the classes as before.

3. If no transitions/animations are detected, the DOM manipulation is executed immediately instead of hung up waiting for an event.
2 changes: 1 addition & 1 deletion gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function (grunt) {
src: 'src/**/*.js'
},
test: {
src: 'test/**/*.js'
src: ['test/unit/specs/**/*.js']
}
},

Expand Down
5 changes: 4 additions & 1 deletion src/directives/transition.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
module.exports = {

priority: 1000,
isLiteral: true,

bind: function () {
this.el.__vueTransition = this.expression
this.el.__v_trans = {
id: this.expression
}
}

}
2 changes: 1 addition & 1 deletion src/emitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ p.emit = function (event, a, b, c) {

p.applyEmit = function (event) {
this._cbs = this._cbs || {}
var callbacks = this._cbs[event], args
var callbacks = this._cbs[event]
if (callbacks) {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
Expand Down
28 changes: 23 additions & 5 deletions src/instance/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ exports._compileNode = function (node) {
}

/**
* Compile an Element
* Compile an Element.
*
* @param {Element} node
*/
Expand Down Expand Up @@ -79,7 +79,10 @@ exports._compileElement = function (node) {
}

/**
* Compile attribtues on an Element
* Compile attribtues on an Element.
* Collect directives, sort them by priority,
* then bind them. Also check normal directives for
* param attributes and interpolation bindings.
*
* @param {Element} node
*/
Expand Down Expand Up @@ -124,7 +127,13 @@ exports._compileAttrs = function (node) {
}

/**
* Compile a TextNode
* Compile a TextNode.
* Possible interpolations include:
*
* - normal text binding, e.g. {{text}}
* - unescaped html binding, e.g. {{{html}}}
* - one-time text binding, e.g. {{*text}}
* - one-time html binding, e.g. {{{*html}}}
*
* @param {TextNode} node
*/
Expand Down Expand Up @@ -166,7 +175,12 @@ exports._compileTextNode = function (node) {

/**
* Check for priority directives that would potentially
* skip other directives.
* skip other directives. Each priority directive, once
* detected, will cause the compilation to skip the rest
* and let that directive handle the rest. This allows,
* for example, "v-repeat" to handle how it should handle
* the situation when "v-component" is also present, and
* "v-component" won't have to worry about that.
*
* @param {Element} node
* @return {Boolean}
Expand Down Expand Up @@ -212,7 +226,11 @@ exports._bindDirective = function (name, value, node) {
}

/**
* Check an attribute for potential bindings.
* Check a normal attribute for bindings.
* A normal attribute could potentially be:
*
* - a param attribute (only on root nodes)
* - an interpolated attribute, e.g. attr="{{data}}"
*/

exports._bindAttr = function (node, attr) {
Expand Down
128 changes: 128 additions & 0 deletions src/transition/css.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
var _ = require('../util')
var Batcher = require('../batcher')
var batcher = new Batcher()
var transDurationProp = _.transitionProp + 'Duration'
var animDurationProp = _.animationProp + 'Duration'

/**
* Force layout before triggering transitions/animations
*/

batcher._preFlush = function () {
/* jshint unused: false */
var f = document.body.offsetHeight
}

/**
* Get an element's transition type based on the
* calculated styles
*
* @param {Element} el
* @return {Number}
* 1 - transition
* 2 - animation
*/

function getTransitionType (el) {
var styles = window.getComputedStyle(el)
if (styles[transDurationProp] !== '0s') {
return 1
} else if (styles[animDurationProp] !== '0s') {
return 2
}
}

/**
* Apply CSS transition to an element.
*
* @param {Element} el
* @param {Number} direction - 1: enter, -1: leave
* @param {Function} op - the actual DOM operation
* @param {Object} data - target element's transition data
*/

module.exports = function (el, direction, op, data) {
var classList = el.classList
var callback = data.callback
var prefix = data.id || 'v'
var enterClass = prefix + '-enter'
var leaveClass = prefix + '-leave'
// clean up potential previously running transitions
if (data.callback) {
el.removeEventListener(data.event, callback)
classList.remove(enterClass)
classList.remove(leaveClass)
data.event = data.callback = null
}
var transitionType, onEnd, endEvent

if (direction > 0) { // enter

classList.add(enterClass)
op()
transitionType = getTransitionType(el)
if (transitionType === 1) {
// Enter Transition
//
// We need to force a reflow to have the enterClass
// applied before removing it to trigger the
// transition, so they are batched to make sure
// there's only one reflow for everything.
batcher.push({
run: function () {
classList.remove(enterClass)
}
})
} else if (transitionType === 2) {
// Enter Animation
//
// Animations are triggered automatically as the
// element is inserted into the DOM, so we just
// listen for the animationend event.
endEvent = data.event = _.animationEndEvent
onEnd = data.callback = function (e) {
if (e.target === el) {
el.removeEventListener(endEvent, onEnd)
data.event = data.callback = null
classList.remove(enterClass)
}
}
el.addEventListener(endEvent, onEnd)
}

} else { // leave

transitionType = getTransitionType(el)
if (
transitionType &&
(el.offsetWidth || el.offsetHeight)
) {
// Leave Transition/Animation
//
// We push it to the batcher to ensure it triggers
// in the same frame with other enter transitions
// happening at the same time.
batcher.push({
run: function () {
classList.add(leaveClass)
}
})
endEvent = data.event = transitionType === 1
? _.transitionEndEvent
: _.animationEndEvent
onEnd = data.callback = function (e) {
if (e.target === el) {
el.removeEventListener(endEvent, onEnd)
data.event = data.callback = null
// actually remove node here
op()
classList.remove(leaveClass)
}
}
el.addEventListener(endEvent, onEnd)
} else {
op()
}

}
}
111 changes: 99 additions & 12 deletions src/transition/index.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,115 @@
var _ = require('../util')
var applyCSSTransition = require('./css')
var applyJSTransition = require('./js')

// TODO
// placeholder for testing
/**
* Append with transition.
*
* @oaram {Element} el
* @param {Element} target
* @param {Function} [cb]
* @param {Vue} vm
*/

exports.append = function (el, target, cb, vm) {
target.appendChild(el)
cb && cb()
apply(el, 1, function () {
target.appendChild(el)
if (cb) cb()
}, vm)
}

/**
* InsertBefore with transition.
*
* @oaram {Element} el
* @param {Element} target
* @param {Function} [cb]
* @param {Vue} vm
*/

exports.before = function (el, target, cb, vm) {
_.before(el, target)
cb && cb()
apply(el, 1, function () {
_.before(el, target)
if (cb) cb()
}, vm)
}

/**
* Remove with transition.
*
* @oaram {Element} el
* @param {Function} [cb]
* @param {Vue} vm
*/

exports.remove = function (el, cb, vm) {
_.remove(el)
cb && cb()
apply(el, -1, function () {
_.remove(el)
if (cb) cb()
}, vm)
}

/**
* Remove by appending to another parent with transition.
*
* @oaram {Element} el
* @param {Element} target
* @param {Function} [cb]
* @param {Vue} vm
*/

exports.removeThenAppend = function (el, target, cb, vm) {
target.appendChild(el)
cb && cb()
apply(el, -1, function () {
target.appendChild(el)
if (cb) cb()
}, vm)
}

exports.apply = function (el, direction, cb, vm) {

/**
* Apply transitions with an operation callback.
*
* @oaram {Element} el
* @param {Number} direction
* 1: enter
* -1: leave
* @param {Function} op - the actual DOM operation
* @param {Vue} vm
*/

var apply = exports.apply = function (el, direction, op, vm) {
function applyOp () {
op()
vm._callHook(direction > 0 ? 'attached' : 'detached')
}
// if the vm is being manipulated by a parent directive
// during the parent's compilation phase, we skip the
// animation.
if (vm.$parent && !vm.$parent._isCompiled) {
applyOp()
return
}
// determine the transition type on the element
var transData = el.__v_trans
var registry = vm.$options.transitions
var jsTransition = transData && registry[transData.id]
if (jsTransition) {
// js
applyJSTransition(
el,
direction,
applyOp,
jsTransition
)
} else if (transData && _.transitionEndEvent) {
// css
applyCSSTransition(
el,
direction,
applyOp,
transData
)
} else {
// not applicable
applyOp()
}
}
3 changes: 3 additions & 0 deletions src/transition/js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function () {

}
Loading

0 comments on commit 9ea6071

Please sign in to comment.