Monday, November 21, 2011

Scrolling on the iPad

Okay, this might be lame, but I decided I had a very specific scrolling requirement that none of the other options out there met.

I tried things like iScroll, but the refresh didn't always work (even after a timeout of several seconds) and I had to hack on another package to get the inputs to respond again.

I tried the new position:fixed options in IOS 5.x, but touch events seem to be broken if you have the page scrolled up. Click events work, but touch ones only work if you're scrolled back to the top!!

So, I dug through whatever I could find and decided to hack my own together. Maybe this will help you too.

Please note that I am no pro javascript developer; I know my way around Python pretty well, but I come by javascript by hook and by crook, as they might say.
Who they are I have no idea, but I'm just saying.

WHAT DOES IT DO?

This is ONLY a vertical scroller. I might adapt it later, but if all you want is a dynamically sizeable vertical scroller (with scrollbar!) with a fixed header (or footer) then this is for you.


Here's the javascript. Copy and paste this and save it as a file (ie. touch.js):



//
// IOS TOUCH EVENTS
//
function Scroller(element,container,maxheight) {
    if (document.getElementById(element))
        element =    document.getElementById(element)
    this.elementid    =    element.id
    this.containerid =    container
    this.maxheight = 0
    if (maxheight)
        this.maxheight = maxheight
    element.addEventListener('touchstart',this,false);
    element.addEventListener('touchmove',this,false);
    element.addEventListener('touchend',this,false);
    this.containerTop = parseInt(document.getElementById(container).style.top) || 0
    this.containerHeight = parseInt(document.getElementById(container).style.height) || 0
    this.ismove = 0
    this.t1 = 0
    this.t2 = 0
    this.starttime = 0
    this.elementHeight = 0
    this.lastY = 0
    this.lastYabsolute = 0
    this.startYabsolute = 0
    this.startY    =    0
    this.scrollTop = 0
    this.scrollHeight = 0
}
Scroller.prototype.handleEvent = function(e) {
    switch (e.type) {
        case 'touchstart': this.ScrollerOnTouchStart(e); break;
        case 'touchmove' : this.ScrollerOnTouchMove(e); break
        case 'touchend' : this.ScrollerOnTouchEnd(e); break
    }
}
Scroller.prototype.CreateScrollbar = function() {
    if (!document.getElementById('scrollbar')) {
        var newitem = document.createElement('div');
        newitem.setAttribute('id','scrollbar');
        document.getElementById(this.containerid).appendChild(newitem);
    }
    var obj = document.getElementById('scrollbar');
    obj.style.opacity = 0;
    obj.style['-webkit-transition-duration']    =    '0ms'
    var h = this.containerHeight/(this.elementHeight/this.containerHeight);
    obj.style.height=parseInt(h)+'px';
    var l = parseInt(document.getElementById(this.elementid).style.left);
    if (!l)
        var l = parseInt(window.getComputedStyle(document.getElementById(this.elementid)).left);
    if (!l)
        l = 0;
    l += parseInt(window.getComputedStyle(document.getElementById(this.elementid)).marginLeft);
    l += parseInt(window.getComputedStyle(document.getElementById(this.elementid)).paddingLeft);
    l += parseInt(window.getComputedStyle(document.getElementById(this.containerid)).marginLeft);
    l += parseInt(window.getComputedStyle(document.getElementById(this.containerid)).paddingLeft);
    l += parseInt(window.getComputedStyle(document.getElementById(this.elementid)).width)-5;
    if (!l)
        l = 900;
    obj.style.left = parseInt(l)+'px';
    this.scrollTop = parseInt(window.getComputedStyle(document.getElementById(this.elementid)).paddingTop)+parseInt(window.getComputedStyle(document.getElementById(this.elementid)).marginTop);
    this.scrollHeight = (parseInt(document.getElementById(this.containerid).style.height)-this.scrollTop)-parseInt(h);
    obj.style['-webkit-transition-duration']    =    '200ms'
    obj.style.opacity = 0.5;
}
Scroller.prototype.MoveScrollbar = function() {
    var t = (-this.lastY/this.elementHeight)
    x = parseInt((this.scrollHeight *t)+this.scrollTop)
    document.getElementById('scrollbar').style['-webkit-transition-duration']    =    '0ms'
    document.getElementById('scrollbar').style.webkitTransform = 'translate3d(0px,' + x + 'px,0px)';
}
Scroller.prototype.ScrollerOnTouchStart = function(e) {
    var d = new Date()
    this.ismove = 0
    this.starttime = d.getTime()
    document.getElementById(this.elementid).style['-webkit-transition-duration']    =    '0ms'
    this.startY = e.touches[0].pageY-this.lastY
    this.startYabsolute = e.touches[0].pageY
    this.elementHeight = parseInt(document.getElementById(this.elementid).style.height)-parseInt(document.getElementById(this.containerid).style.height)
    if (this.maxheight)
        this.elementHeight = this.maxheight
    //elementHeight = parseInt(document.getElementById(elementid).style.height)
    this.CreateScrollbar()
}
Scroller.prototype.ScrollerOnTouchMove = function(e) {
    this.ismove = 1
    var d = new Date()
    this.t2 = this.t1
    this.t1 = e.targetTouches[0].pageY
    this.lastY = e.targetTouches[0].pageY-this.startY
    this.lastYabsolute = e.targetTouches[0].pageY
    if (this.lastY >= (this.containerTop+100)) {
        this.lastY = this.containerTop+100
    }
    else if (this.lastY < -(this.elementHeight+100) && this.elementHeight>this.containerHeight) {
        this.lastY = -(this.elementHeight+100)
    }
    document.getElementById(this.elementid).style.webkitTransform = 'translate3d(0px,' + this.lastY + 'px,0px)';
    //console.log(this.lastY+'/'+this.elementHeight+'/'+this.containerHeight)
    this.MoveScrollbar()
}
Scroller.prototype.ScrollerOnTouchEnd = function(e) {
    // DO THE MOMENTUM
    if (this.ismove == 0) {
        var obj = document.getElementById('scrollbar')
        obj.style['-webkit-transition']    =    'all 200ms ease-out';
        obj.style.opacity = 0;
        return
    }
    var d = new Date()
    var endtime = d.getTime()
    var duration = (endtime-this.starttime)
    var distance = this.lastYabsolute - this.startYabsolute
    var b = 120*distance
    var distance = b/duration
    //console.log(this.t2+' / '+this.t1+ ' = '+(this.t2-this.t1))
    if (this.t2-this.t1 < 20 && this.t2-this.t1 > -20)
        distance    =    0

    if (duration > 600)
        distance = 0
    if (this.lastY > this.containerTop) {
        this.lastY = this.containerTop
        document.getElementById(this.elementid).style['-webkit-transition-duration']    =    '200ms'
        document.getElementById(this.elementid).style.webkitTransform = 'translate3d(0px,' + 0 + 'px,0px)';
        duration = 200
    }
    else if (this.lastY < -this.elementHeight) {
        this.lastY = -this.elementHeight
        var pos = this.elementHeight
        if (parseInt(document.getElementById(this.elementid).style.height) < this.containerHeight) {
            this.lastY = this.containerTop
            pos = 0
        }
        document.getElementById(this.elementid).style['-webkit-transition-duration']    =    '200ms'
        document.getElementById(this.elementid).style.webkitTransform = 'translate3d(0px,-' + pos + 'px,0px)';
        duration = 200
    }
    else {
        // USE MOMENTUM TO SLOW IT DOWN
        var t = this.t2-this.t1
        if (t < 0)
            t = -t
        t = t * 0.8
        distance = (distance * (t/20))*1.4
        duration = (duration * (t/20))*1.4
        this.lastY = this.lastY+distance
        if (this.lastY < -this.elementHeight)
            this.lastY = -this.elementHeight
        else if (this.lastY > this.containerTop)
            this.lastY = this.containerTop
        //document.getElementById(this.elementid).style['-webkit-transition-duration']    =    parseInt(duration)+'ms'
        document.getElementById(this.elementid).style['-webkit-transition']    =    'all '+parseInt(duration)+'ms cubic-bezier(0,0,0.1,1)'
        //document.getElementById(this.elementid).style['-webkit-transition-duration']    =    '400ms'
        document.getElementById(this.elementid).style.webkitTransform = 'translate3d(0px,' + this.lastY + 'px,0px)';

    }
    
    // MOVE THE SCROLLBAR
    var obj = document.getElementById('scrollbar')
    var t = (-this.lastY/this.elementHeight)
    x = (this.scrollHeight *t)+this.scrollTop
    obj.style['-webkit-transition']    =    'all '+parseInt(duration)+'ms ease-out';
    document.getElementById('scrollbar').style.webkitTransform = 'translate3d(0px,' + x + 'px,0px)';
    obj.style.opacity = 0;
}
Scroller.prototype.ScrollerReset = function() {
    //console.log('here')
    document.getElementById(this.elementid).style['-webkit-transition-duration']    =    '0ms'    
    document.getElementById(this.elementid).style.webkitTransform = 'translate3d(0px,0px,0px)';
    this.lastY = 0
}
Once you've got that, then you refer to it with something like this in the header:


