Skip to content

Instantly share code, notes, and snippets.

@ncammarata
Created April 8, 2014 06:36
Show Gist options
  • Select an option

  • Save ncammarata/10097320 to your computer and use it in GitHub Desktop.

Select an option

Save ncammarata/10097320 to your computer and use it in GitHub Desktop.
/**
* x is a value between 0 and 1, indicating where in the animation you are.
*/
var duScrollDefaultEasing = function (x) {
if(x < 0.5) {
return Math.pow(x*2, 2)/2;
}
return 1-Math.pow((1-x)*2, 2)/2;
};
angular.module('duScroll', ['duScroll.scroller', 'duScroll.scrollPosition', 'duScroll.scrollspy', 'duScroll.requestAnimation', 'duScroll.smoothScroll']).value('duScrollDuration', 1000).value('duScrollEasing', duScrollDefaultEasing);
angular.module('duScroll.requestAnimation', []).
factory('requestAnimation', function($window, $timeout) {
return $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
$window.mozRequestAnimationFrame ||
$window.oRequestAnimationFrame ||
$window.msRequestAnimationFrame ||
function fallback( callback ){
$timeout(callback, 1000 / 60);
};
});
angular.module('duScroll.scrollPosition', ['duScroll.requestAnimation']).
factory('scrollPosition',
function($document, $rootScope, $timeout, requestAnimation) {
var getScrollY = function() {
return $('.comments-scroll')[0].scrollY || document.documentElement.scrollTop || document.body.scrollTop;
};
var getScrollX = function() {
return $('.comments-scroll')[0].scrollX || document.documentElement.scrollLeft || document.body.scrollLeft;
};
var observers = [];
var lastScrollY;
var currentScrollY;
var executeCallbacks = function(){
currentScrollY = lastScrollY;
$rootScope.$emit('$duScrollChanged', currentScrollY);
for(var i = 0; i < observers.length; i++){
observers[i](currentScrollY);
}
};
var onScroll = function(){
lastScrollY = getScrollY();
if(lastScrollY !== currentScrollY){
requestAnimation(executeCallbacks);
}
};
angular.element($document).on('scroll', onScroll).triggerHandler('scroll');
var deprecationWarned = false;
return {
observe : function(cb){
if(!deprecationWarned && console && console.warn) {
console.warn('scrollPosition.observe is deprecated, use $rootScope.$on(\'$duScrollChanged\') instead');
deprecationWarned = true;
}
observers.push(cb);
},
x: getScrollX,
y: getScrollY
};
}
);
angular.module('duScroll.scroller', ['duScroll.requestAnimation']).
factory('scroller',
function(requestAnimation, $window, scrollPosition, duScrollEasing) {
scrollDiv = $('.comments-scroll')[0]
function scrollTo(x, y, duration){
if(!duration) {
scrollDiv.scrollTop = y;
return;
}
var start = {
y: scrollPosition.y(),
x: scrollPosition.x()
};
var delta = {
y: Math.round(y - start.y),
x: Math.round(x - start.x)
};
if(!delta.x && !delta.y) return;
var frame = 0;
var frames = Math.ceil(duration/60);
var animate = function() {
frame++;
var percent = (frame === frames ? 1 : duScrollEasing(frame/frames));
scrollDiv.scrollTop = start.y + Math.ceil(delta.y * percent);
if(frame<frames) {
requestAnimation(animate);
}
};
animate();
}
function scrollDelta(x, y, duration){
scrollTo(scrollPosition.x() + (x || 0), scrollPosition.y() + (y || 0), duration);
}
function scrollToElement(element, offset, duration){
if(!angular.isElement(element)) { return; }
//Remove jQuery wrapper (if any)
element = element[0] || element;
if(!element.getBoundingClientRect) return;
var pos = element.getBoundingClientRect();
scrollDelta(0, pos.top + (!offset || isNaN(offset) ? 0 : -offset), duration);
}
return {
scrollTo: scrollTo,
scrollToElement: scrollToElement,
scrollDelta: scrollDelta
};
}
);
angular.module('duScroll.smoothScroll', ['duScroll.scroller']).
directive('duSmoothScroll', function(scroller, duScrollDuration){
return {
link : function($scope, $element, $attr){
var element = angular.element($element[0]);
element.on('click', function(e){
if(!$attr.href || $attr.href.indexOf('#') === -1) return;
var elem = document.getElementById($attr.href.replace(/.*(?=#[^\s]+$)/, '').substring(1));
if(!elem || !elem.getBoundingClientRect) return;
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
var offset = -($attr.offset ? parseInt($attr.offset, 10) : 0);
var duration = $attr.duration ? parseInt($attr.duration, 10) : duScrollDuration;
var pos = elem.getBoundingClientRect();
scroller.scrollDelta(0, pos.top + (isNaN(offset) ? 0 : offset), duration);
});
}
};
});
angular.module('duScroll.scrollspy', ['duScroll.scrollPosition']).
factory('duSpyAPI', function($rootScope, scrollPosition) {
var contexts = {};
var isObserving = false;
var createContext = function($scope) {
var id = $scope.$id;
contexts[id] = {
spies: []
};
return id;
};
var defaultContextId = createContext($rootScope);
var gotScroll = function($event, scrollY) {
var i, id, context, currentlyActive, toBeActive, spies, spy, pos;
for(id in contexts) {
context = contexts[id];
spies = context.spies;
currentlyActive = context.currentlyActive;
toBeActive = undefined;
for(i = 0; i < spies.length; i++) {
spy = spies[i];
pos = spy.getTargetPosition();
if (!pos) continue;
if(pos.top + spy.offset < 20 && pos.top*-1 < pos.height) {
if(!toBeActive || toBeActive.top < pos.top) {
toBeActive = {
top: pos.top,
spy: spy
};
}
}
}
if(toBeActive) {
toBeActive = toBeActive.spy;
}
if(currentlyActive === toBeActive) continue;
if(currentlyActive) {
currentlyActive.$element.removeClass('active');
$rootScope.$broadcast('duScrollspy:becameInactive', currentlyActive.$element);
}
if(toBeActive) {
toBeActive.$element.addClass('active');
$rootScope.$broadcast('duScrollspy:becameActive', toBeActive.$element);
}
context.currentlyActive = toBeActive;
}
};
var getContextForScope = function(scope) {
if(contexts[scope.$id]) {
return contexts[scope.$id];
}
if(scope.$parent) {
return getContextForScope(scope.$parent);
}
return contexts[defaultContextId];
};
var getContextForSpy = function(spy) {
return getContextForScope(spy.$element.scope());
};
var addSpy = function(spy) {
if(!isObserving) {
$rootScope.$on('$duScrollChanged', gotScroll);
isObserving = true;
}
getContextForSpy(spy).spies.push(spy);
};
var removeSpy = function(spy) {
var context = getContextForSpy(spy);
if(spy === context.currentlyActive) {
context.currentlyActive = null;
}
var i = context.spies.indexOf(spy);
if(i !== -1) {
context.spies.splice(i, 1);
}
};
return {
addSpy: addSpy,
removeSpy: removeSpy,
createContext: createContext
};
}).
directive('duSpyContext', function(duSpyAPI) {
return {
restrict: 'A',
scope: true,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink($scope, iElement, iAttrs, controller) {
duSpyAPI.createContext($scope);
}
};
}
};
}).
directive('duScrollspy', function(duSpyAPI) {
var Spy = function(targetElementOrId, $element, offset) {
if(angular.isElement(targetElementOrId)) {
this.target = targetElementOrId;
} else if(angular.isString(targetElementOrId)) {
this.targetId = targetElementOrId;
}
this.$element = $element;
this.offset = offset;
};
Spy.prototype.getTargetElement = function() {
if (!this.target && this.targetId) {
this.target = document.getElementById(this.targetId);
}
return this.target;
};
Spy.prototype.getTargetPosition = function() {
var target = this.getTargetElement();
if(target) {
return target.getBoundingClientRect();
}
};
Spy.prototype.flushTargetCache = function() {
if(this.targetId) {
this.target = undefined;
}
};
return {
link: function ($scope, $element, $attr) {
var href = $attr.ngHref || $attr.href;
var targetId;
if (href && href.indexOf('#') !== -1) {
targetId = href.replace(/.*(?=#[^\s]+$)/, '').substring(1);
} else if($attr.duScrollspy) {
targetId = $attr.duScrollspy;
}
if(!targetId) return;
var spy = new Spy(targetId, $element, -($attr.offset ? parseInt($attr.offset, 10) : 0));
duSpyAPI.addSpy(spy);
$scope.$on('$destroy', function() {
duSpyAPI.removeSpy(spy);
});
$scope.$on('$locationChangeSuccess', spy.flushTargetCache.bind(spy));
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment