Skip to content

Instantly share code, notes, and snippets.

@kerrishotts
Created June 8, 2012 22:30
Show Gist options
  • Save kerrishotts/2898456 to your computer and use it in GitHub Desktop.
Save kerrishotts/2898456 to your computer and use it in GitHub Desktop.
This library is intended to be similar to iScroll-lite in that it should be a relatively fast method of scrolling content without being horribly laggy or cause incorrect "clicks" to be registered.
/******************************************************************************
*
* SCROLLER
* Author: Kerri Shotts
* Version: 0.1 alpha
* License: MIT
*
* This library is intended to be similar to iScroll-lite in that it should be
* a relatively fast method of scrolling content without being horribly laggy
* or cause incorrect "clicks" to be registered.
*
* This library does *NOT* support physics-based scrolling, except for a small
* inertia animation at the end of a scroll. It is not intended to replicate
* native scrolling /at all/. There are no bounces at the top or bottom. There
* is no visible scroll bar either. Essentially, overflow:scroll as supported
* on iOS 5 with no bounce/inertia scrolling.
*
* Consider this library an experiment. The idea is to be simpler than iScroll
* to use -- for example, the scroller only needs to be created once -- it does
* not need to be refreshed when AJAX content loads. It is intended to be at
* least as fast as iScroll, if not a little faster. It is not, however,
* intended to be a native scrolling solution. At this time, I do not believe
* it truly possible or practice, and users will notice any non-native solution
* that tries to match, so why try?
*
* Usage:
*
* var yourScroller = new SCROLLER.GenericScroller ( "the_element_to_scroll" );
*
* where you have the following DOM tree:
*
* container_element
* - the_element_to_scroll
*
* Future Goals:
*
* - Detect native physics scrolling and use it when possible
* - Detect native overflow:scroll (non-physics) and use it when possible
* - Improve the inertial scrolling at end (this is a very rough implementation)
* - Become irrelevant. I hope for a day when all mobile browsers can scroll
* complex content natively and smoothly.
*
* Supported Platforms:
*
* - Android 2.3+
* - iOS 4.3+
* - probably any webkit browser?
*
* Known Issues:
* - A little too willing to call a scroll a "click".
*
******************************************************************************/
var SCROLLER = SCROLLER || {}; // create the namespace
SCROLLER.GenericScroller = function ( element )
{
var self = this;
// the element that should scroll
self.theElement = {};
// various touch-related coords.
self._touchX = -1;
self._touchY = -1;
self._touchStartX = 0;
self._touchStartY = 0;
self._actualX = 0;
self._actualY = 0;
// delta changes; totalDelta should be total change from start to end.
self._deltaX = 0;
self._deltaY = 0;
self._totalDeltaX = 0;
self._totalDeltaY = 0;
// inertial animation
self._timer = -1;
self._step = 0;
/**
*
* Attaches the scroller to a new element. The element MUST be contained within
* another element that can support the setting of scrollTop/Left.
*
* Any overflow values will be overridden. Any webkit transforms may also be
* overridden.
*/
self.attachToElement = function ( element)
{
// get our element
self.theElement = document.getElementById(element);
// attach our listeners
self.theElement.addEventListener ( "touchstart", self.touchStart, false );
self.theElement.addEventListener ( "touchmove", self.touchMove, false );
self.theElement.addEventListener ( "touchend", self.touchEnd, false );
// turn overflow to hidden; if it is auto or scroll, the native scrolling
// might kick in (if supported) and confuse us. IF YOU WANT NATIVE SCROLLING,
// DON'T USE THIS SCROLLER.
self.theElement.parentNode.style.overflow = "hidden";
// A quandary. on iOS, it is faster to have translate3d(0,0,0) enabled,
// but you have more visual glitches. Without it, it is a little slower,
// but with no visual glitches. For Android, we leave it on, just in case
// it can be used.
if (device)
{
if (device.platform == "Android")
{
self.theElement.style.webkitTransform = "translate3d(0,0,0)";
}
}
else
{
// if we can't detect the device, we'll always use it.
self.theElement.style.webkitTransform = "translate3d(0,0,0)";
}
console.log ("Scroller initiated for element " + element);
}
/**
*
* Get the scroll position
*
*/
self.getScrollTop = function ()
{
return self.theElement.parentNode.scrollTop;
}
self.getScrollLeft = function ()
{
return self.theElement.parentNode.scrollLeft;
}
/**
*
* Scroll to a given location. If the location can't be scrolled to,
* the nearest location will be used.
*
*/
self.scrollTo = function (left, top)
{
self.theElement.parentNode.scrollTop = top;
self.theElement.parentNode.scrollLeft = left;
self._actualX = -left;
self._actualY = -top;
}
/**
*
* touchStart initializes all our values when a touch is received.
*
*/
self.touchStart = function (event)
{
// if an inertia animation is underway, clear it.
if (self._timer!=-1) { clearInterval (self._timer); }
self._timer = -1;
// record our touches.
self._touchX = event.touches[0].screenX;
self._touchY = event.touches[0].screenY;
self._touchStartX = self._touchX;
self._touchStartY = self._touchY;
// zero the deltas
self._deltaX = 0;
self._deltaY = 0;
self._totalDeltaX = 0;
self._totalDeltaY = 0;
// get our actual scroll position
self._actualX = -self.theElement.parentNode.scrollLeft;
self._actualY = -self.theElement.parentNode.scrollTop;
}
/**
*
* When a touch moves, we'll receive the event here.
*
*/
self.touchMove = function (event)
{
// calculate the delta between our last and current
// position
self._deltaX = self._touchX - event.touches[0].screenX;
self._deltaY = self._touchY - event.touches[0].screenY;
// update totalDelta
self._totalDeltaX -= self._deltaX;
self._totalDeltaY -= self._deltaY;
// update our actual scroll position
self._actualX -= self._deltaX;
self._actualY -= self._deltaY;
// store our current screen position
self._touchX = event.touches[0].screenX;
self._touchY = event.touches[0].screenY;
// scroll to the new position
self.theElement.parentNode.scrollTop = -self._actualY;
self.theElement.parentNode.scrollLeft = -self._actualX;
// if there is any movement, prevent the default.
if ( Math.sqrt((self._totalDeltaX * self._totalDeltaX) + (self._totalDeltaY * self._totalDeltaY)) > 0 )
{
event.preventDefault ();
}
}
/**
*
* When a finger is lifted from the screen, we'll get this event.
* We can determine whether or not it was a click if we scrolled at all,
* and if we scrolled a certain distance, we'll do a little inertial
* scrolling
*/
self.touchEnd = function (event)
{
// were we just a click? if so, this'll be zero -- otherwise prevent the default.
if ( Math.sqrt((self._totalDeltaX * self._totalDeltaX) + (self._totalDeltaY * self._totalDeltaY)) > 0 )
{
event.preventDefault ();
}
// how far did we scroll just prior to getting the end event? Is it far enough to have
// some inertia?
if ( Math.sqrt((self._deltaX * self._deltaX) + (self._deltaY * self._deltaY)) > 10 )
{
// yes, set up our animation
self._step = 0;
self._timer = setInterval ( function ()
{
// increment the frame
self._step = self._step + 1;
if (self._step<10)
{
// we'll permit 10 frames of animation
self._actualX -= self._deltaX/(self._step);
self._actualY -= self._deltaY/(self._step);
self.theElement.parentNode.scrollTop = -self._actualY;
self.theElement.parentNode.scrollLeft = -self._actualX;
}
else
{
// animation complete
clearInterval (self._timer);
self._timer = -1;
}
}, 10 ); // 10 = as fast as possible
}
}
// attach to the element passed in the constructor.
self.attachToElement ( element );
}
@kerrishotts
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment