Spirit Tag

HTML
<h1>Meet Spirit Tag</h1> <spirit-tag animations="liveHeight,zoom" zoom="item" id="container"> <div class="bottom-bar"> <button v-on:click="addItem()">Add Item</button> <button v-on:click="removeItem()">Remove Item</button> </div> <div v-for="item in items" class="item"></div> </spirit-tag>
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 = [{},{},{}];
Expand for more options Login