mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
316 lines
8.6 KiB
JavaScript
316 lines
8.6 KiB
JavaScript
IonicModule
|
|
.controller('$ionicRefresher', [
|
|
'$scope',
|
|
'$attrs',
|
|
'$element',
|
|
'$ionicBind',
|
|
'$timeout',
|
|
function($scope, $attrs, $element, $ionicBind, $timeout) {
|
|
var self = this,
|
|
isDragging = false,
|
|
isOverscrolling = false,
|
|
dragOffset = 0,
|
|
lastOverscroll = 0,
|
|
ptrThreshold = 60,
|
|
activated = false,
|
|
scrollTime = 500,
|
|
startY = null,
|
|
deltaY = null,
|
|
canOverscroll = true,
|
|
scrollParent,
|
|
scrollChild;
|
|
|
|
if (!isDefined($attrs.pullingIcon)) {
|
|
$attrs.$set('pullingIcon', 'ion-android-arrow-down');
|
|
}
|
|
|
|
$scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';
|
|
|
|
$scope.showIcon = isDefined($attrs.refreshingIcon);
|
|
|
|
$ionicBind($scope, $attrs, {
|
|
pullingIcon: '@',
|
|
pullingText: '@',
|
|
refreshingIcon: '@',
|
|
refreshingText: '@',
|
|
spinner: '@',
|
|
disablePullingRotation: '@',
|
|
$onRefresh: '&onRefresh',
|
|
$onPulling: '&onPulling'
|
|
});
|
|
|
|
function handleTouchend() {
|
|
// if this wasn't an overscroll, get out immediately
|
|
if (!canOverscroll && !isDragging) {
|
|
return;
|
|
}
|
|
// reset Y
|
|
startY = null;
|
|
// the user has overscrolled but went back to native scrolling
|
|
if (!isDragging) {
|
|
dragOffset = 0;
|
|
isOverscrolling = false;
|
|
setScrollLock(false);
|
|
return true;
|
|
}
|
|
isDragging = false;
|
|
dragOffset = 0;
|
|
|
|
// the user has scroll far enough to trigger a refresh
|
|
if (lastOverscroll > ptrThreshold) {
|
|
start();
|
|
scrollTo(ptrThreshold, scrollTime);
|
|
|
|
// the user has overscrolled but not far enough to trigger a refresh
|
|
} else {
|
|
scrollTo(0, scrollTime, deactivate);
|
|
isOverscrolling = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function handleTouchmove(e) {
|
|
// if multitouch or regular scroll event, get out immediately
|
|
if (!canOverscroll || e.touches.length > 1) {
|
|
return;
|
|
}
|
|
//if this is a new drag, keep track of where we start
|
|
if (startY === null) {
|
|
startY = parseInt(e.touches[0].screenY, 10);
|
|
}
|
|
|
|
// how far have we dragged so far?
|
|
deltaY = parseInt(e.touches[0].screenY, 10) - startY;
|
|
|
|
// if we've dragged up and back down in to native scroll territory
|
|
if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
|
|
|
|
if (isOverscrolling) {
|
|
isOverscrolling = false;
|
|
setScrollLock(false);
|
|
}
|
|
|
|
if (isDragging) {
|
|
nativescroll(scrollParent,parseInt(deltaY - dragOffset, 10) * -1);
|
|
}
|
|
|
|
// if we're not at overscroll 0 yet, 0 out
|
|
if (lastOverscroll !== 0) {
|
|
overscroll(0);
|
|
}
|
|
|
|
return true;
|
|
|
|
} else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
|
|
// starting overscroll, but drag started below scrollTop 0, so we need to offset the position
|
|
dragOffset = deltaY;
|
|
}
|
|
|
|
// prevent native scroll events while overscrolling
|
|
e.preventDefault();
|
|
|
|
// if not overscrolling yet, initiate overscrolling
|
|
if (!isOverscrolling) {
|
|
isOverscrolling = true;
|
|
setScrollLock(true);
|
|
}
|
|
|
|
isDragging = true;
|
|
// overscroll according to the user's drag so far
|
|
overscroll(parseInt((deltaY - dragOffset) / 3, 10));
|
|
|
|
// update the icon accordingly
|
|
if (!activated && lastOverscroll > ptrThreshold) {
|
|
activated = true;
|
|
ionic.requestAnimationFrame(activate);
|
|
|
|
} else if (activated && lastOverscroll < ptrThreshold) {
|
|
activated = false;
|
|
ionic.requestAnimationFrame(deactivate);
|
|
}
|
|
}
|
|
|
|
function handleScroll(e) {
|
|
// canOverscrol is used to greatly simplify the drag handler during normal scrolling
|
|
canOverscroll = (e.target.scrollTop === 0) || isDragging;
|
|
}
|
|
|
|
function overscroll(val) {
|
|
scrollChild.style[ionic.CSS.TRANSFORM] = 'translateY(' + val + 'px)';
|
|
lastOverscroll = val;
|
|
}
|
|
|
|
function nativescroll(target, newScrollTop) {
|
|
// creates a scroll event that bubbles, can be cancelled, and with its view
|
|
// and detail property initialized to window and 1, respectively
|
|
target.scrollTop = newScrollTop;
|
|
var e = document.createEvent("UIEvents");
|
|
e.initUIEvent("scroll", true, true, window, 1);
|
|
target.dispatchEvent(e);
|
|
}
|
|
|
|
function setScrollLock(enabled) {
|
|
// set the scrollbar to be position:fixed in preparation to overscroll
|
|
// or remove it so the app can be natively scrolled
|
|
if (enabled) {
|
|
ionic.requestAnimationFrame(function() {
|
|
scrollChild.classList.add('overscroll');
|
|
show();
|
|
});
|
|
|
|
} else {
|
|
ionic.requestAnimationFrame(function() {
|
|
scrollChild.classList.remove('overscroll');
|
|
hide();
|
|
deactivate();
|
|
});
|
|
}
|
|
}
|
|
|
|
$scope.$on('scroll.refreshComplete', function() {
|
|
// prevent the complete from firing before the scroll has started
|
|
$timeout(function() {
|
|
|
|
ionic.requestAnimationFrame(tail);
|
|
|
|
// scroll back to home during tail animation
|
|
scrollTo(0, scrollTime, deactivate);
|
|
|
|
// return to native scrolling after tail animation has time to finish
|
|
$timeout(function() {
|
|
|
|
if (isOverscrolling) {
|
|
isOverscrolling = false;
|
|
setScrollLock(false);
|
|
}
|
|
|
|
}, scrollTime);
|
|
|
|
}, scrollTime);
|
|
});
|
|
|
|
function scrollTo(Y, duration, callback) {
|
|
// scroll animation loop w/ easing
|
|
// credit https://gist.github.com/dezinezync/5487119
|
|
var start = Date.now(),
|
|
from = lastOverscroll;
|
|
|
|
if (from === Y) {
|
|
callback();
|
|
return; /* Prevent scrolling to the Y point if already there */
|
|
}
|
|
|
|
// decelerating to zero velocity
|
|
function easeOutCubic(t) {
|
|
return (--t) * t * t + 1;
|
|
}
|
|
|
|
// scroll loop
|
|
function scroll() {
|
|
var currentTime = Date.now(),
|
|
time = Math.min(1, ((currentTime - start) / duration)),
|
|
// where .5 would be 50% of time on a linear scale easedT gives a
|
|
// fraction based on the easing method
|
|
easedT = easeOutCubic(time);
|
|
|
|
overscroll(parseInt((easedT * (Y - from)) + from, 10));
|
|
|
|
if (time < 1) {
|
|
ionic.requestAnimationFrame(scroll);
|
|
|
|
} else {
|
|
|
|
if (Y < 5 && Y > -5) {
|
|
isOverscrolling = false;
|
|
setScrollLock(false);
|
|
}
|
|
|
|
callback && callback();
|
|
}
|
|
}
|
|
|
|
// start scroll loop
|
|
ionic.requestAnimationFrame(scroll);
|
|
}
|
|
|
|
|
|
self.init = function() {
|
|
scrollParent = $element.parent().parent()[0];
|
|
scrollChild = $element.parent()[0];
|
|
|
|
if (!scrollParent.classList.contains('ionic-scroll') ||
|
|
!scrollChild.classList.contains('scroll')) {
|
|
throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
|
|
}
|
|
|
|
ionic.on('touchmove', handleTouchmove, scrollChild);
|
|
ionic.on('touchend', handleTouchend, scrollChild);
|
|
ionic.on('scroll', handleScroll, scrollParent);
|
|
};
|
|
|
|
|
|
$scope.$on('$destroy', destroy);
|
|
|
|
function destroy() {
|
|
ionic.off('dragdown', handleTouchmove, scrollChild);
|
|
ionic.off('dragend', handleTouchend, scrollChild);
|
|
ionic.off('scroll', handleScroll, scrollParent);
|
|
scrollParent = null;
|
|
scrollChild = null;
|
|
}
|
|
|
|
// DOM manipulation and broadcast methods shared by JS and Native Scrolling
|
|
// getter used by JS Scrolling
|
|
self.getRefresherDomMethods = function() {
|
|
return {
|
|
activate: activate,
|
|
deactivate: deactivate,
|
|
start: start,
|
|
show: show,
|
|
hide: hide,
|
|
tail: tail
|
|
};
|
|
};
|
|
|
|
function activate() {
|
|
$element[0].classList.add('active');
|
|
$scope.$onPulling();
|
|
}
|
|
|
|
function deactivate() {
|
|
// give tail 150ms to finish
|
|
$timeout(function() {
|
|
// deactivateCallback
|
|
$element.removeClass('active refreshing refreshing-tail');
|
|
if (activated) activated = false;
|
|
}, 150);
|
|
}
|
|
|
|
function start() {
|
|
// startCallback
|
|
$element[0].classList.add('refreshing');
|
|
$scope.$onRefresh();
|
|
}
|
|
|
|
function show() {
|
|
// showCallback
|
|
$element[0].classList.remove('invisible');
|
|
}
|
|
|
|
function hide() {
|
|
// showCallback
|
|
$element[0].classList.add('invisible');
|
|
}
|
|
|
|
function tail() {
|
|
// tailCallback
|
|
$element[0].classList.add('refreshing-tail');
|
|
}
|
|
|
|
// for testing
|
|
self.__handleTouchmove = handleTouchmove;
|
|
self.__getScrollChild = function() { return scrollChild; };
|
|
self.__getScrollParent= function() { return scrollParent; };
|
|
}
|
|
]);
|