Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit 90efd74

Browse files
committed
init
0 parents  commit 90efd74

File tree

4 files changed

+270
-0
lines changed

4 files changed

+270
-0
lines changed

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# vue-animated-list
2+
3+
A Vue.js plugin for easily animating `v-for` rendered lists.
4+
5+
## Installation
6+
7+
- #### With Modules
8+
9+
``` js
10+
// ES6
11+
import Vue from 'vue'
12+
import VueAnimatedList from 'vue-animated-list'
13+
Vue.use(VueAnimatedList)
14+
15+
// ES5
16+
var Vue = require('vue')
17+
Vue.use(require('vue-animated-list'))
18+
```
19+
20+
- #### `<script>` Include
21+
22+
Just include `vue-animated-list.js` after Vue itself.
23+
24+
## Usage
25+
26+
There's nothing you need to do in JavaScript except for installation. In your markup, make sure the `v-for` has a transition attribute:
27+
28+
``` html
29+
<div v-for="item in items" transition="item">
30+
{{ item.text }}
31+
</div>
32+
```
33+
34+
Now, all you need to do is define the `.item-move` CSS class:
35+
36+
``` css
37+
.item-move {
38+
/* applied to the element when moving */
39+
transition: transform .5s cubic-bezier(.55,0,.1,1);
40+
}
41+
```
42+
43+
And that's it! You can also add CSS classes for enter and leave transitions - they all work nicely together!
44+
45+
A few things to note:
46+
47+
1. The animation is done using the CSS `transform` property. So make sure when `.item-move` is applied its `transform` property is transition-enabled.
48+
49+
2. Move animations can only work on elements, so it doesn't work for `<template v-for>` and fragment instance components.

example.html

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Vue animated list example</title>
6+
<script src="https://cdn.jsdelivr.net/lodash/4.3.0/lodash.min.js"></script>
7+
<script src="https://cdn.jsdelivr.net/vue/1.0.16/vue.min.js"></script>
8+
<script src="./vue-animated-list.js"></script>
9+
<style>
10+
.container {
11+
width: 300px;
12+
}
13+
.item {
14+
box-sizing: border-box;
15+
background-color: #eee;
16+
border: 1px solid black;
17+
display: inline-block;
18+
width: 100px;
19+
height: 100px;
20+
}
21+
.item-transition {
22+
transition: opacity .5s ease;
23+
}
24+
.item-enter {
25+
opacity: 0;
26+
}
27+
.item-leave {
28+
opacity: 0;
29+
position: absolute; /* important for removal move to work */
30+
}
31+
.item-move {
32+
color: red;
33+
transition: transform .5s cubic-bezier(.55,0,.1,1); /* applied when moving */
34+
}
35+
</style>
36+
</head>
37+
<body>
38+
39+
<div id="el">
40+
<button @click="shuffle">shuffle</button>
41+
<button @click="add">add</button>
42+
<button @click="remove">remove</button>
43+
<div class="container">
44+
<div class="item"
45+
v-for="item in items"
46+
transition="item">
47+
{{item.text}}
48+
</div>
49+
</div>
50+
</div>
51+
52+
<script>
53+
var items = []
54+
for (var i = 0; i < 9; i++) {
55+
items.push({ text: i })
56+
}
57+
var vm = new Vue({
58+
el: '#el',
59+
data: {
60+
items: items
61+
},
62+
methods: {
63+
shuffle: function () {
64+
this.items = _.shuffle(this.items)
65+
},
66+
add: function () {
67+
this.items.unshift({ text: this.items.length })
68+
},
69+
remove: function () {
70+
this.items.shift()
71+
}
72+
}
73+
})
74+
</script>
75+
</body>
76+
</html>

package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "vue-animated-list",
3+
"version": "1.0.0",
4+
"description": "A Vue.js plugin for easily animating `v-for` rendered lists.",
5+
"main": "vue-animated-list.js",
6+
"files": [
7+
"vue-animated-list.js"
8+
],
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/vuejs/vue-animated-list.git"
12+
},
13+
"keywords": [
14+
"vue",
15+
"vuejs",
16+
"animation"
17+
],
18+
"author": "Evan You",
19+
"license": "MIT",
20+
"bugs": {
21+
"url": "https://github.com/vuejs/vue-animated-list/issues"
22+
},
23+
"homepage": "https://github.com/vuejs/vue-animated-list#readme"
24+
}

