(function() { var module = angular.module('app', [ 'nzAccordiScroll' ]); module.controller('mainController', function($scope) { }); })(); (function() { var module = angular.module('nzAccordiScroll', []); module.directive('nzAccordiScroll', ['$timeout', function($timeout) { return { restrict: 'EA', transclude: true, scope: { showStacks: '=', collapseSize: '=' }, replace: true, template: [ '
' ].join(' '), controller: function($scope) { var root = this; $scope.root = root; root.init = function() { root.states = []; root.stacks = []; root.stackElements = []; root.topBreaks = []; root.bottomBreaks = []; root.topActive = []; root.bottomActive = []; root.topBreaks = []; root.bottomBreaks = []; root.toppedOut = 0; root.bottomedOut = 0; root.collapseSize = $scope.collapseSize || 2; if (typeof $scope.showStacks != 'undefined') { root.hasMaxTop = true; root.hasMaxBottom = true; if (typeof $scope.showStacks == 'object') { root.maxTop = $scope.showStacks[0]; root.maxBottom = $scope.showStacks[1] + 2; } else if (typeof $scope.showStacks == 'number') { root.maxTop = $scope.showStacks; root.maxBottom = $scope.showStacks + 2; } if (typeof root.maxTop != 'undefined' && root.maxTop < 1) { root.maxTop = 1; } if (typeof root.maxBottom != 'undefined' && root.maxBottom < 3) { root.maxBottom = 3; } } }; var debounce; root.build = function(container) { if (debounce) { $timeout.cancel(debounce); } debounce = $timeout(function() { root.init(); angular.forEach(container.find('.stack.original'), function(stack, index) { if (angular.element(stack).scope().init) { angular.element(stack).scope().init(index); } }); $timeout.cancel(debounce); }, 100); }; root.register = function(index, el) { root.stacks[index] = el.outerHeight() - 1; root.states[index] = 3; root.stackElements[index] = el; if (!root.hasMaxTop) { root.maxTop = root.stacks.length; } if (!root.hasMaxBottom) { root.maxBottom = root.stacks.length; } calculateBreaks(); }; function calculateBreaks() { root.topBreaks = []; root.bottomBreaks = []; for (var i = 0; i < root.stacks.length; i++) { var topBreak = 0; var start = i - root.maxTop >= 0 ? i - root.maxTop : 0; for (var b = 0; b < root.stacks.length; b++) { if (b > start && b <= i) { topBreak += root.stacks[b]; } } var bottomBreak = 0; var end = i + root.maxBottom <= root.stacks.length ? i + root.maxBottom : root.stacks.length; for (var c = 0; c < root.stacks.length; c++) { if (c > i && c <= end) { bottomBreak += root.stacks[c]; } } root.topBreaks[i] = topBreak; root.bottomBreaks[i] = bottomBreak; } } root.init(); window.tanner = root; }, link: function($scope, el, attrs) { el.css({ position: 'relative', overflow: 'hidden', }); var content = el.find('.nzAccordiScroll-content').css({ overflowY: 'scroll', overflowX: 'hidden', height: '100%', width: '100%', zIndex: -1, WebkitOverflowScrolling: 'touch', paddingRight: '25px', boxSizing: 'content-box', }); content.bind('touchmove', drag); content.bind('mousedown', function() { window.addEventListener('mousemove', drag); window.addEventListener('mouseup', function(e) { drag(e); window.removeEventListener('mousemove', drag); }); }); function drag(e) { if (content[0].scrollLeft > 0) { content[0].scrollLeft = 0; } } } }; }]); module.directive('stack', ['$timeout', function($timeout) { return { restrict: 'EA', transclude: true, scope: true, replace: true, require: '^nzAccordiScroll', template: [ '
' ].join(' '), link: function($scope, el, attrs, root) { var container = el.closest('.nzAccordiScroll'); var content = el.closest('.nzAccordiScroll-content'); var clone = el.clone().addClass('clone').appendTo(container); el.addClass('original'); var index; var scrollTop; var scrollBottom; var containerTop; var elementTop; var elementRelativeTop; var elementRelativeBottom; var previousHeight; var previousBreak; var nextHeight; var nextBreak; clone.click(go); angular.element(window).bind('scroll', update); content.bind('scroll', update); el.on('$destroy', function() { $timeout(function() { clone.remove(); root.build(container); }, 100); }); $scope.init = function(i) { index = i; el.css({ cursor: 'pointer', position: 'relative', transition: 'all .1s linear', userSelect: 'none', overflow: 'hidden', }); clone.css({ cursor: 'pointer', position: 'absolute', transition: 'all .1s linear', width: '100%', userSelect: 'none', display: 'none', bottom: '0', overflow: 'hidden', }); scrollTop = container.scrollTop(); scrollBottom = scrollTop + container.innerHeight(); containerTop = container[0].offsetTop; elementTop = el[0].offsetTop; elementRelativeTop = elementTop - containerTop; elementRelativeBottom = elementRelativeTop + el.outerHeight(); root.register(index, el); if (root.preScroll) { $timeout.cancel(root.preScroll); } root.preScroll = $timeout(function() { content.animate({ scrollTop: content[0].scrollHeight > 800 ? 800 : content[0].scrollHeight }, 0); content.animate({ scrollTop: 0 }, 800); }, 400); $scope.go = go; update(); }; root.build(container); function go() { content.animate({ scrollTop: elementRelativeTop - previousBreak }, 800); } function update() { root.toppedOut = 0; root.bottomedOut = 0; for (var i = root.states.length - 1; i >= 0; i--) { if (root.states[i] == 1) { root.toppedOut++; } if (root.states[i] == 5) { root.bottomedOut++; } } scrollTop = content.scrollTop(); scrollBottom = scrollTop + content.innerHeight(); contentTop = content[0].offsetTop; elementTop = el[0].offsetTop; elementRelativeTop = elementTop - contentTop; elementRelativeBottom = elementRelativeTop + el.outerHeight(); previousBreak = root.topBreaks[index - root.toppedOut] ? root.topBreaks[index - root.toppedOut] : 0; nextBreak = root.bottomBreaks[index + root.bottomedOut] ? root.bottomBreaks[index + root.bottomedOut] : 0; if (!previousHeight) { previousHeight = root.stackElements[index - 1] ? root.stackElements[index - 1].outerHeight() : 0; } if (!nextHeight) { nextHeight = root.stackElements[index + 1] ? root.stackElements[index + 1].outerHeight() : 0; } var state = root.states[index]; var pastTop = scrollTop + previousBreak + previousHeight > elementRelativeTop; var pastPreviousBreak = scrollTop + previousBreak + root.toppedOut * root.collapseSize + (index > root.maxTop ? el.outerHeight() : 0) > elementRelativeTop; var pastNextBreak = scrollBottom - nextBreak - root.bottomedOut * root.collapseSize - ((root.stacks.length - 1 - index) > root.maxBottom ? el.outerHeight() : 0) < elementRelativeBottom; var pastBottom = scrollBottom - nextBreak - nextHeight < elementRelativeBottom; // Determine State clone.html(el.html()); if (pastPreviousBreak) { // if should be hidden if (root.states[index + 1 + root.maxTop] < 3) { if (root.states[index] > 1) { root.states[index] = 1; root.toppedOut++; } clone.css({ display: 'block', top: index * root.collapseSize + 'px', bottom: 'initial', zIndex: -(index - root.stacks.length) }); return; } else { // remove from hidden if (root.states[index] == 1) { root.states[index] = 2; root.toppedOut--; } // make sticky if (root.states[index] > 2) { root.states[index] = 2; } clone.css({ display: 'block', top: previousBreak + root.toppedOut * root.collapseSize + 'px', bottom: 'initial', zIndex: -(index - root.stacks.length) }); return; } } else if (pastNextBreak) { // if should be hidden if (root.states[index + 1 - root.maxBottom] > 3) { if (root.states[index] < 5) { root.states[index] = 5; root.bottomedOut++; } clone.css({ display: 'block', bottom: (root.stacks.length - 1 - index) * root.collapseSize + 'px', top: 'initial', zIndex: index }); return; } else { // remove from hidden if (root.states[index] == 5) { root.states[index] = 4; root.bottomedOut--; } // make sticky if (root.states[index] < 4) { root.states[index] = 4; } clone.css({ display: 'block', bottom: nextBreak + (root.bottomedOut * root.collapseSize) + 'px', top: 'initial', zIndex: 0 }); return; } } else { root.states[index] = 3; clone.css({ top: 'initial', bottom: (root.stacks.length - index) * root.collapseSize + 'px', zIndex: -(index - root.stacks.length), display: 'none' }); return; } } }, }; }]); })();