1. #31
    Sencha User armode's Avatar
    Join Date
    Nov 2011
    Location
    Germany / Darmstadt
    Posts
    64
    Vote Rating
    4
    armode is on a distinguished road

      0  

    Default


    Has anyone port the plugin to ST2?

    I tried it by myself but was not successful so far...

  2. #32
    Sencha User armode's Avatar
    Join Date
    Nov 2011
    Location
    Germany / Darmstadt
    Posts
    64
    Vote Rating
    4
    armode is on a distinguished road

      0  

    Default


    Ok, I want to share my port-state to ST2!
    I tried standard stuff like using the Ext.define for class defnition, etc
    There's maybe a problem with the initialize function that was the initComponent in ST1...
    I get no more errors or hints, but the componet still doesn't work!
    ImageViewer.js:
    Code:
    Ext.ns('Jarvus.mobile');
    
    Ext.define('Jarvus.mobile.ImageViewer',{
        
        extend: 'Ext.Container',
        config: {
            doubleTapScale: 1
            ,maxScale: 1
            ,loadingMask: true
            ,previewSrc: false
            ,imageSrc: false
            ,initOnActivate: false
            
            ,cls: 'imageBox'
            ,scroll: 'both'            
            ,html: '<figure><img></figure>'
        }
        ,xtype: 'imageviewer'
        ,initialize: function() {
            /*
            * TypeError: 'undefined' is not an object (evaluating 'Jarvus.mobile.ImageViewer.superclass.initComponent.apply')
            */
            // Jarvus.mobile.ImageViewer.superclass.initComponent.apply(this, arguments);        
            if(this.initOnActivate)
                this.on('activate', this.initViewer, this, {delay: 10, single: true});
            else
                this.on('afterrender', this.initViewer, this, {delay: 10, single: true});    
        }
            
        ,initViewer: function() {
            
            //    disable scroller
            this.scroller.disable();
            
            // mask image viewer
            if(this.loadingMask)
                this.el.mask(Ext.LoadingSpinner);
            
            // retrieve DOM els
            this.figEl = this.el.down('figure');
            this.imgEl = this.figEl.down('img');
            
            // apply required styles
            this.figEl.setStyle({
                overflow: 'hidden'
                ,display: 'block'
                ,margin: 0
            });
            
            this.imgEl.setStyle({
                '-webkit-user-drag': 'none'
                ,'-webkit-transform-origin': '0 0'
                ,'visibility': 'hidden'
            });
            
            // show preview
            if(this.previewSrc)
            {
                this.el.setStyle({
                    backgroundImage: 'url('+this.previewSrc+')'
                    ,backgroundPosition: 'center center'
                    ,backgroundRepeat: 'no-repeat'
                    ,webkitBackgroundSize: 'contain'
                });
            }
            
            // attach event listeners
            this.mon(this.imgEl, {
                scope: this
                ,load: this.onImageLoad
                ,doubletap: this.onDoubleTap
                ,pinchstart: this.onImagePinchStart
                ,pinch: this.onImagePinch
                ,pinchend: this.onImagePinchEnd
            });
            
            // load image
            if(this.imageSrc)
                this.loadImage(this.imageSrc);
            
        }
        
        ,loadImage: function(src) {    
            if(this.imgEl)
                this.imgEl.dom.src = src;
            else
                this.imageSrc = src;
            
        }
        
        
        ,onImageLoad: function() {
            // get viewport size
            this.viewportWidth = this.viewportWidth || this.getWidth() || this.ownerCt.body.getWidth();
            this.viewportHeight = this.viewportHeight || this.getHeight() || this.ownerCt.body.getHeight();
            
            // grab image size
            this.imgWidth = this.imgEl.dom.width
                this.imgHeight = this.imgEl.dom.height;
            
            // calculate and apply initial scale to fit image to screen
            if(this.imgWidth > this.viewportWidth || this.imgHeight > this.viewportHeight)
                this.scale = this.baseScale = Math.min(this.viewportWidth/this.imgWidth, this.viewportHeight/this.imgHeight);
            else
                this.scale = this.baseScale = 1;
            
            // set initial translation to center
            this.translateX = this.translateBaseX = (this.viewportWidth - this.baseScale * this.imgWidth) / 2;
            this.translateY = this.translateBaseY = (this.viewportHeight - this.baseScale * this.imgHeight) / 2;
            
            // apply initial scale and translation
            this.applyTransform();
            
            // initialize scroller configuration
            this.adjustScroller();
            
            // show image and remove mask
            this.imgEl.setStyle({ visibility: 'visible' });
            
            // remove preview
            if(this.previewSrc)
            {
                this.el.setStyle({
                    backgroundImage: 'none'
                });
            }
            
            if(this.loadingMask)
                this.el.unmask();
            
            this.fireEvent('imageLoaded', this);
        }
        
        ,onImagePinchStart: function(ev) {
            // disable scrolling during pinch
            this.scroller.stopMomentumAnimation();
            this.scroller.disable();
            
            // store beginning scale
            this.startScale = this.scale;
            
            // calculate touch midpoint relative to image viewport
            this.originViewportX = (ev.touches[0].clientX + ev.touches[1].clientX) / 2 - this.el.getX();
            this.originViewportY = (ev.touches[0].clientY + ev.touches[1].clientY) / 2 - this.el.getY();
            
            // translate viewport origin to position on scaled image
            this.originScaledImgX = this.originViewportX - this.scroller.offset.x - this.translateX;
            this.originScaledImgY = this.originViewportY - this.scroller.offset.y - this.translateY;
            
            // unscale to find origin on full size image
            this.originFullImgX = this.originScaledImgX / this.scale;
            this.originFullImgY = this.originScaledImgY / this.scale;
            
            // calculate translation needed to counteract new origin and keep image in same position on screen
            this.translateX += (-1 * ((this.imgWidth*(1-this.scale)) * (this.originFullImgX/this.imgWidth)));
            this.translateY += (-1 * ((this.imgHeight*(1-this.scale)) * (this.originFullImgY/this.imgHeight)))
                
                // apply new origin
                this.setOrigin(this.originFullImgX, this.originFullImgY);
            
            // apply translate and scale CSS
            this.applyTransform();
        }
        
        ,onImagePinch: function(ev) {
            // prevent scaling to smaller than screen size
            this.scale = Ext.util.Numbers.constrain(ev.scale * this.startScale, this.baseScale, this.maxScale);
            this.applyTransform();
        }
        
        ,onImagePinchEnd: function(ev) {
            
            // set new translation
            if(this.scale == this.baseScale)
            {
                // move to center
                this.setTranslation(this.translateBaseX, this.translateBaseY);
            }
            else
            {
                // calculate rescaled origin
                this.originReScaledImgX = this.originScaledImgX * (this.scale / this.startScale);
                this.originReScaledImgY = this.originScaledImgY * (this.scale / this.startScale);
                
                // maintain zoom position
                this.setTranslation(this.originViewportX - this.originReScaledImgX, this.originViewportY - this.originReScaledImgY);            
            }
            // reset origin and update transform with new translation
            this.setOrigin(0, 0);
            this.applyTransform();
            
            // adjust scroll container
            this.adjustScroller();
        }
        
        
        ,onDoubleTap: function(ev, t) {
            
            if(!this.doubleTapScale)
                return false;
            
            // set scale and translation
            if(this.scale >= .9)
            {
                // zoom out to base view
                this.scale = this.baseScale;
                this.setTranslation(this.translateBaseX, this.translateBaseY);
            }
            else
            {
                // zoom in toward tap position
                var oldScale = this.scale
                    ,newScale = 1
                    ,originViewportX = ev ? (ev.pageX - this.el.getX()) : 0
                        ,originViewportY = ev ? (ev.pageY - this.el.getY()) : 0
                            ,originScaledImgX = originViewportX - this.scroller.offset.x - this.translateX
                                ,originScaledImgY = originViewportY - this.scroller.offset.y - this.translateY
                                    ,originReScaledImgX = originScaledImgX * (newScale / oldScale)
                                        ,originReScaledImgY = originScaledImgY * (newScale / oldScale);
                
                this.scale = newScale;
                this.setTranslation(originViewportX - originReScaledImgX, originViewportY - originReScaledImgY);
            }
            
            // reset origin and update transform with new translation
            this.applyTransform();
            
            // adjust scroll container
            this.adjustScroller();
            
            // force repaint to solve occasional iOS rendering delay
            Ext.repaint();
        }
        
        ,setOrigin: function(x, y) {
            this.imgEl.dom.style.webkitTransformOrigin = x+'px '+y+'px';
        }
        
        ,setTranslation:  function(translateX, translateY) {
            this.translateX = translateX;
            this.translateY = translateY;
            
            // transfer negative translations to scroll offset
            this.scrollX = this.scrollY = 0;
            
            if(this.translateX < 0)
            {
                this.scrollX = this.translateX;
                this.translateX = 0;
            }
            if(this.translateY < 0)
            {
                this.scrollY = this.translateY;
                this.translateY = 0;
            }
        }
        
        
        ,applyTransform: function() {
            
            var fixedX = Ext.util.Numbers.toFixed(this.translateX,5)
                ,fixedY = Ext.util.Numbers.toFixed(this.translateY,5)
                ,fixedScale = Ext.util.Numbers.toFixed(this.scale, 8);
            
            if(Ext.is.Android)
            {
                this.imgEl.dom.style.webkitTransform = 
                    //'translate('+fixedX+'px, '+fixedY+'px)'
                    //+' scale('+fixedScale+','+fixedScale+')';
                    'matrix('+fixedScale+',0,0,'+fixedScale+','+fixedX+','+fixedY+')'
                    }
            else
            {
                this.imgEl.dom.style.webkitTransform =
                    'translate3d('+fixedX+'px, '+fixedY+'px, 0)'
                    +' scale3d('+fixedScale+','+fixedScale+',1)';
            }
            
        }
        
        
        ,adjustScroller: function() {
            
            // disable scrolling if zoomed out completely, else enable it
            if(this.scale == this.baseScale)
                this.scroller.disable();
            else
                this.scroller.enable();
            
            // size container to final image size
            var boundWidth = Math.max(this.imgWidth * this.scale, this.viewportWidth);
            var boundHeight = Math.max(this.imgHeight * this.scale, this.viewportHeight);
            
            this.figEl.setStyle({
                width: boundWidth + 'px'
                ,height: boundHeight + 'px'
            });
            
            // update scroller to new content size
            this.scroller.updateBoundary();
            
            // apply scroll
            this.scroller.setOffset({
                x: this.scrollX || 0
                ,y: this.scrollY || 0
            });
        }        
    });


    example-image.html:

    Code:
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>ImageViewer Example</title>
            
            <link rel="stylesheet" type="text/css" href="http://docs.sencha.com/touch/2-0/touch/resources/css/sencha-touch.css">
            <script type="text/javascript" src="http://docs.sencha.com/touch/2-0/touch/sencha-touch-all-debug.js"></script>
    
            <script type="text/javascript" src="ImageViewer.js"></script>
            
            <script type="text/javascript">
                Ext.application({
                
                    name: 'ImageExample'
                    
                    ,launch: function() {
                    
                        Ext.define('WPMobile.Viewport', {
                            extend: 'Ext.Container',
                            xtype: 'my-viewport',
                            config: {
                                fullscreen: true,
                                layout: 'fit',
                                items: [
                                 {
                                    xtype: 'imageviewer',
                                    style: {
                                        backgroundColor: '#000'
                                    },
                                    imageSrc: 'http://placekitten.com/2048/1024'
    
                            /*
                                if you already have a low resolution thumbnail loaded,
                                supply its URL via previewSrc to provide a backdrop
                                for loading the full-res version
                            */
                                    //,previewSrc: 'http://placekitten.com/20/20'
                                    }
                                ]
                                },
    
                        });
    
    
                        Ext.create('WPMobile.Viewport', {
                            id: 'viewport'
                        });              
                                         
                    }
                });
            </script>
            
            
        </head>
    
        <body></body>
    </html>‚Äč
    any help is appreciated!

    P.S:
    there's another thread about pinching images with ST2, but there's no smoothness in pinching, etc
    http://www.sencha.com/forum/showthre...h-2.0&p=733212

  3. #33
    Sencha User
    Join Date
    Nov 2010
    Posts
    388
    Vote Rating
    6
    gkatz is on a distinguished road

      0  

    Default


    @themightlychris:
    I think I have noticed (by using your great plugin) that the images can be zoomed in until the original size of the image is reached... am I correct?
    It would be great to be able so scale it 'endlleslly'
    thanks

  4. #34
    Sencha User armode's Avatar
    Join Date
    Nov 2011
    Location
    Germany / Darmstadt
    Posts
    64
    Vote Rating
    4
    armode is on a distinguished road

      0  

    Default 99% progress on porting ImageViewer to ST2

    99% progress on porting ImageViewer to ST2


    Hello everyone,

    I just wanted to share my progress in porting the ImageViewer.js to ST2.
    Overall it works fine, images are displayes, doubleclick works fine, just one hopefully last issue on pinching more then one times...

    I had to fire the load event by myself or was not able to listen to the right point, anyway it works ;-)

    Something seems to changed with the scroller coordinates!? To get the doubleclick zoomIn working, I had to convert the scroller coordinates from negative to positive numbers (see adjustScroller)

    Maybe someone could have a look at the pinching math? If you pinch to zoom the first time everthing is fine, but the second pinch moves the image und resizes it the wrong way...

    Here's the ImageViwer.js code
    Code:
        Ext.define('Jarvus.mobile.ImageViewer',{
            
        extend: 'Ext.Container',
        config: {
                doubleTapScale: 1
                ,maxScale: 1
                ,loadingMask: true
                ,previewSrc: false
                ,imageSrc: false
                ,initOnActivate: false
                ,cls: 'imageBox'
                ,scrollable: 'both'            
                ,html: '<figure><img></figure>'
        }
        ,xtype: 'imageviewer'
        ,initialize: function() {
            if(this.initOnActivate)
                this.addListener('activate', this.initViewer, this, {delay: 10, single: true});
            else
                this.addListener('painted', this.initViewer, this, {delay: 10, single: true});        
        }
        
        ,initViewer: function() {
            
            //    disable scroller
            var scroller = this.getScrollable().getScroller();
            scroller.setDisabled(true);
            
            // mask image viewer
            if(this.config.__proto__.loadingMask)
                this.setMasked({
                       xtype: 'loadmask',
                });
    
            // retrieve DOM els
            this.figEl = this.element.down('figure');
            this.imgEl = this.figEl.down('img');
    
            // apply required styles
            this.figEl.setStyle({
                overflow: 'hidden'
                ,display: 'block'
                ,margin: 0
            });
    
            this.imgEl.setStyle({
                '-webkit-user-drag': 'none'
                ,'-webkit-transform-origin': '0 0'
                ,'visibility': 'hidden'
            });
    
            // show preview
            if(this._previewSrc)
            {
                this.element.setStyle({
                    backgroundImage: 'url('+this.previewSrc+')'
                    ,backgroundPosition: 'center center'
                    ,backgroundRepeat: 'no-repeat'
                    ,webkitBackgroundSize: 'contain'
                });
            }
    
            // attach event listeners
            this.on('load', this.onImageLoad, this);
            this.imgEl.addListener({
                scope: this
                ,doubletap: this.onDoubleTap
                ,pinchstart: this.onImagePinchStart
                ,pinch: this.onImagePinch
                ,pinchend: this.onImagePinchEnd
            });    
    
            // load image
            if(this._imageSrc)
                this.loadImage(this._imageSrc);
        }
        
        ,loadImage: function(src) {    
            if(this.imgEl){
                this.imgEl.dom.src = src;
                this.imgEl.dom.onload = Ext.Function.bind(this.onLoad, this, this.imgEl, 0);
            }
            else
                this.imageSrc = src;
        }
    
        ,onLoad : function(el, e) {
            this.fireEvent('load', this, el, e);
        }
        
        ,onImageLoad: function() {
            // get viewport size
            this.viewportWidth = this.viewportWidth || this.getWidth() || this.parent.element.getWidth();
            this.viewportHeight = this.viewportHeight || this.getHeight() || this.parent.element.getHeight();
                
            // grab image size
            this.imgWidth = this.imgEl.dom.width
            this.imgHeight = this.imgEl.dom.height;
                    
            // calculate and apply initial scale to fit image to screen
            if(this.imgWidth > this.viewportWidth || this.imgHeight > this.viewportHeight)
                this.scale = this.baseScale = Math.min(this.viewportWidth/this.imgWidth, this.viewportHeight/this.imgHeight);
            else
                this.scale = this.baseScale = 1;
            
            // set initial translation to center
            this.translateX = this.translateBaseX = (this.viewportWidth - this.baseScale * this.imgWidth) / 2;
            this.translateY = this.translateBaseY = (this.viewportHeight - this.baseScale * this.imgHeight) / 2;
            
            // apply initial scale and translation
            this.applyTransform();
            
            // initialize scroller configuration
            this.adjustScroller();
    
            // show image and remove mask
            this.imgEl.setStyle({ visibility: 'visible' });
    
            // remove preview
            if(this._previewSrc)
            {
                this.element.setStyle({
                    backgroundImage: 'none'
                });
            }
    
            if(this.config.__proto__.loadingMask)
                this.setMasked(false);
    
            this.fireEvent('imageLoaded', this);
        }
        
        ,onImagePinchStart: function(ev) {
            //TODO Pinching math after pinching the first time; strange resizing and moving...
            var scroller = this.getScrollable().getScroller();
    
            // disable scrolling during pinch
            // TODO ? reactivate: scroller.stopMomentumAnimation();
            scroller.setDisabled(true);
            
            // store beginning scale
            this.startScale = this.scale;
            
            // calculate touch midpoint relative to image viewport
            this.originViewportX = (ev.touches[0].pageX + ev.touches[1].pageX) / 2 - this.element.getX();
            this.originViewportY = (ev.touches[0].pageY + ev.touches[1].pageY) / 2 - this.element.getY();
    
            // translate viewport origin to position on scaled image
            this.originScaledImgX = this.originViewportX - scroller.position.x - this.translateX;
            this.originScaledImgY = this.originViewportY - scroller.position.y - this.translateY;
            
            // unscale to find origin on full size image
            this.originFullImgX = this.originScaledImgX / this.scale;
            this.originFullImgY = this.originScaledImgY / this.scale;
            
            // calculate translation needed to counteract new origin and keep image in same position on screen
            this.translateX += (-1 * ((this.imgWidth*(1-this.scale)) * (this.originFullImgX/this.imgWidth)));
            this.translateY += (-1 * ((this.imgHeight*(1-this.scale)) * (this.originFullImgY/this.imgHeight)))
        
            // apply new origin
            this.setOrigin(this.originFullImgX, this.originFullImgY);
        
            // apply translate and scale CSS
            this.applyTransform();
        }
        
        ,onImagePinch: function(ev) {
            // prevent scaling to smaller than screen size
            this.scale = Ext.Number.constrain(ev.scale * this.startScale, this.baseScale, this.maxScale);
            this.applyTransform();
        }
        
        ,onImagePinchEnd: function(ev) {
            
            // set new translation
            if(this.scale == this.baseScale)
            {
                // move to center
                this.setTranslation(this.translateBaseX, this.translateBaseY);
            }
            else
            {
                // calculate rescaled origin
                this.originReScaledImgX = this.originScaledImgX * (this.scale / this.startScale);
                this.originReScaledImgY = this.originScaledImgY * (this.scale / this.startScale);
                
                // maintain zoom position
                this.setTranslation(this.originViewportX - this.originReScaledImgX, this.originViewportY - this.originReScaledImgY);            
            }
            // reset origin and update transform with new translation
            this.setOrigin(0, 0);
            this.applyTransform();
    
            // adjust scroll container
            this.adjustScroller();
        }
    
        
        ,onDoubleTap: function(ev, t) {
            
            var scroller = this.getScrollable().getScroller();
            if(!this.config.__proto__.doubleTapScale)
                return false;
            
            // set scale and translation
            if(this.scale >= .9)
            {
                // zoom out to base view
                this.scale = this.baseScale;
                this.setTranslation(this.translateBaseX, this.translateBaseY);
            }
            else
            {
                // zoom in toward tap position
                var oldScale = this.scale
                    ,newScale = 1
                    ,originViewportX = ev ? (ev.pageX - this.element.getX()) : 0
                    ,originViewportY = ev ? (ev.pageY - this.element.getY()) : 0
                    ,originScaledImgX = originViewportX - scroller.position.x - this.translateX
                    ,originScaledImgY = originViewportY - scroller.position.y - this.translateY
                    ,originReScaledImgX = originScaledImgX * (newScale / oldScale)
                    ,originReScaledImgY = originScaledImgY * (newScale / oldScale);
                    
                this.scale = newScale;
                this.setTranslation(originViewportX - originReScaledImgX, originViewportY - originReScaledImgY);
            }
                
            // reset origin and update transform with new translation
            this.applyTransform();
    
            // adjust scroll container
            this.adjustScroller();
            
            // force repaint to solve occasional iOS rendering delay
            Ext.repaint();
        }
        
        ,setOrigin: function(x, y) {
            this.imgEl.dom.style.webkitTransformOrigin = x+'px '+y+'px';
        }
        
        ,setTranslation:  function(translateX, translateY) {
            this.translateX = translateX;
            this.translateY = translateY;
                
            // transfer negative translations to scroll offset
            this.scrollX = this.scrollY = 0;
            
            if(this.translateX < 0)
            {
                this.scrollX = this.translateX;
                this.translateX = 0;
            }
            if(this.translateY < 0)
            {
                this.scrollY = this.translateY;
                this.translateY = 0;
            }
        }
            
    
        ,applyTransform: function() {
            var fixedX = Ext.Number.toFixed(this.translateX,5)
                ,fixedY = Ext.Number.toFixed(this.translateY,5)
                ,fixedScale = Ext.Number.toFixed(this.scale, 8);
            console.log('Transform X: ' + fixedX + ' Y: ' + fixedY + ' Scale: ' + fixedScale);
            if(Ext.os.is.Android)
            {
                this.imgEl.dom.style.webkitTransform = 
                    //'translate('+fixedX+'px, '+fixedY+'px)'
                    //+' scale('+fixedScale+','+fixedScale+')';
                    'matrix('+fixedScale+',0,0,'+fixedScale+','+fixedX+','+fixedY+')'
            }
            else
            {
                this.imgEl.dom.style.webkitTransform =
                    'translate3d('+fixedX+'px, '+fixedY+'px, 0)'
                    +' scale3d('+fixedScale+','+fixedScale+',1)';
            }
        }
    
    
        ,adjustScroller: function() {
            var scroller = this.getScrollable().getScroller();    
            
            // disable scrolling if zoomed out completely, else enable it
            if(this.scale == this.baseScale)
                scroller.setDisabled(true);
            else
                scroller.setDisabled(false);
            
            // size container to final image size
            var boundWidth = Math.max(this.imgWidth * this.scale, this.viewportWidth);
            var boundHeight = Math.max(this.imgHeight * this.scale, this.viewportHeight);
    
            this.figEl.setStyle({
                width: boundWidth + 'px'
                ,height: boundHeight + 'px'
            });
            
            // update scroller to new content size
            scroller.refresh();
    
            // apply scroll
            var x = 0;
            if(this.scrollX){
                x = this.scrollX
            }
            var y = 0;
            if(this.scrollY){
                y = this.scrollY
            }
            // TODO correct?
            scroller.scrollTo(x*-1,y*-1)
        }        
        });

  5. #35
    Sencha User
    Join Date
    Feb 2012
    Location
    Ireland
    Posts
    6
    Vote Rating
    0
    Gaelmart is on a distinguished road

      0  

    Default


    Does anyone have a online working example of this that I can try.

    My client keeps showing me his android phone 2.3 and pinches in and out of any image he finds on any crap web page.
    Then asks me why if after buying the software and spending 2 months developing an app which has one purpose- zoom his diagram images, I can't do it, despite his competitors all have the ability in, quote 'not this fancy html5 rubbish that's supposed to work on anything'. :-)
    He's got a point.

    I'm new to sencha but have all the database drill downs working etc but this is a show-stopper. I'll be back to regular html pages if its a no-go which is ridiculous if you read the sencha PR.
    Users see an small image, their instinct is to pinch, full stop. If it can't do that, what's the point?.

    Thanks


    Gary

  6. #36
    Sencha Premium Member
    Join Date
    Apr 2008
    Posts
    247
    Vote Rating
    24
    themightychris will become famous soon enough themightychris will become famous soon enough

      0  

    Default


    @gary re: pinch-zoom on android

    On Android 2.3, the Android browser itself supports pinch-zooming but does not provide Javascript any access to the events. Sencha can't do anything about it, the blame for that ridiculous functionality gap belongs entirely in Google's hands.

    The only way you could make use of pinch-zoom on Android 2.3 would be to open your images directly in a new browser window, or use PhoneGap/Cordova native wrappers and implement/find a plugin for overlaying a native image viewer. This plugin provides double-tap zoom in/out for Android -- that's the best we can do =/


    @armode re: st2 port

    awesome work armode! thanks for sharing, I'll give it a try and see if I can make any tweaks before adding it to the github repo


    @gkatz re: zooming past original image size

    The maxScale setting's default of 1 is the culprit, increase it to enable deeper zooming
    Chief Architect @ Jarv.us Innovations
    Co-captain @ Code for Philly
    Co-founder @ Devnuts - Philadelphia Hackerspace

  7. #37
    Touch Premium Member hotdp's Avatar
    Join Date
    Nov 2010
    Location
    Denmark
    Posts
    603
    Vote Rating
    14
    hotdp will become famous soon enough

      0  

    Default


    Quote Originally Posted by themightychris View Post
    @gary re: pinch-zoom on android

    On Android 2.3, the Android browser itself supports pinch-zooming but does not provide Javascript any access to the events. Sencha can't do anything about it, the blame for that ridiculous functionality gap belongs entirely in Google's hands.

    The only way you could make use of pinch-zoom on Android 2.3 would be to open your images directly in a new browser window, or use PhoneGap/Cordova native wrappers and implement/find a plugin for overlaying a native image viewer. This plugin provides double-tap zoom in/out for Android -- that's the best we can do =/


    @armode re: st2 port

    awesome work armode! thanks for sharing, I'll give it a try and see if I can make any tweaks before adding it to the github repo


    @gkatz re: zooming past original image size

    The maxScale setting's default of 1 is the culprit, increase it to enable deeper zooming
    Hi, do you have any "date" for when you will have looked at it and added it to git?

  8. #38
    Sencha User
    Join Date
    Dec 2011
    Location
    Brazil
    Posts
    105
    Vote Rating
    0
    Perdiga is an unknown quantity at this point

      0  

    Default


    armode very nice, I was doing the same
    I found some bug in line 169

    Code:
    this.scale = Ext.Number.constrain(ev.scale * this.startScale, this.baseScale, this.maxScale);
    replace for this

    Code:
    this.scale = Ext.Number.constrain(ev.scale * this.startScale, this.baseScale, this.getMaxScale());
    with this maxScale will work fine

    in line 202
    Code:
    if(!this.config.__proto__.doubleTapScale)
    replace for
    Code:
    if(!this.getDoubleTapScale())
    its the same but more elegant
    in line 30 replace
    Code:
    if(this.config.__proto__.loadingMask)
    for
    Code:
    if(this.getLoadingMask())
    in line 74 replace
    Code:
    if(this._imageSrc)
    for
    Code:
    if(this.getImageSrc())
    in line 120 replace
    [CODE]if(this._previewSrc)/CODE]
    for
    Code:
    if(this.getPreviewSrc())
    in line 127 replace
    Code:
    if(this.config.__proto__.loadingMask)
    for
    Code:
    if(this.getLoadingMask())
    as to "scroller.stopMomentumAnimation();" we no longer need to use it because the scroller component already does this or use scroller.stopAnimation(); to ensure

  9. #39
    Sencha User armode's Avatar
    Join Date
    Nov 2011
    Location
    Germany / Darmstadt
    Posts
    64
    Vote Rating
    4
    armode is on a distinguished road

      0  

    Default


    Quote Originally Posted by armode View Post
    Something seems to changed with the scroller coordinates!? To get the doubleclick zoomIn working, I had to convert the scroller coordinates from negative to positive numbers (see adjustScroller)
    Ok, I have found the (self made?) mistake after a bit debugging...
    It's not originViewport - scroller.position, it's originViewport + scroller.position after converting the scroller coordinates!

    Another small issue was that maxScale was undefined, fixed now.

    @Perdiga: oh didn't see your posting, will have a look...

    Code:
        Ext.define('Jarvus.mobile.ImageViewer',{
            
        extend: 'Ext.Container',
        config: {
                doubleTapScale: 1
                ,maxScale: 1
                ,loadingMask: true
                ,previewSrc: false
                ,imageSrc: false
                ,initOnActivate: false
                ,cls: 'imageBox'
                ,scrollable: 'both'            
                ,html: '<figure><img></figure>'
        }
        ,xtype: 'imageviewer'
        ,initialize: function() {
            if(this.initOnActivate)
                this.addListener('activate', this.initViewer, this, {delay: 10, single: true});
            else
                this.addListener('painted', this.initViewer, this, {delay: 10, single: true});        
        }
        
        ,initViewer: function() {
            
            //    disable scroller
            var scroller = this.getScrollable().getScroller();
            scroller.setDisabled(true);
            
            // mask image viewer
            if(this.getLoadingMask())
                this.setMasked({
                       xtype: 'loadmask',
                });
    
            // retrieve DOM els
            this.figEl = this.element.down('figure');
            this.imgEl = this.figEl.down('img');
    
            // apply required styles
            this.figEl.setStyle({
                overflow: 'hidden'
                ,display: 'block'
                ,margin: 0
            });
    
            this.imgEl.setStyle({
                '-webkit-user-drag': 'none'
                ,'-webkit-transform-origin': '0 0'
                ,'visibility': 'hidden'
            });
    
            // show preview
            if(this.getPreviewSrc())
            {
                this.element.setStyle({
                    backgroundImage: 'url('+this.getPreviewSrc()+')'
                    ,backgroundPosition: 'center center'
                    ,backgroundRepeat: 'no-repeat'
                    ,webkitBackgroundSize: 'contain'
                });
            }
    
            // attach event listeners
            this.on('load', this.onImageLoad, this);
            this.imgEl.addListener({
                scope: this
                ,doubletap: this.onDoubleTap
                ,pinchstart: this.onImagePinchStart
                ,pinch: this.onImagePinch
                ,pinchend: this.onImagePinchEnd
            });    
    
            // load image
            if(this.getImageSrc())
                this.loadImage(this.getImageSrc());
        }
        
        ,loadImage: function(src) {    
            if(this.imgEl){
                this.imgEl.dom.src = src;
                this.imgEl.dom.onload = Ext.Function.bind(this.onLoad, this, this.imgEl, 0);
            }
            else
                this.getImageSrc() = src;
        }
    
        ,onLoad : function(el, e) {
            this.fireEvent('load', this, el, e);
        }
        
        ,onImageLoad: function() {
            // get viewport size
            this.viewportWidth = this.viewportWidth || this.getWidth() || this.parent.element.getWidth();
            this.viewportHeight = this.viewportHeight || this.getHeight() || this.parent.element.getHeight();
                
            // grab image size
            this.imgWidth = this.imgEl.dom.width
            this.imgHeight = this.imgEl.dom.height;
                    
            // calculate and apply initial scale to fit image to screen
            if(this.imgWidth > this.viewportWidth || this.imgHeight > this.viewportHeight)
                this.scale = this.baseScale = Math.min(this.viewportWidth/this.imgWidth, this.viewportHeight/this.imgHeight);
            else
                this.scale = this.baseScale = 1;
            
            // set initial translation to center
            this.translateX = this.translateBaseX = (this.viewportWidth - this.baseScale * this.imgWidth) / 2;
            this.translateY = this.translateBaseY = (this.viewportHeight - this.baseScale * this.imgHeight) / 2;
            
            // apply initial scale and translation
            this.applyTransform();
            
            // initialize scroller configuration
            this.adjustScroller();
    
            // show image and remove mask
            this.imgEl.setStyle({ visibility: 'visible' });
    
            // remove preview
            if(this.getPreviewSrc())
            {
                this.element.setStyle({
                    backgroundImage: 'none'
                });
            }
    
            if(this.getLoadingMask())
                this.setMasked(false);
    
            this.fireEvent('imageLoaded', this);
        }
        
        ,onImagePinchStart: function(ev) {
            var scroller = this.getScrollable().getScroller();
    
            // disable scrolling during pinch
            // TODO ? reactivate: scroller.stopMomentumAnimation();
            scroller.setDisabled(true);
            
            // store beginning scale
            this.startScale = this.scale;
            
            // calculate touch midpoint relative to image viewport
            this.originViewportX = (ev.touches[0].pageX + ev.touches[1].pageX) / 2 - this.element.getX();
            this.originViewportY = (ev.touches[0].pageY + ev.touches[1].pageY) / 2 - this.element.getY();
            
            // translate viewport origin to position on scaled image
            this.originScaledImgX = this.originViewportX + scroller.position.x - this.translateX;
            this.originScaledImgY = this.originViewportY + scroller.position.y - this.translateY;
            
            // unscale to find origin on full size image
            this.originFullImgX = this.originScaledImgX / this.scale;
            this.originFullImgY = this.originScaledImgY / this.scale;
            
            // calculate translation needed to counteract new origin and keep image in same position on screen
            this.translateX += (-1 * ((this.imgWidth*(1-this.scale)) * (this.originFullImgX/this.imgWidth)));
            this.translateY += (-1 * ((this.imgHeight*(1-this.scale)) * (this.originFullImgY/this.imgHeight)))
        
            // apply new origin
            this.setOrigin(this.originFullImgX, this.originFullImgY);
        
            // apply translate and scale CSS
            this.applyTransform();
        }
        
        ,onImagePinch: function(ev) {
            // prevent scaling to smaller than screen size
            this.scale = Ext.Number.constrain(ev.scale * this.startScale, this.baseScale, this.getMaxScale());
            this.applyTransform();
        }
        
        ,onImagePinchEnd: function(ev) {
            
            // set new translation
            if(this.scale == this.baseScale)
            {
                // move to center
                this.setTranslation(this.translateBaseX, this.translateBaseY);
            }
            else
            {
                // calculate rescaled origin
                this.originReScaledImgX = this.originScaledImgX * (this.scale / this.startScale);
                this.originReScaledImgY = this.originScaledImgY * (this.scale / this.startScale);
                
                // maintain zoom position
                this.setTranslation(this.originViewportX - this.originReScaledImgX, this.originViewportY - this.originReScaledImgY);            
            }
            // reset origin and update transform with new translation
            this.setOrigin(0, 0);
            this.applyTransform();
    
            // adjust scroll container
            this.adjustScroller();
        }
    
        
        ,onDoubleTap: function(ev, t) {
            
            var scroller = this.getScrollable().getScroller();
            if(!this.getDoubleTapScale())
                return false;
            
            // set scale and translation
            if(this.scale >= .9)
            {
                // zoom out to base view
                this.scale = this.baseScale;
                this.setTranslation(this.translateBaseX, this.translateBaseY);
            }
            else
            {
                // zoom in toward tap position
                var oldScale = this.scale
                    ,newScale = 1
                    ,originViewportX = ev ? (ev.pageX - this.element.getX()) : 0
                    ,originViewportY = ev ? (ev.pageY - this.element.getY()) : 0
                    ,originScaledImgX = originViewportX + scroller.position.x - this.translateX
                    ,originScaledImgY = originViewportY + scroller.position.y - this.translateY
                    ,originReScaledImgX = originScaledImgX * (newScale / oldScale)
                    ,originReScaledImgY = originScaledImgY * (newScale / oldScale);
                    
                this.scale = newScale;
                this.setTranslation(originViewportX - originReScaledImgX, originViewportY - originReScaledImgY);
            }
                
            // reset origin and update transform with new translation
            this.applyTransform();
    
            // adjust scroll container
            this.adjustScroller();
            
            // force repaint to solve occasional iOS rendering delay
            Ext.repaint();
        }
        
        ,setOrigin: function(x, y) {
            this.imgEl.dom.style.webkitTransformOrigin = x+'px '+y+'px';
        }
        
        ,setTranslation:  function(translateX, translateY) {
            this.translateX = translateX;
            this.translateY = translateY;
                
            // transfer negative translations to scroll offset
            this.scrollX = this.scrollY = 0;
            
            if(this.translateX < 0)
            {
                this.scrollX = this.translateX;
                this.translateX = 0;
            }
            if(this.translateY < 0)
            {
                this.scrollY = this.translateY;
                this.translateY = 0;
            }
        }
            
    
        ,applyTransform: function() {
            var fixedX = Ext.Number.toFixed(this.translateX,5)
                ,fixedY = Ext.Number.toFixed(this.translateY,5)
                ,fixedScale = Ext.Number.toFixed(this.scale, 8);
            
            if(Ext.os.is.Android)
            {
                this.imgEl.dom.style.webkitTransform = 
                    //'translate('+fixedX+'px, '+fixedY+'px)'
                    //+' scale('+fixedScale+','+fixedScale+')';
                    'matrix('+fixedScale+',0,0,'+fixedScale+','+fixedX+','+fixedY+')'
            }
            else
            {
                this.imgEl.dom.style.webkitTransform =
                    'translate3d('+fixedX+'px, '+fixedY+'px, 0)'
                    +' scale3d('+fixedScale+','+fixedScale+',1)';
            }
        }
    
    
        ,adjustScroller: function() {
            var scroller = this.getScrollable().getScroller();    
            
            // disable scrolling if zoomed out completely, else enable it
            if(this.scale == this.baseScale)
                scroller.setDisabled(true);
            else
                scroller.setDisabled(false);
            
            // size container to final image size
            var boundWidth = Math.max(this.imgWidth * this.scale, this.viewportWidth);
            var boundHeight = Math.max(this.imgHeight * this.scale, this.viewportHeight);
    
            this.figEl.setStyle({
                width: boundWidth + 'px'
                ,height: boundHeight + 'px'
            });
            
            // update scroller to new content size
            scroller.refresh();
    
            // apply scroll
            var x = 0;
            if(this.scrollX){
                x = this.scrollX
            }
            var y = 0;
            if(this.scrollY){
                y = this.scrollY
            }
            scroller.scrollTo(x*-1,y*-1)
        }        
        });

  10. #40
    Sencha User armode's Avatar
    Join Date
    Nov 2011
    Location
    Germany / Darmstadt
    Posts
    64
    Vote Rating
    4
    armode is on a distinguished road

      0  

    Default


    Quote Originally Posted by Perdiga View Post
    armode very nice, I was doing the same
    I found some bug in line...
    Thanks for the hints, that's much nicer! I will edit my code above in a few minutes