SCSS
* {
box-sizing: border-box;
}
h1 {
text-align: center;
}
#container {
width: 500px;
margin: 50px auto;
overflow: hidden;
border-radius: 4px;
position: relative;
background-color: #fff;
padding: 15px 20px;
box-shadow:rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
}
.bottom-bar {
top: 0;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
}
button {
background-color: #2196F3;
margin: 5px;
border:none;
border-radius: 4px;
line-height: 20px;
color: #fff;
padding: 10px;
cursor: pointer;
&:hover {
background-color: #31a6Ff;
}
}
.item {
float: left;
width: 80px;
height: 80px;
margin: 15px;
background-color: #3cf;
}
spirit-tag {
display: block;
}
.zoom-out {
opacity: 0;
transform: scale(.01) rotate(-90deg);
}
JAVASCRIPT
var SpiritTagAnimations = {};
(function () {
var getContentHeight = function (element, withPaddings) {
var height = 0;
var logs = [];
var children = element.children;
for (var index = 0, length = children.length; index < length; index++) {
var cs = window.getComputedStyle(children[index], null);
if (children[index].__ui_neutral || cs.position === 'absolute') {
continue;
}
var dimension = children[index].offsetTop + children[index].offsetHeight;
var marginBottom = parseInt(cs.marginBottom || 0);
height = dimension + marginBottom > height ? dimension + marginBottom : height;
}
if (withPaddings) {
height += parseInt(window.getComputedStyle(element).paddingBottom || 0);
}
return height;
};
// spirit animations should follow the service pattern, so the animation is a singleton object which is
// responsible for registering elements and managing their animations
SpiritTagAnimations.liveHeight = {};
SpiritTagAnimations.liveHeight.register = function (element) {
new LiveHeightAnimation(element);
};
SpiritTagAnimations.liveHeight.deregister = function (element) {
if (element.xtag.liveHeightAnimation) {
element.xtag.liveHeightAnimation.off();
}
};
function LiveHeightAnimation(element) {
var _this = this;
_this.element = element;
if (!this.observer) {
_this.observer = new MutationObserver(function (mutations) {
mutations.forEach(function (item) {
if (item.addedNodes[0] && item.addedNodes[0].__ui_neutral) {
return null;
}
if (item.removedNodes[0] && item.removedNodes[0].__ui_neutral) {
return null;
}
_this.animate();
});
});
window.requestAnimationFrame(function () {
_this.height = element.offsetHeight;
TweenLite.set(_this.element, {
height: _this.height
});
if (_this.observer) {
_this.observer.observe(_this.element, {
attributes: false,
childList: true,
characterData: false,
subtree: true
});
}
});
_this.resizeHandler = function () {
_this.animate();
};
window.addEventListener('resize', _this.resizeHandler);
}
_this.element.xtag.liveHeightAnimation = this;
}
LiveHeightAnimation.prototype.off = function () {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
window.removeEventListener('resize', this.resizeHandler);
TweenLite.set(this.element, {
height: ''
});
}
};
LiveHeightAnimation.prototype.animate = function () {
var _this = this;
var from = 0;
clearTimeout(_this.animationThrottle);
if(_this.animation) {
from = _this.animation.time();
}
var newHeight = getContentHeight(_this.element, true);
if (_this.height !== newHeight) {
_this.animation = TweenLite.fromTo(_this.element, .3, {
paused:true,
height: _this.height
}, {
height: newHeight,
ease: 'Power2.easeInOut',
onComplete: function () {
_this.animation = null;
_this.height = newHeight;
}
});
_this.animation.play(from);
}
};
// ------ //
SpiritTagAnimations.zoom = {};
SpiritTagAnimations.zoom.register = function (element) {
new ZoomInAnimation(element);
};
SpiritTagAnimations.zoom.deregister = function (element) {
if (element.xtag.PopInAnimation) {
element.xtag.PopInAnimation.off();
}
};
function ZoomInAnimation(element) {
var _this = this;
_this.element = element;
_this.zoomItem = element.getAttribute('zoom');
if (!this.observer) {
_this.observer = new MutationObserver(function (mutations) {
_this.stagger = 0;
var state = 'in';
var nodes = [];
mutations.forEach(function (item) {
var node = null;
if (item.addedNodes[0]) {
if (item.addedNodes[0].__ui_neutral || item.addedNodes[0].__zoom_out ||
item.addedNodes[0].nodeType !== Node.ELEMENT_NODE ||
!item.addedNodes[0].classList.contains(_this.zoomItem))
return null;
node = item.addedNodes[0];
}
if (item.removedNodes[0] && (item.removedNodes[0].__ui_neutral || item.removedNodes[0].__zoom_out)) {
item.removedNodes[0].__zoom_out = false;
return null;
}
if(item.removedNodes[0] ) {
node = item.removedNodes[0];
node.__zoom_out = true;
item.target.insertBefore(node,item.previousSibling.nextSibling);
state = 'out';
}
nodes.push(node);
});
_this.animate(nodes, state);
});
_this.observer.observe(_this.element, {
attributes: false,
childList: true,
characterData: false,
subtree: true
});
}
_this.element.xtag.PopInAnimation = this;
}
ZoomInAnimation.prototype.off = function () {
if (this.observer) {
this.observer.disconnect();
}
};
ZoomInAnimation.prototype.animate = function (node,style) {
var _this = this;
var from = 0;
if (!node.length) {
return;
}
//if (!_this.timeline) {
_this.timelineItems = [];
_this.timeline = new TimelineLite({
paused: true,
smoothChildTiming: true,
onComplete: function () {
_this.timeline = null;
from = 0;
}
});
//} else {
// _this.timeline.pause();
// from = _this.timeline.time();
//}
if(style === 'in') {
TweenLite.set(node, {
transition: 'none',
className :'+=zoom-out'
});
node.forEach(function(element){
_this.timelineItems.push(TweenLite.to(element, .5, {
className:'-=zoom-out',
ease: 'Power3.easeOut',
onComplete: function () { }
}))
});
_this.timeline.add(_this.timelineItems,null,null, 0.2);
} else {
TweenLite.set(node, {
transition: 'none',
className:'-=zoom-out'
});
node.forEach(function(element){
_this.timelineItems.push(TweenLite.to(element, .5, {
className:'+=zoom-out',
ease: 'Power3.easeOut',
onComplete: function () {
element.parentNode && element.parentNode.removeChild(element);
}
}))
});
_this.timeline.add(_this.timelineItems,null,null, 0.2);
}
_this.timeline.play(0);
};
})();
(function() {
var SpiritTag = {
lifecycle: {
created: function () {
var element = this;
element.xtag.animations = [];
element.xtag.registeredAnimations = [];
this.xtag.cachedAnimations = this.getAttribute('animations');
},
attributeChanged: function (attrName, oldValue, newValue) {
},
inserted: function () {
if (this.xtag.cachedAnimations && !this.xtag.animations.length) {
this.setAttribute('animations', this.xtag.cachedAnimations)
this.xtag.cachedAnimations = null;
this.prepare();
}
},
removed: function () {
this.xtag.cachedAnimations = xtag.clone(this.xtag.animations).join(',');
this.xtag.animations = [];
this.prepare();
}
},
accessors: {
animations: {
attribute: {
},
set: function (value) {
var element = this;
if (typeof value === 'string') {
this.xtag.animations = value.split(/[\s,]+/).filter(Boolean);
} else {
this.xtag.animations = [];
}
element.prepare();
},
get: function () {
return this.xtag.animations;
}
}
},
events: {
},
methods: {
prepare: function () {
var element = this;
this.xtag.animations.forEach(function (item) {
if (element.xtag.registeredAnimations.indexOf(item) !== -1) {
return null;
}
if (!SpiritTagAnimations[item]) {
return console.warn('spirit animation not found:', item);
}
SpiritTagAnimations[item].register(element);
element.xtag.registeredAnimations.push(item);
});
this.xtag.registeredAnimations = this.xtag.registeredAnimations.filter(function (item) {
if (element.xtag.animations.indexOf(item) === -1) {
SpiritTagAnimations[item].deregister(element);
return false;
}
return true;
});
}
}
};
xtag.register('spirit-tag', SpiritTag);
})();
var container = new Vue({
el:'#container',
data: {
items: []
},
methods: {
addItem: function() {
this.items.push({});
},
removeItem: function() {
this.items.pop();
}
}
});
container.items = [{},{},{}];