vue-animated-list.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
(function (global, factory) {
2+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3+
typeof define === 'function' && define.amd ? define(factory) :
4+
(global.VueAnimatedList = factory());
5+
}(this, function () { 'use strict';
6+
function install (Vue) {
7+
var _ = Vue.util
8+
var transitionEndEvent = _.transitionEndEvent
9+
var addClass = _.addClass
10+
var removeClass = _.removeClass
11+
var on = _.on
12+
var off = _.off
13+
14+
// patch v-for
15+
var vFor = Vue.directive('for')
16+
var diff = vFor.diff
17+
vFor.diff = function () {
18+
var needMoveTransition = prepareMoveTransition(this.frags)
19+
diff.apply(this, arguments)
20+
if (needMoveTransition) {
21+
applyMoveTransition(this.frags)
22+
}
23+
}
24+
25+
/**
26+
* Check if move transitions are needed, and if so,
27+
* record the bounding client rects for each item.
28+
*
29+
* @param {Array<Fragment>|undefined} frags
30+
* @return {Boolean|undefined}
31+
*/
32+
33+
function prepareMoveTransition (frags) {
34+
var transition =
35+
transitionEndEvent && // css transition supported?
36+
frags && frags.length && // has frags to be moved?
37+
frags[0].node.__v_trans // has transitions?
38+
if (transition) {
39+
var node = frags[0].node
40+
var moveClass = transition.id + '-move'
41+
var moving = node._pendingMoveCb
42+
var type
43+
if (!moving) {
44+
// sniff whether element has a transition duration for transform
45+
// with the move class applied
46+
addClass(node, moveClass)
47+
type = transition.getCssTransitionType(moveClass, true)
48+
removeClass(node, moveClass)
49+
}
50+
if (moving || type === 'transition') {
51+
frags.forEach(frag => {
52+
frag._oldPos = frag.node.getBoundingClientRect()
53+
})
54+
return true
55+
}
56+
}
57+
}
58+
59+
/**
60+
* Apply move transitions.
61+
* Calculate new target positions after the move, then apply the
62+
* FLIP technique to trigger CSS transforms.
63+
*
64+
* @param {Array<Fragment>} frags
65+
*/
66+
67+
function applyMoveTransition (frags) {
68+
frags.forEach(function (frag) {
69+
var node = frag.node
70+
var oldPos = frag._oldPos
71+
if (!oldPos) return
72+
if (!frag.moved) {
73+
// transition busting to ensure correct bounding rect:
74+
// if an element has an ongoing transition and not "reinserted",
75+
// the bounding rect will not be calculated at its target position,
76+
// but rather an in-transition position.
77+
var p = node.parentNode
78+
var next = node.nextSibling
79+
p.removeChild(node)
80+
p.insertBefore(node, next)
81+
}
82+
var newPos = node.getBoundingClientRect()
83+
var dx = oldPos.left - newPos.left
84+
var dy = oldPos.top - newPos.top
85+
if (dx !== 0 || dy !== 0) {
86+
frag.moved = true
87+
node.style.transform = `translate(${dx}px, ${dy}px)`
88+
node.style.transitionDuration = '0s'
89+
} else {
90+
frag.moved = false
91+
}
92+
})
93+
Vue.nextTick(function () {
94+
var f = document.documentElement.offsetHeight
95+
frags.forEach(function (frag) {
96+
var node = frag.node
97+
var moveClass = node.__v_trans.id + '-move'
98+
if (frag.moved) {
99+
addClass(node, moveClass)
100+
node.style.transform = ''
101+
node.style.transitionDuration = ''
102+
if (node._pendingMoveCb) {
103+
off(node, transitionEndEvent, node._pendingMoveCb)
104+
}
105+
node._pendingMoveCb = function cb () {
106+
off(node, transitionEndEvent, cb)
107+
node._pendingMoveCb = null
108+
removeClass(node, moveClass)
109+
}
110+
on(node, transitionEndEvent, node._pendingMoveCb)
111+
}
112+
})
113+
})
114+
}
115+
}
116+
117+
if (typeof Vue !== 'undefined') {
118+
Vue.use(install)
119+
}
120+
return install
121+
}));

0 commit comments

Comments
 (0)