8000 implement staggering transitions for v-repeat · Snoopbobb/vue@93a10b5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 93a10b5

Browse files
committed
implement staggering transitions for v-repeat
1 parent 1ac26d0 commit 93a10b5

File tree

2 files changed

+142
-38
lines changed

2 files changed

+142
-38
lines changed

src/directives/repeat.js

Lines changed: 141 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ module.exports = {
2323
bind: function () {
2424
// uid as a cache identifier
2525
this.id = '__v_repeat_' + (++uid)
26-
// setup anchor node
27-
this.anchor = _.createAnchor('v-repeat')
28-
_.replace(this.el, this.anchor)
26+
// setup anchor nodes
27+
this.start = _.createAnchor('v-repeat-start')
28+
this.end = _.createAnchor('v-repeat')
29+
_.replace(this.el, this.end)
30+
_.before(this.start, this.end)
2931
// check if this is a block repeat
3032
this.template = this.el.tagName === 'TEMPLATE'
3133
? templateParser.parse(this.el, true)
@@ -39,6 +41,10 @@ module.exports = {
3941
this.idKey =
4042
this._checkParam('track-by') ||
4143
this._checkParam('trackby') // 0.11.0 compat
44+
// check for transition stagger
45+
var stagger = +this._checkParam('stagger')
46+
this.enterStagger = +this._checkParam('enter-stagger') || stagger
47+
this.leaveStagger = +this._checkParam('leave-stagger') || stagger
4248
this.cache = Object.create(null)
4349
},
4450

@@ -239,7 +245,9 @@ module.exports = {
239245
diff: function (data, oldVms) {
240246
var idKey = this.idKey
241247
var converted = this.converted
242-
var anchor = this.anchor
248+
var start = this.start
249+
var end = this.end
250+
var inDoc = _.inDoc(start)
243251
var alias = this.arg
244252
var init = !oldVms
245253
var vms = new Array(data.length)
@@ -275,7 +283,7 @@ module.exports = {
275283
vms[i] = vm
276284
// insert if this is first run
277285
if (init) {
278-
vm.$before(anchor)
286+
vm.$before(end)
279287
}
280288
}
281289
// if this is the first run, we're done.
@@ -285,45 +293,38 @@ module.exports = {
285293
// Second pass, go through the old vm instances and
286294
// destroy those who are not reused (and remove them
287295
// from cache)
296+
var removalIndex = 0
297+
var totalRemoved = oldVms.length - vms.length
288298
for (i = 0, l = oldVms.length; i < l; i++) {
289299
vm = oldVms[i]
290300
if (!vm._reused) {
291301
this.uncacheVm(vm)
292-
vm.$destroy(true)
302+
vm.$destroy(false, true) // defer cleanup until removal
303+
this.remove(vm, removalIndex++, totalRemoved, inDoc)
293304
}
294305
}
295306
// final pass, move/insert new instances into the
296-
// right place. We're going in reverse here because
297-
// insertBefore relies on the next sibling to be
298-
// resolved.
299-
var targetNext, currentNext
300-
i = vms.length
301-
while (i--) {
307+
// right place.
308+
var targetPrev, prevEl, currentPrev
309+
var insertionIndex = 0
310+
for (i = 0, l = vms.length; i < l; i++) {
302311
vm = vms[i]
303-
// this is the vm that we should be in front of
304-
targetNext = vms[i + 1]
305-
if (!targetNext) {
306-
// This is the last item. If it's reused then
307-
// everything else will eventually be in the right
308-
// place, so no need to touch it. Otherwise, insert
309-
// it.
310-
if (!vm._reused) {
311-
vm.$before(anchor)
312+
// this is the vm that we should be after
313+
targetPrev = vms[i - 1]
314+
prevEl = targetPrev
315+
? targetPrev._staggerCb
316+
? targetPrev._staggerAnchor
317+
: targetPrev._blockEnd || targetPrev.$el
318+
: start
319+
if (vm._reused && !vm._staggerCb) {
320+
currentPrev = findPrevVm(vm, start)
321+
if (currentPrev !== targetPrev) {
322+
this.move(vm, prevEl)
312323
}
313324
} else {
314-
var nextEl = targetNext.$el
315-
if (vm._reused) {
316-
// this is the vm we are actually in front of
317-
currentNext = findNextVm(vm, anchor)
318-
// we only need to move if we are not in the right
319-
// place already.
320-
if (currentNext !== targetNext) {
321-
vm.$before(nextEl, null, false)
322-
}
323-
} else {
324-
// new instance, insert to existing next
325-
vm.$before(nextEl)
326-
}
325+
// new instance, or still in stagger.
326+
// insert with updated stagger index.
327+
this.insert(vm, insertionIndex++, prevEl, inDoc)
327328
}
328329
vm._reused = false
329330
}
@@ -559,12 +560,114 @@ module.exports = {
559560
this.converted = true
560561
return res
561562
}
563+
},
564+
565+
/**
566+
* Insert an instance.
567+
*
568+
* @param {Vue} vm
569+
* @param {Number} index
570+
* @param {Node} prevEl
571+
* @param {Boolean} inDoc
572+
*/
573+
574+
insert: function (vm, index, prevEl, inDoc) {
575+
if (vm._staggerCb) {
576+
vm._staggerCb.cancel()
577+
vm._staggerCb = null
578+
}
579+
var staggerAmount = this.getStagger(vm, index, null, 'enter')
580+
if (inDoc && staggerAmount) {
581+
// create an anchor and insert it synchronously,
582+
// so that we can resolve the correct order without
583+
// worrying about some elements not inserted yet
584+
var anchor = vm._staggerAnchor
585+
if (!anchor) {
586+
anchor = vm._staggerAnchor = _.createAnchor('stagger-anchor')
587+
anchor.__vue__ = vm
588+
}
589+
_.after(anchor, prevEl)
590+
var op = vm._staggerCb = _.cancellable(function () {
591+
vm._staggerCb = null
592+
vm.$before(anchor)
593+
_.remove(anchor)
594+
})
595+
setTimeout(op, staggerAmount)
596+
} else {
597+
vm.$after(prevEl)
598+
}
599+
},
600+
601+
/**
602+
* Move an already inserted instance.
603+
*
604+
* @param {Vue} vm
605+
* @param {Node} prevEl
606+
*/
607+
608+
move: function (vm, prevEl) {
609+
vm.$after(prevEl, null, false)
610+
},
611+
612+
/**
613+
* Remove an instance.
614+
*
615+
* @param {Vue} vm
616+
* @param {Number} index
617+
* @param {Boolean} inDoc
618+
*/
619+
620+
remove: function (vm, index, total, inDoc) {
621+
if (vm._staggerCb) {
622+
vm._staggerCb.cancel()
623+
vm._staggerCb = null
624+
// it's not possible for the same vm to be removed
625+
// twice, so if we have a pending stagger callback,
626+
// it means this vm is queued for enter but removed
627+
// before its transition started. Since it is already
628+
// destroyed, we can just leave it in detached state.
629+
return
630+
}
631+
var staggerAmount = this.getStagger(vm, index, total, 'leave')
632+
if (inDoc && staggerAmount) {
633+
var op = vm._staggerCb = _.cancellable(function () {
634+
vm._staggerCb = null
635+
remove()
636+
})
637+
setTimeout(op, staggerAmount)
638+
} else {
639+
remove()
640+
}
641+
function remove () {
642+
vm.$remove(function () {
643+
vm._cleanup()
644+
})
645+
}
646+
},
647+
648+
/**
649+
* Get the stagger amount for an insertion/removal.
650+
*
651+
* @param {Vue} vm
652+
* @param {Number} index
653+
* @param {String} type
654+
* @param {Number} total
655+
*/
656+
657+
getStagger: function (vm, index, total, type) {
658+
type = type + 'Stagger'
659+
var transition = vm.$el.__v_trans
660+
var hooks = transition && transition.hooks
661+
var hook = hooks && (hooks[type] || hooks.stagger)
662+
return hook
663+
? hook.call(vm, index, total)
664+
: index * this[type]
562665
}
563666

564667
}
565668

566669
/**
567-
* Helper to find the next element that is an instance
670+
* Helper to find the previous element that is an instance
568671
* root node. This is necessary because a destroyed vm's
569672
* element could still be lingering in the DOM before its
570673
* leaving transition finishes, but its __vue__ reference
@@ -575,10 +678,10 @@ module.exports = {
575678
* @return {Vue}
576679
*/
577680

578-
function findNextVm (vm, anchor) {
579-
var el = (vm._blockEnd || vm.$el).nextSibling
681+
function findPrevVm (vm, anchor) {
682+
var el = vm.$el.previousSibling
580683
while (!el.__vue__ && el !== anchor) {
581-
el = el.nextSibling
684+
el = el.previousSibling
582685
}
583686
return el.__vue__
584687
}

src/instance/init.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ exports._init = function (options) {
6464

6565
// props used in v-repeat diffing
6666
this._reused = false
67+
this._staggerOp = null
6768

6869
// merge options.
6970
options = this.$options = mergeOptions(

0 commit comments

Comments
 (0)
0