let slider = new Vue({ el: '#slider', data: { items: [ {background: '#1A237E'}, {background: '#283593'}, {background: '#303F9F'}, {background: '#3949AB'}, {background: '#3F51B5'}, {background: '#5C6BC0'}, {background: '#7986CB'} ] }, methods: { setElementIndex: function() { this.itemElements = document.querySelectorAll('.item'); }, update: function() { console.log('update') if (this.animate) requestAnimationFrame(this.update); if (!this.target) return; if (this.dragging) this.updateItem(this.currentX - this.startX); else if (this.remove) this.slideAway(); else this.resetItem(); }, updateItem: function(translationX) { this.target.style.transform = `translateX(${translationX}px)`; this.target.style.opacity = 1 - Math.abs(this.originX - this.target.getBoundingClientRect().left) / this.target.getBoundingClientRect().width; }, onDown: function(event) { this.$el.style.willChange = 'transform'; this.originX = this.$el.getBoundingClientRect().left; this.startX = event.pageX || event.touches[0].pageX; this.dragging = true; this.itemElements.forEach((item, i) => { if (item === event.target) this.index = i; }); this.target = this.itemElements[this.index]; if (this.target) { this.animate = true; requestAnimationFrame(this.update); } }, onMove: function(event) { this.currentX = event.pageX || event.touches[0].pageX || 0; }, onUp: function(event) { if (this.itemElements.length < 1 || !this.target) return; this.dragging = false; this.releaseX = this.target.getBoundingClientRect().left; this.slideOffset = this.target.getBoundingClientRect().left - this.originX; this.remove = (Math.abs(this.currentX - this.startX) > (this.$el.getBoundingClientRect().width * .55)); }, resetItem: function() { let easing = (this.originX - this.target.getBoundingClientRect().left) * .8; let translationX = this.target.getBoundingClientRect().left + (this.currentX - this.startX < 0) ? -easing : easing; // Snap the easing algorithm & check for corner case 0 to ensure no mem leaks. if (Math.abs(this.originX - this.target.getBoundingClientRect().left) < 2 || translationX == 0 || easing == 0) { translationX = 0; this.updateItem(translationX); this.target.style.willChange = 'initial'; this.target.style.transform = 'none'; this.animate = false; this.index = -1; this.target = null; return; } this.updateItem(translationX); }, slideAway: function() { let direction = (this.releaseX < this.originX) ? -1 : 1; let easing = (this.originX + direction * this.target.getBoundingClientRect().width - this.releaseX) / 10; this.slideOffset += easing; this.updateItem(this.slideOffset); if (this.target.getBoundingClientRect().left < this.originX - this.target.getBoundingClientRect().width || this.target.getBoundingClientRect().left > this.originX + this.target.getBoundingClientRect().width) this.deleteItem(); }, deleteItem: function() { let onAnimationComplete = (event) => { event.target.removeEventListener( 'transitionend', onAnimationComplete ); event.target.style.transition = ''; event.target.style.transform = ''; this.animate = false; }; for (let i = this.index + 1; i < this.itemElements.length; ++i) { this.itemElements[i].style.transform = `translateY( ${ this.itemElements[i - 1].getBoundingClientRect().height }px )`; this.itemElements[i].addEventListener( 'transitionend', onAnimationComplete ); } this.target.parentNode.removeChild(this.target); requestAnimationFrame( () => { for (let i = this.index + 1; i < this.itemElements.length; ++i) { this.itemElements[i].style.transition = `transform 150ms cubic-bezier( 0,0,0.31,1 ) ${ i * 50 }ms`; this.itemElements[i].style.transform = ''; } }); this.setElementIndex(); this.index = -1; this.target = null; } }, mounted: function() { this.setElementIndex(); document.addEventListener( 'mousedown', this.onDown ); document.addEventListener( 'mousemove', this.onMove ); document.addEventListener( 'mouseup', this.onUp ); document.addEventListener( 'touchstart', this.onDown ); document.addEventListener( 'touchmove', this.onMove ); document.addEventListener( 'touchend', this.onUp ); } });