<script src="touch.js" type="text/javascript"/>
In your body HTML, you might have some code like this:


<div id="contents" style="height: 400px; position: absolute; left: 0px; width: 100%; height: 400px;"></div>
    <div id="scrolleritems" style="position: absolute;">my html here</div>
</div>

The "contents" div is the parent div. It's what should have the dimensions you want. The "scrolleritems" is the window that will scroll.

Once you've got that, then you need to initialize it with this:


<script>myscroll = new Scroller('scrolleritems','contents')</script>

I like to put that bit of code at the end of the page, before the </body> tag, just because I seem to have an aversion to using the "onload" body event.

There's only one method for this routine, and that's "reset":

myscroll.ScrollerReset()

If you need to reset it for any reason, (say, an orientation change) that's what you do.

Now, this works in my environment; I haven't tested it as written here, so if you have problems just let me know and I'll get it fixed!

UPDATE:

I forgot to include the styling for the scrollbar! Somewhere in your css (or in a <style> tag) include this:


#scrollbar {
position: absolute;
border-radius: 3px;
width: 5px;
opacity 0.5;
border: 1px solid #000;
background-color: #000;
}

3 comments:

  1. hi , i have to manually do scrolling, i can scroll the page with two fingers bt not able to see the scrollbar.

    Any help?

    ReplyDelete
  2. You mean when using this scrolling program?

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete