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="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.Carousel',
xtype: 'my-viewport',
config: {
//fullscreen: true,
//layout: 'fit',
items: [
{
xtype: 'imageviewer',
style: {
backgroundColor: '#255'
},
imageSrc: 'http://cdn.macrumors.com/article/2010/09/03/145454-itunes_10_icon.jpg'
},
{
xtype: 'imageviewer',
style: {
backgroundColor: '#255'
},
imageSrc: 'http://www.baixandowallpapers.com/wallpapers/03-2011/lua-cheia-paisagem-noite-1299711122.jpg'
},
{
xtype: 'imageviewer',
style: {
backgroundColor: '#255'
},
imageSrc: 'http://www.baixandowallpapers.com/wallpapers/03-2011/lua-cheia-paisagem-noite-1299711122.jpg'
}
],
listeners: {
activeitemchange: function( container,value, oldValue,eOpts ){
if(oldValue){
//oldValue.resetZoom();
this.getActiveItem().resize();
}
},
resize: function( component , eOpts ){
this.getActiveItem().resize();
}
},
},
onDragStart: function (e) {
var scroller = this.getActiveItem().getScrollable().getScroller();
if (e.targetTouches.length == 1 && (e.deltaX < 0 && scroller.getMaxPosition().x === scroller.position.x) || (e.deltaX > 0 && scroller.position.x == 0)) {
this.callParent(arguments);
}
},
onDrag: function (e) {
if (e.targetTouches.length == 1)
this.callParent(arguments);
},
onDragEnd: function (e) {
if (e.targetTouches.length < 2)
this.callParent(arguments);
}
});
Ext.define('WPMobile.ViewportPanel', {
extend: 'Ext.Panel',
xtype: 'my-viewport-panel',
config: {
fullscreen: true,
layout: 'fit',
items: [
{
xtype: 'titlebar',
docked: 'top',
items: [
{ text: 'ZoomIn',
listeners: {
tap:function(){
Ext.getCmp('myCarousel').getActiveItem().onZoomIn();
}
},
},
{ text: 'ZoomOut' ,
listeners: {
tap:function(){
Ext.getCmp('myCarousel').getActiveItem().onZoomOut();
},
},
},
]
},
{
xtype:'my-viewport',
id:'myCarousel'
},
{
xtype: 'titlebar',
docked: 'bottom',
items: [
{ text: 'ZoomIn',
listeners: {
tap:function(){
Ext.getCmp('myCarousel').getActiveItem().onZoomIn();
}
},
},
{ text: 'ZoomOut' ,
listeners: {
tap:function(){
Ext.getCmp('myCarousel').getActiveItem().onZoomOut();
},
},
},
]
},
],
},
});
Ext.Viewport.add({xtype:'my-viewport-panel'});
}
});
</script>
</head>
<body></body>
</html>
imageViewer.js
Code:
Ext.define('Arkivus.ImageViewer',{
extend: 'Ext.Container',
config: {
doubleTapScale: 1
,maxScale: 4
,loadingMask: true
,previewSrc: false
,resizeOnLoad:true
,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.getResizeOnLoad()){
this.scale = this.baseScale = Math.min(this.viewportWidth/this.imgWidth, this.viewportHeight/this.imgHeight);
this.setMaxScale(this.scale*4);
}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
scroller.stopAnimation();
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-2, 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
{
//Resize to init size like ios
if(this.scale < this.baseScale && this.getResizeOnLoad()){
this.resetZoom();
return;
}
// 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();
}
,onZoomIn: function(){
var myScale = this.scale;
if (myScale < this.getMaxScale())
myScale = this.scale + 0.05;
if (myScale >= this.getMaxScale())
myScale = this.getMaxScale();
var ev = {pageX : 0, pageY: 0};
ev.pageX = this.viewportWidth / 2;
ev.pageY = this.viewportHeight / 2;
this.zoomImage(ev,myScale);
}
,onZoomOut: function(){
var myScale = this.scale;
if (myScale > this.baseScale)
myScale = this.scale - 0.05;
if (myScale <= this.baseScale)
myScale = this.baseScale;
var ev = {pageX : 0, pageY: 0};
ev.pageX = this.viewportWidth / 2;
ev.pageY = this.viewportHeight / 2;
this.zoomImage(ev,myScale);
}
,zoomImage: function (ev, scale, scope) {
var scroller = this.getScrollable().getScroller();
var that = this;
// zoom in toward tap position
var oldScale = this.scale
,newScale = scale
,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;
setTimeout(function(){
that.setTranslation(originViewportX - originReScaledImgX, originViewportY - originReScaledImgY);
// reset origin and update transform with new translation
//that.setOrigin(0, 0);
// reset origin and update transform with new translation
that.applyTransform();
// adjust scroll container
that.adjustScroller();
// force repaint to solve occasional iOS rendering delay
Ext.repaint();
},50)
}
,onDoubleTap: function(ev, t) {
var that = this;
var scroller = this.getScrollable().getScroller();
if(!this.getDoubleTapScale())
return false;
// set scale and translation
if(this.scale > this.baseScale)
{
// zoom out to base view
this.scale = this.baseScale;
this.setTranslation(this.translateBaseX, this.translateBaseY);
// 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();
}
else
{
// zoom in toward tap position
var oldScale = this.scale
,newScale = this.baseScale*4
,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;
//smoothes the transition
setTimeout(function(){
that.setTranslation(originViewportX - originReScaledImgX, originViewportY - originReScaledImgY);
// reset origin and update transform with new translation
that.applyTransform();
// adjust scroll container
that.adjustScroller();
// force repaint to solve occasional iOS rendering delay
Ext.repaint();
},50)
}
}
,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;
}
}
,resetZoom:function(){
//Resize to init size like ios
this.scale = this.baseScale;
this.setTranslation(this.translateBaseX, this.translateBaseY);
// reset origin and update transform with new translation
this.setOrigin(0, 0);
this.applyTransform();
// adjust scroll container
this.adjustScroller();
}
,resize:function(){
// get viewport size
this.viewportWidth = this.parent.element.getWidth() ||this.viewportWidth || this.getWidth();
this.viewportHeight = this.parent.element.getHeight() || this.viewportHeight || this.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.getResizeOnLoad()){
this.scale = this.baseScale = Math.min(this.viewportWidth/this.imgWidth, this.viewportHeight/this.imgHeight);
this.setMaxScale(this.scale*4);
}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();
}
,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)
}
});