Hybrid View

  1. #1
    Sencha Premium Member
    Join Date
    Apr 2008
    Posts
    231
    Vote Rating
    19
    themightychris will become famous soon enough themightychris will become famous soon enough

      0  

    Default mostly working pinch-zoom image carousel -- help perfect it!

    mostly working pinch-zoom image carousel -- help perfect it!


    After scouring the forums, I found many discussions about implementing a pinch-zoom image carousel. There were useful bits of code here and there but nothing complete.

    I've got something working decently now... the trickiest part involved mobile safari not adjusting scrollWidth/scrollHeight when scale3d is applied. I solved that by wrapping the image in a block container with overflow:hidden that gets set to the scaled size on pinchend.

    Here's the complete code for anyone to reuse... there are still a few quirks though that I could use some help solving though:
    - Scaling is from 0,0 origin... ideally the origin should be the midpoint between your fingers so you can actually zoom into a desired point
    - Pinch events are monitored on the <img> el... if you pinch with one finger on the img and one finger in the empty space, it's all kinds of quirky... if I attach the pinch events to the container instead, it feels less responsive

    Complete demo here: http://mobile.jarv.us/pinch/index.html

    To debug on a mobile device with weinre, use this URL instead: http://mobile.jarv.us/pinch/index.html#with-weinre
    Then visit: http://mobile.jarv.us:8080/client/#pinch


    Code:
    Ext.ns('Jarvus.views');
    
    Jarvus.views.PinchSandbox = Ext.extend(Ext.Container, {
    
    	title: 'Pinch Sandbox'
    	,cls: 'MoreImages'
    	
    	,layout: 'fit'
    	
    	,initComponent: function() {
    
    		this.carousel = new Ext.Carousel({
    			flex: 1
    			,ui: 'light'
    			,items: [{
    				xtype: 'component'
    				,cls: 'imageBox'
    				,scroll: 'both'
    				,html: '<figure>'+Ext.LoadingSpinner+'<img src="http://placekitten.com/1300/900"></figure>'
    				,listeners: {
    					scope: this
    					,afterrender: this.onAfterImageBoxRender
    				}
    			},{
    				xtype: 'component'
    				,cls: 'imageBox'
    				,html: '<figure>'+Ext.LoadingSpinner+'<img src="http://placekitten.com/800/1200"></figure>'
    				,scroll: 'both'
    				,listeners: {
    					scope: this
    					,afterrender: this.onAfterImageBoxRender
    				}
    			},{
    				xtype: 'component'
    				,cls: 'imageBox'
    				,html: '<figure>'+Ext.LoadingSpinner+'<img src="http://placekitten.com/800/800"></figure>'
    				,scroll: 'both'
    				,listeners: {
    					scope: this
    					,afterrender: this.onAfterImageBoxRender
    				}
    			}]
    			
    			// Check for Pinch Conflict
    			,onDragStart: function(e) {
    				if(e.targetTouches.length == 1)
    					Ext.Carousel.prototype.onDragStart.call(this, e);
    			}
    			
    			// Check for Pinch Conflict
    			,onDrag: function(e) {
    				if(e.targetTouches.length == 1)
    					Ext.Carousel.prototype.onDrag.call(this, e);
    			}
    			
    			// Check for Pinch Conflict
    			,onDragEnd: function(e) {
    				if(e.targetTouches.length < 2)
    					Ext.Carousel.prototype.onDragEnd.call(this, e);
    			}
    		});
    
    		
    		this.items = this.carousel    	
    		
    		Jarvus.views.PinchSandbox.superclass.initComponent.apply(this, arguments);
    
    	}
    	
    	,onAfterImageBoxRender: function(imgBox) {
    		
    		// retrieve DOM els
    		imgBox.figEl = imgBox.el.down('figure');
    		imgBox.imgEl = imgBox.figEl.down('img');
    		
    		// apply required styles
    		imgBox.figEl.setStyle({
    			overflow: 'hidden'
    			,display: 'block'
    			,margin: 0
    			,textAlign: 'center'
    		});
    		
    		imgBox.imgEl.setStyle({
    			'-webkit-user-drag': 'none'
    			,'-webkit-transform-origin': '0 0'
    			,'visibility': 'hidden'
    		});
    
    	
    		this.mon(imgBox.imgEl, 'load', function() {
    			
    			// calculate and apply initial scale to fit image to screen
    			imgBox.scale = imgBox.baseScale = Math.min(imgBox.getWidth()/imgBox.imgEl.getWidth(), imgBox.getHeight()/imgBox.imgEl.getHeight());
    			imgBox.imgEl.dom.style.webkitTransform = 'scale3d('+imgBox.baseScale+','+imgBox.baseScale+',1)';
    			
    			console.log('image loaded: '+imgBox.imgEl.getWidth()+' x '+imgBox.imgEl.getHeight()+', baseScale set to '+imgBox.baseScale);
    			
    			imgBox.el.down('.x-loading-spinner').remove();
    			imgBox.imgEl.setStyle({ visibility: '' });
    
    			// initialize scroller configuration
    			this.adjustScroller(imgBox);
    			
    			// wire pinch listeners
    			imgBox.imgEl.on({
    				scope: this
    				,pinchstart: function(ev, t) {
    					console.log('pinchstart');
    					imgBox.startScale = imgBox.scale;
    					
    					// disable scrolling during pinch
    					imgBox.scroller.stopMomentumAnimation();
    					imgBox.scroller.disable();
    				}
    				,pinch: function(ev, t) {
    					// prevent scaling to smaller than screen size
    					imgBox.scale = Ext.util.Numbers.constrain(ev.scale * imgBox.startScale, imgBox.baseScale);
    
    					imgBox.imgEl.dom.style.webkitTransform = 'scale3d('+imgBox.scale+','+imgBox.scale+',1)';
    				}
    				,pinchend: function(ev, t) {
    					this.adjustScroller(imgBox);
    				}
    			});
    		}, this)
    		
    	}
    	
    	,adjustScroller: function(imgBox) {
    		console.log('adjustScroller: scale='+imgBox.scale+', baseScale='+imgBox.baseScale);
    	
    		// disable scrolling if zoomed out completely, else enable it
    		if(imgBox.scale == imgBox.baseScale)
    			imgBox.scroller.disable();
    		else
    			imgBox.scroller.enable();
    		
    		
    		// size container to final image size
    		var boundWidth = Math.max(imgBox.imgEl.getWidth() * imgBox.scale, imgBox.scroller.containerBox.width);
    		var boundHeight = Math.max(imgBox.imgEl.getHeight() * imgBox.scale, imgBox.scroller.containerBox.height);
    		imgBox.figEl.setStyle({
    			width: boundWidth + 'px'
    			,height: boundHeight + 'px'
    		});
    		
    		// update scroller to new content size
    		imgBox.scroller.updateBoundary();
    	}
    	
    	
    	// debug routine - change scale by a specific +/- delta
    	,changeScale: function(delta) {
    		console.log('changeScale('+delta+')');
    		var imgBox = this.carousel.getActiveItem();
    		this.setScale(Ext.util.Numbers.constrain(delta + imgBox.scale, imgBox.baseScale));
    	}
    	
    	// debug routine - set a specific scale
    	,setScale: function(scale) {
    		var imgBox = this.carousel.getActiveItem();
    
    		console.log('setScale('+scale+'), baseScale='+imgBox.baseScale);
    
    		imgBox.scale = scale;
    		imgBox.imgEl.dom.style.webkitTransform = 'scale3d('+imgBox.scale+','+imgBox.scale+',1)';		
    		
    		this.adjustScroller(imgBox);
    			
    	}
    	
    });
    Attached Files
    Chief Architect @ Jarv.us Innovations
    Co-captain @ Code for Philly
    Co-founder @ Devnuts - Philadelphia Hackerspace

  2. #2
    Sencha Premium Member
    Join Date
    Apr 2008
    Posts
    231
    Vote Rating
    19
    themightychris will become famous soon enough themightychris will become famous soon enough

      0  

    Default


    This bug is preventing me from attaching the pinchstart listener to an element that takes up the full screen =[: http://www.sencha.com/forum/showthre...ighlight=pinch

    So when scaling the image leaves empty space to the side or bottom... you can't start your pinch with a finger outside the image.
    Chief Architect @ Jarv.us Innovations
    Co-captain @ Code for Philly
    Co-founder @ Devnuts - Philadelphia Hackerspace

  3. #3
    Sencha Premium Member
    Join Date
    Apr 2008
    Posts
    231
    Vote Rating
    19
    themightychris will become famous soon enough themightychris will become famous soon enough

      0  

    Lightbulb


    I've been working on getting the zoom to expand/collapse around the midpoint of the pinch... it's real wonky though and I'm not sure if the error is in my logic or its application

    here's what i'm trying to do in the attached code:

    pinchstart:
    - calculate midpoint between two touches, relative to scroller.el
    - translate the midpoint on the screen to its corresponding point on the full unscaled image, set that as the -webkit-transform-origin

    pinch:
    - apply scale3d to image, use translate3d with an offset equal to the scroller.el midpoint * current scale * -1

    pinchend:
    - reset transform origin to 0,0
    - remove translate3d
    - update image container to new scaled image size
    - set scroller offset to last translate3d position


    any ideas??
    Attached Files
    Chief Architect @ Jarv.us Innovations
    Co-captain @ Code for Philly
    Co-founder @ Devnuts - Philadelphia Hackerspace

  4. #4
    Sencha User
    Join Date
    Jul 2011
    Posts
    1
    Vote Rating
    0
    alexis.puska is on a distinguished road

      0  

    Default


    Hi,

    I have found your code very great, but it's only for the <img> tag, i have try to apply this with the object tag and the code don't work, i think the afterRender event is not triggered. did you have a solution, i would like to view with zoom and scroll some many type of document (pdf, img, xls, doc, txt...) did you have a solution to use the object tag with your code ?

    thanks.

    PS : sorry for my english

  5. #5
    Sencha Premium Member
    Join Date
    Apr 2008
    Posts
    231
    Vote Rating
    19
    themightychris will become famous soon enough themightychris will become famous soon enough

      0  

    Default


    It should work with any tag, but I'm not sure how you'd load those files into an object tag. One big limitation of Sencha Touch's gesture recognition is that pinch doesn't work if your two fingers are touching different tags, even if they are children of the same tag. This basically means that you can only monitor for pinches on single elements with no children
    Chief Architect @ Jarv.us Innovations
    Co-captain @ Code for Philly
    Co-founder @ Devnuts - Philadelphia Hackerspace

  6. #6
    Sencha User
    Join Date
    Feb 2011
    Posts
    57
    Vote Rating
    2
    Hertz is on a distinguished road

      0  

    Default


    I would like to help, I am working on this functionality as well.
    I took a look at the code and I got a little confused by the names.

    Could you explain me what these properties are supposed to keep track of:
    imgBox.scale (current scale?)
    imgBox.baseScale
    imgBox.startScale (this should be fixed but I see you update it on each 'pinchstart')
    On my version, I use a separate object to keep track of width, height, scale and sensibility factor.
    For scale i have:
    • min
    • max
    • current (set equal to min after the image is loaded, used as a starting point and updated on 'pinchend' with the value of 'new')
    • new (only used while on 'pinch')

  7. #7
    Sencha User
    Join Date
    Jan 2012
    Posts
    6
    Vote Rating
    0
    lalbihari is on a distinguished road

      0  

    Default


    but this is not working in multi screen devices.
    have u checked????.........

  8. #8
    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 lalbihari View Post
    but this is not working in multi screen devices.
    have u checked????.........
    what do you mean with 'multi screen devices'?

    I have found a solution how to show PNGs or GIFs inside the component, actually there's no bug in the component, it's a problem on iOS devices. There seems to be a 'magic' resolution border of about 5 Megapixels; if the image is bigger than that size, the bowser just show a question mark respectivly the component can't load the image. Maybe the 5MP border changes on different iOS devices, in my case an iPhone 4. Any idea, if it is possible to show bigger images on iOS devices???

    Still didn't find a solution how to get the pinching gestures to work on Android phones...

  9. #9
    Sencha User
    Join Date
    Nov 2010
    Posts
    385
    Vote Rating
    4
    gkatz is on a distinguished road

      0  

    Default porting to sencha touch 2

    porting to sencha touch 2


    Hi;
    is there a port to sencha touch 2 in the works? anything planned? maybe even something based on Ext.Image?

  10. #10
    Sencha Premium Member
    Join Date
    Apr 2008
    Posts
    231
    Vote Rating
    19
    themightychris will become famous soon enough themightychris will become famous soon enough

      0  

    Default


    re: pinch on android
    it's not going to happen, at least not on current devices... the browser doesn't pass multitouch events

    re: sencha 2 port
    i'm going to tackle it soon, I just started getting the hang of ST2. Basing it on Ext.Image is a good idea
    Chief Architect @ Jarv.us Innovations
    Co-captain @ Code for Philly
    Co-founder @ Devnuts - Philadelphia Hackerspace