1

I'm looking for somthing like this to use it in a JQuery Mobile/Phonegap App. Pictures in the gallery should be changeable by swiping left/right. All plugins I found so far are either not made for mobile or don't have this apple like coverflow style.

Dirk Siegmund
  • 131
  • 2
  • 11

1 Answers1

3

Don't know of any out-of-the-box plugins for this, but it's actually super simple to build from scratch with CSS3 & Javascript - You can even get hardware acceleration on iOS devices.

Basically, you want to do 3 things:

  1. Setup a basic touch/swipe event handler. Something like this is a good starting point (probably needs tweaking - the "getMovement" function is definitely not the best way to handle detecting swipe events. You'll also need to setup some defaults for your parameters, incase they are empty... Actually, you're probably better off replacing them with an "options" object, so you don't need to do that).
    It also implements webkitAnimationEnd & webkitTransitionEnd (note: these events have different names in Fennec, if you're planning to support that browser) listeners to detect when your css3 animations/transitions have finished so you can hookup callback events to run once your animations are done:

    function TouchHandler(elem, tStart, tEnd, sLFunc, sRFunc, aFunc, trFunc) {
        //Members
        this.element = elem;
        this.animations = 0;
        this.transitions = 0;
    
        //Callbacks
        this.touchStart = tStart;
        this.touched = tFunc;
        this.swipeLeft = sLFunc;
        this.swipeRight = sRFunc;
        this.transitioned = trFunc;
        this.animated = aFunc;
    
        //Event Listners
        if (window.Touch) this.element.addEventListener('touchstart', this, false);
        this.element.addEventListener('webkitAnimationEnd', this, false);
        this.element.addEventListener('webkitTransitionEnd', this, false);
    }
    
    //Methods
    TouchHandler.prototype = {
        handleEvent: function (e) {
            switch (e.type) {
                case 'touchstart': this.onTouchStart(e); break;
                case 'touchmove': this.onTouchMove(e); break;
                case 'touchend': this.onTouchEnd(e); break;
                case 'webkitAnimationEnd': this.onAnimEnd(e); break;
                case 'webkitTransitionEnd': this.onTransEnd(e); break;
            }
        },
        onTouchStart: function (e) {
            e.preventDefault();
            this.touchStart();
            this.moved = false;
            this.startX = event.touches[0].pageX;
            this.startY = event.touches[0].pageY;
            this.element.className += ' touched';
            console.log('touched! ' + this.element.className);
            this.element.addEventListener('touchmove', this, false);
            this.element.addEventListener('touchend', this, false);
        },
        onTouchMove: function (e) {
            if (this.animations == 0) {
                this.moved = true;
                this.changeInX = event.touches[0].pageX;
                this.changeInY = event.touches[0].pageY;
            }
        },
        onTouchEnd: function (e) {
            this.element.removeEventListener('touchmove', this, false);
            this.element.removeEventListener('touchend', this, false);
            if (this.element.className.search('touched') >= 0) { this.element.className = this.element.className.replace(' touched', ''); }
            console.log('untouched! ' + this.element.className);
            if (this.animations == 0 && this.transitions == 0) {
                if (!this.moved) {
                    this.touched();
                } else {
                    if (this.getMovement() == 'imprecise') {
                        this.touched();
                    }
                    if (this.getMovement() == 'swipeLeft') {
                        this.swipeLeft();
                    }
                    if (this.getMovement() == 'swipeRight') {
                        this.swipeRight();
                    }
                }
            }
        },
        onAnimEnd: function (e) {
            if (this.animations > 0) {
                this.animations -= 1;
                if (this.animations == 0) {
                    this.animated();
                }
            }
        },
        onTransEnd: function (e) {
            if (this.transitions > 0) {
                this.transitions -= 1;
                if (this.transitions == 0) {
                    this.transitioned();
                }
            }
        },
        getMovement: function () {
            var diffY = this.startY - this.changeInY;
            var diffX = this.startX - this.changeInX;
            var impreciseTouch  = (diffX < 30 || diffX > -30) && (diffY > -30 || diffY < 30);
            var swipeLeft       = diffX > 60 && (diffY > -30 || diffY < 30);
            var swipeRight      = diffX < -60 && (diffY > -30 || diffY < 30);
    
            if (impreciseTouch) { return 'imprecise'; }
            else if (swipeLeft) { return 'swipeLeft'; }
            else if (swipeRight){ return 'swipeRight'; }
        }
    };
    
  2. In your CSS, setup your image elements with position absolute so that they don't affect other page elements when manipulating them (important for performance, as we're going to try to avoid triggering page reflows as much as possible). Also be sure to set -webkit-transform-style (and -moz-transform-style, if you'd like to support Fennec) to preserve-3d - this will allow your inner layers to preserve their place in 3space, instead of being artificially flattened by the browser. We're going to be using css3 translate3d & rotateY to move & rotate the image elements when a user swipes. By using 3dTransforms, we're also activating hardware-acceleration in iOS browsers. Now, setup a css3 transition for each visible position a photo can be in. Based on your image, you have 5 positions. Lets call them prev2, prev1, current, next1, next2. Each photo should also have a class .photo, which we will use to setup the transition properties Ex:

    .photo-container {-webkit-transform-style:preserve-3d;}
    .prev2   {-webkit-transform:  translate3d(0px,0,0)   rotateY(-45deg);}
    .prev1   {-webkit-transform:  translate3d(200px,0,0) rotateY(-30deg);}
    .current {-webkit-transform:  translate3d(400px,0,0) rotateY(-45deg);}
    .next1   {-webkit-transform:  translate3d(600px,0,0) rotateY(-30deg);}
    .next2   {-webkit-transform:  translate3d(800px,0,0) rotateY(-45deg);}
    
    .photo{ 
        -webkit-transition: -webkit-transform 500ms ease-in-out; 
    }
    
  3. Instantiate a touchHandler object & bind it to whatever DOM element you want to have listen for your touch events. It has to be a parent of the elements that are transitioning in order for the animation events to bubble up. You can also instantiate each individual photo, but depending on your setup the results will vary. You might want to test both setups. Basically, when a swipeLeft fires, you want to remove the .current class from the current photo & add a .next class (using javascript/jquery) - write this in a function and pass it into the parameters of the touchHandler when you instantiate. This can be unorganized, so it would probably be better to extend the class (or write another class that inherits from it) to programatically handle your photo transitions. Ideally, you would store the DOM elements representing your photos in an array & manipulate the elements using this.array[i], iteratively...

Hope this all makes sense to you...

1nfiniti
  • 2,032
  • 14
  • 19
  • P.S. -- this solution will work on iOS & Android, although Hardware acceleration will only be triggered on iOS devices AFAIK. – 1nfiniti Feb 01 '12 at 16:08
  • I'd also suggest not using jQueryMobile. It's bloated, it's touch events aren't very responsive & it's performance can become simple awful when your app reaches a more than minimal size. – 1nfiniti Feb 01 '12 at 16:31
  • 1
    @ mikeyUX: Android 3.0 and on-wards, supports hardwareAcceleration, you need to configure it in Manifest file. – Nova Mar 18 '12 at 15:26