1343c3c45c9c3c766e7231aa50cdcad4

In this class all prototype functions except for init are passed the instance of the class to operate on. Is there a way to make it work without passing the instance(named Z in the script) every time ?

/**
 *  SVGZoomNPan library 1.0
 * ========================
 *
 */
var ZoomNPan = function (zoomElt) {
	if (zoomElt != '[object SVGSVGElement]') {
		console.log('Argument passed is not an SVG element');
		return;
	}
	
	this.zoomElt = zoomElt ;
	
	this.options = {

		autoRun:true,
		autoFitViewport:true, 
		zoomScale:1.2,
		fastZoomMultiplier: 5,
		toggleZoomKeyCode: 27,
		cursorGrab: ' move',
		cursorHand: ' default'
		
	};
	
	this.currentState = {
		state: 'none',
		panX: 0,
		panY: 0,
		translateX: 0,
		translateY: 0,
		oldTranslateX: 0,
		oldTranslateY: 0,
		zoomAllowed: this.options.autoRun,
		timePrev: 0
			
	};
	this.init()
};
	
ZoomNPan.prototype.init = function () {
	
	var self = this;
	// Setup event listeners
	this.zoomElt.addEventListener('mousedown',  function(evt){self.handleMouseDown (evt, self)}, false);
	this.zoomElt.addEventListener('mouseup',    function(evt){self.handleMouseUp (evt, self)}, false);
	this.zoomElt.addEventListener('mousemove',  function(evt){self.handleMouseMove (evt, self)}, false);
	this.zoomElt.addEventListener('mousewheel', function(evt){self.handleMouseWheel (evt, self)}, false);
	this.zoomElt.addEventListener('keyup',      function(evt){self.handleKeyUp (evt, self)}, false);
	//zoomElt.addEventListener('DOMMouseScroll', handleMouseWheel, false);// mousewheel for Firefox
	
	//this.currentState.zoomAllowed = this.options.autoRun
	
	// Scale the image to fit screen
	if (self.options.autoFitViewport){
		self.makeViewBox(self.zoomElt);
	}
}

ZoomNPan.prototype.handleMouseMove = function (evt, Z) { 


  if (Z.currentState.zoomAllowed){
		if(evt.preventDefault){
			evt.preventDefault();
		}
		evt.returnValue = false;

		if(Z.currentState.state == 'pan') {
			// Pan mode
			// get difference to previous position
			Z.currentState.translateX = evt.x - Z.currentState.panX;
			Z.currentState.translateY = evt.y - Z.currentState.panY;

			evt.currentTarget.currentTranslate.x += Z.currentState.translateX;
			evt.currentTarget.currentTranslate.y += Z.currentState.translateY;
				
			// Remember previous position
			Z.currentState.panX = evt.x;
			Z.currentState.panY = evt.y;
		}
	}
}

		
ZoomNPan.prototype.handleMouseDown = function (evt, Z) {
  	if (Z.currentState.zoomAllowed) {
		// Handle click
		if(evt.preventDefault) {
			evt.preventDefault();
		}

		evt.returnValue = false;

		if(evt.currentTarget.tagName == "svg") {
			// Pan mode
			Z.currentState.state = 'pan';
			Z.zoomElt.style.cursor = Z.options.cursorGrab;
			Z.currentState.panX = evt.x;
			Z.currentState.panY = evt.y;
		}
	}
}

ZoomNPan.prototype.handleMouseUp = function(evt, Z) {
//alert('mouse released')
  if (Z.currentState.zoomAllowed){
		// Handle mouse button release
		if(evt.preventDefault){
			evt.preventDefault();
		}
		evt.returnValue = false;

		if (Z.currentState.state == 'pan') {
			// Quit pan mode
			Z.zoomElt.style.cursor = Z.options.cursorHand;//'default'
			Z.currentState.state = '';
		}
	}
}

ZoomNPan.prototype.handleMouseWheel = function (evt, Z) {
  if (Z.currentState.zoomAllowed){
		if (evt.preventDefault){
			evt.preventDefault();
		}
		evt.returnValue = false;
		var delta;
		if (evt.wheelDelta) {
			delta = evt.wheelDelta / 360; // Webkit, Opera
		}
		else{
			delta = evt.detail / -9; // Mozilla, Opera
		}
		if (evt.altKey){
			delta *= Z.options.fastZoomMultiplier;
		}
		ZoomNPan.prototype.zoomIt(delta, Z);
	}
}

ZoomNPan.prototype.handleKeyUp = function (evt, Z) {
	if (evt.keyCode != Z.options.toggleZoomKeyCode) {
		return
	}
	// Global killswitch :) 
	// Toggle zoom and pan when toggle key is pressed (default "z")
	Z.currentState.zoomAllowed = !Z.currentState.zoomAllowed;
	if (!Z.currentState.zoomAllowed){
		Z.resetState();
		}
}

ZoomNPan.prototype.handleMouseOut = function (evt, Z){
  if (Z.currentState.zoomAllowed){
	// not used, buggy
	// Handle mouse out event
		if(evt.preventDefault){
			evt.preventDefault();
		}
		evt.returnValue = false;

		if (Z.currentState.state == 'pan') {
			// Quit pan mode
			Z.currentState.state = '';
		}
	}
}

ZoomNPan.prototype.zoomIt = function (delta, Z) {

	var timeNow = Date.now();
	if (timeNow - Z.currentState.timePrev > 0) { // skip unnecessary redraw
		var cur = Z.currentState;
		var ZE = Z.zoomElt;
		var oldScale = ZE.currentScale;
		var scaleFactor = Math.pow(1 + Z.options.zoomScale, delta);
		
		// Remember translate before zooming
		cur.oldTranslateX = ZE.currentTranslate.x;
		cur.oldTranslateY = ZE.currentTranslate.y;

		ZE.currentScale *= scaleFactor;
		
		var vp_width = ZE.viewport.width;
		var vp_height = ZE.viewport.height;
		
		// Very complicated calculations :)
		// Borrowed from http://jwatt.org/svg/tests/zoom-and-pan-controls.svg
		ZE.currentTranslate.x = vp_width/2  - (ZE.currentScale/oldScale) * (vp_width/2  - cur.oldTranslateX);
		ZE.currentTranslate.y = vp_height/2 - (ZE.currentScale/oldScale) * (vp_height/2 - cur.oldTranslateY);
	
		cur.timePrev = timeNow;
	 }
}

 ZoomNPan.prototype.zoom1x = function (Z) {
 
	Z.zoomElt.currentScale = 1;//(1.0);
	Z.zoomElt.currentTranslate.x = 0;//(1.0);
	Z.zoomElt.currentTranslate.y = 0;//(1.0);
}

ZoomNPan.prototype.zoomIn = function (Z) {
	zoomIt (0.23, Z)
}

ZoomNPan.prototype.zoomOut = function (Z) {
	zoomIt (-0.23, Z)

}

ZoomNPan.prototype.resetState = function(Z) {
	//if (!Z.currentState.zoomAllowed){
		Z.zoomElt.style.cursor = 'default';
		Z.currentState.oldTranslateX = 0;
		Z.currentState.oldTranslateY = 0;
		Z.currentState.state == 'none'
	//}
}

ZoomNPan.prototype.makeViewBox = function (SVGElt, Z) {
		
	var doc  = SVGElt || Z.zoomElt, // to be able to use this function  separately
		w  = parseFloat(doc.viewport.width) || 0,
		h  = parseFloat(doc.viewport.height) || 0,
		wAttr = doc.getAttributeNS(null, "width"),
		hAttr = doc.getAttributeNS(null, "height"),
		vB    = doc.viewBox.baseVal;//SVGRoot.getAttributeNS(null, "viewBox");

	if (!wAttr && !hAttr && !(vB && vB.width)) { 
		// If there are NO width & height & viewbox try to guess them
		var BBox = doc.getBBox();
		vB.x = BBox.x;
		vB.y = BBox.y;
		vB.width = BBox.width;
		vB.height = BBox.height;
	}
	else if ((wAttr || hAttr) && !vB.width) {
	// If there IS width or height and NO viewBox,
	// generate viewbox based on them
		vB.x = doc.x.baseVal.value;
		vB.y = doc.y.baseVal.value;
		vB.width  = w || doc.width.baseVal.value;
		vB.height = h || doc.height.baseVal.value;
		
		doc.removeAttributeNS(null, 'width');
		doc.removeAttributeNS(null, 'height');
	}
	else if((wAttr || hAttr) && vB.width){
	// If there are ALL OF width/height/viewBox
	// only remove width and height attibutes
	// or opera won't scale to fit the image
			doc.removeAttributeNS(null, 'width');
			doc.removeAttributeNS(null, 'height');
	}
	
	return doc;
}

Refactorings

No refactoring yet !

D41d8cd98f00b204e9800998ecf8427e

lomax

September 13, 2010, September 13, 2010 20:53, permalink

No rating. Login to rate!

The way you've bound the event listeners, Z is equivalent to the keyword 'this' in every single one of your prototype methods. Try "alert(this==Z)" in one of your methods to verify. The reason I suspect you got confused was because of the scope change during your anonymous function event rebind.

for example,

this.zoomElt.addEventListener('mousedown', function(evt){self.handleMouseDown (evt, self)}, false);

can be changed without semantic loss to

this.zoomElt.addEventListener('mousedown', function(evt){self.handleMouseDown (evt)}, false);

because you've already established the link to 'this'/self/Z by calling the bound instance method self.handleMouseDown; this means that inside handleMouseDown, the keyword 'this' will refer to whatever 'self' refers to in the anonymous function, and can supplant the reference to 'Z'.

However, I would go even further, and suggest that you bind pointer references, instead of anonymous functions. I also suspect that this class is instantiated perhaps once or twice per user experience, not the thousand+ times that would offer a performance benefit in sharing a single prototype method over an instance method. Therefore, I would suggest refactoring your class in a more encapsulated manner as so:

*(Note that this is clearly untested, I hope I didn't miss any public/private conversions; I also had to make some guesses about which methods needed to be public to consume the class programmatically, and which were artifacts of using the prototype pattern; If I guessed incorrectly, simply follow the pattern and adjust accordingly)

/**
 *  SVGZoomNPan library 1.0
 * ========================
 *
 */
var ZoomNPan=function(){
	//private members
	var _currentState;
	var _initialized;
	var _options;
	var _this;
	var _zoomElt;
	
	//public members
    this.currentState;
	this.options;
	
	// ctor
	function ZoomNPan(zoomElt){
	    // Guard parameters
	    if(zoomElt != '[object SVGSVGElement]') {
		    console.log('Argument passed is not an SVG element');
		    return;
	    }
	    // Set defaults/private members
        _currentState={
		    state: 'none',
		    panX: 0,
		    panY: 0,
		    translateX: 0,
		    translateY: 0,
		    oldTranslateX: 0,
		    oldTranslateY: 0,
		    zoomAllowed: _options.autoRun,
		    timePrev: 0
	    };
	    _options={
		    autoRun:true,
		    autoFitViewport:true, 
		    zoomScale:1.2,
		    fastZoomMultiplier: 5,
		    toggleZoomKeyCode: 27,
		    cursorGrab: ' move',
		    cursorHand: ' default'
	    };
	    _this=this;
	    _zoomElt=zoomElt;
	    
	    // Assign public members
	    //      NOTE: omit these public assignments if you don't really need them exposed;
	    //            the class no longer uses them internally. Also delete them in the 
	    //            public member placeholder assignment above; I can't tell your intended 
	    //            usage from the class definition.
	    this.zoomElt=_zoomElt;
	    this.currentState=_currentState;
        this.options=_options;
	
	    addEvents();
	    scaleImage();
	    
	    _initialized=true;
	}
	// Call ctor, preserving instantiation scope; equivalent to your this.init() call;
	ZoomNPan.apply(this,arguments); 
	
	// Public methods
	//      NOTE: I made best guesses about which methods actually need to be exposed.
	//            If I guessed wrong, simply follow the pattern to expose more/less as needed.

    this.zoomIt=function (delta) {
	    ensureControl();
	    var timeNow = Date.now();
	    if (timeNow - _currentState.timePrev > 0) { // skip unnecessary redraw
		    var cur = _currentState;
		    var ZE = _zoomElt;
		    var oldScale = ZE.currentScale;
		    var scaleFactor = Math.pow(1 + _options.zoomScale, delta);
    		
		    // Remember translate before zooming
		    cur.oldTranslateX = ZE.currentTranslate.x;
		    cur.oldTranslateY = ZE.currentTranslate.y;

		    ZE.currentScale *= scaleFactor;
    		
		    var vp_width = ZE.viewport.width;
		    var vp_height = ZE.viewport.height;
    		
		    // Very complicated calculations :)
		    // Borrowed from http://jwatt.org/svg/tests/zoom-and-pan-controls.svg
		    ZE.currentTranslate.x = vp_width/2  - (ZE.currentScale/oldScale) * (vp_width/2  - cur.oldTranslateX);
		    ZE.currentTranslate.y = vp_height/2 - (ZE.currentScale/oldScale) * (vp_height/2 - cur.oldTranslateY);
    	
		    cur.timePrev = timeNow;
	     }
    }

    this.zoom1x=function() {
        ensureControl();
	    _zoomElt.currentScale = 1;//(1.0);
	    _zoomElt.currentTranslate.x = 0;//(1.0);
	    _zoomElt.currentTranslate.y = 0;//(1.0);
    }

    this.zoomIn=function() {
	    ensureControl();
	    this.zoomIt(0.23)
    }

    this.zoomOut=function() {
	    ensureControl();
	    this.zoomIt(-0.23)
    }
	
	// Private methods
	function addEvents(){
	    _zoomElt.addEventListener('mousedown',  handleMouseDown, false);
	    _zoomElt.addEventListener('mouseup',    handleMouseUp, false);
	    _zoomElt.addEventListener('mousemove',  handleMouseMove, false);
	    _zoomElt.addEventListener('mousewheel', handleMouseWheel, false);
	    _zoomElt.addEventListener('keyup',      handleKeyUp, false);
	    //_zoomElt.addEventListener('DOMMouseScroll', handleMouseWheel, false);// mousewheel for Firefox
	}
	
	function cancelEvent(evt){
        if(!evt)return;
        if(evt.preventDefault){
	        evt.preventDefault();
        }
        evt.returnValue = false;
	}
	
	function ensureControl(){
	    if(!_initialized)throw new Error("ZoomNPan: control has not been initialized.");
	}

    function makeViewBox(SVGElt) {
	    var doc  = SVGElt || _zoomElt; // to be able to use this function  separately
		var w  = parseFloat(doc.viewport.width) || 0;
		var h  = parseFloat(doc.viewport.height) || 0;
		var wAttr = doc.getAttributeNS(null, "width");
		var hAttr = doc.getAttributeNS(null, "height");
		var vB    = doc.viewBox.baseVal;//SVGRoot.getAttributeNS(null, "viewBox");

	    if (!wAttr && !hAttr && !(vB && vB.width)) { 
		    // If there are NO width & height & viewbox try to guess them
		    var BBox = doc.getBBox();
		    vB.x = BBox.x;
		    vB.y = BBox.y;
		    vB.width = BBox.width;
		    vB.height = BBox.height;
	    }
	    else if ((wAttr || hAttr) && !vB.width) {
	    // If there IS width or height and NO viewBox,
	    // generate viewbox based on them
		    vB.x = doc.x.baseVal.value;
		    vB.y = doc.y.baseVal.value;
		    vB.width  = w || doc.width.baseVal.value;
		    vB.height = h || doc.height.baseVal.value;
    		
		    doc.removeAttributeNS(null, 'width');
		    doc.removeAttributeNS(null, 'height');
	    }
	    else if((wAttr || hAttr) && vB.width){
	    // If there are ALL OF width/height/viewBox
	    // only remove width and height attibutes
	    // or opera won't scale to fit the image
			    doc.removeAttributeNS(null, 'width');
			    doc.removeAttributeNS(null, 'height');
	    }
    	
	    return doc;
    }
    
    function resetState(){
	    //if (!_currentState.zoomAllowed){
		    _zoomElt.style.cursor = 'default';
		    _currentState.oldTranslateX = 0;
		    _currentState.oldTranslateY = 0;
		    _currentState.state == 'none'
	    //}
    }

    function scaleImage(){
	    // Scale the image to fit screen
	    if (_options.autoFitViewport){
		    makeViewBox(_zoomElt);
	    }
    }
    
    // Events
    function handleMouseMove(evt) { 
        if (_currentState.zoomAllowed){
	        cancelEvent(evt);

	        if(_currentState.state == 'pan') {
		        // Pan mode
		        // get difference to previous position
		        _currentState.translateX = evt.x - _currentState.panX;
		        _currentState.translateY = evt.y - _currentState.panY;

		        evt.currentTarget.currentTranslate.x += _currentState.translateX;
		        evt.currentTarget.currentTranslate.y += _currentState.translateY;
        			
		        // Remember previous position
		        _currentState.panX = evt.x;
		        _currentState.panY = evt.y;
	        }
        }
    }
    
    function handleMouseDown(evt) {
  	    if (_currentState.zoomAllowed) {
	        cancelEvent(evt);
		    if(evt.currentTarget.tagName == "svg") {
			    // Pan mode
			    _currentState.state = 'pan';
			    _zoomElt.style.cursor = _options.cursorGrab;
			    _currentState.panX = evt.x;
			    _currentState.panY = evt.y;
		    }
	    }
    }
    
    function handleMouseUp(evt) {
        //alert('mouse released')
        if (_currentState.zoomAllowed){
            cancelEvent(evt);
            if (_currentState.state == 'pan') {
	            // Quit pan mode
	            _zoomElt.style.cursor = _options.cursorHand;//'default'
	            _currentState.state = '';
            }
        }
    }
    
    function handleMouseWheel(evt) {
        if (_currentState.zoomAllowed){
	        cancelEvent(evt);
	        var delta;
	        if (evt.wheelDelta) {
		        delta = evt.wheelDelta / 360; // Webkit, Opera
	        }
	        else{
		        delta = evt.detail / -9; // Mozilla, Opera
	        }
	        if (evt.altKey){
		        delta *= _options.fastZoomMultiplier;
	        }
	        _this.zoomIt(delta);
        }
    }
    
    function handleKeyUp(evt) {
	    if (evt.keyCode != _options.toggleZoomKeyCode) {
		    return
	    }
        // Global killswitch :) 
        // Toggle zoom and pan when toggle key is pressed (default "z")
        _currentState.zoomAllowed = !_currentState.zoomAllowed;
        if (!_currentState.zoomAllowed){
            resetState();
        }
    }
    
    function handleMouseOut(evt){
        if (_currentState.zoomAllowed){
        // not used, buggy
        // Handle mouse out event
	        cancelEvent(evt);
	        if (_currentState.state == 'pan') {
		        // Quit pan mode
		        _currentState.state = '';
	        }
        }
    }
}
D41d8cd98f00b204e9800998ecf8427e

lomax

September 13, 2010, September 13, 2010 20:53, permalink

1 rating. Login to rate!

The way you've bound the event listeners, Z is equivalent to the keyword 'this' in every single one of your prototype methods. Try "alert(this==Z)" in one of your methods to verify. The reason I suspect you got confused was because of the scope change during your anonymous function event rebind.

for example,

this.zoomElt.addEventListener('mousedown', function(evt){self.handleMouseDown (evt, self)}, false);

can be changed without semantic loss to

this.zoomElt.addEventListener('mousedown', function(evt){self.handleMouseDown (evt)}, false);

because you've already established the link to 'this'/self/Z by calling the bound instance method self.handleMouseDown; this means that inside handleMouseDown, the keyword 'this' will refer to whatever 'self' refers to in the anonymous function, and can supplant the reference to 'Z'.

However, I would go even further, and suggest that you bind pointer references, instead of anonymous functions. I also suspect that this class is instantiated perhaps once or twice per user experience, not the thousand+ times that would offer a performance benefit in sharing a single prototype method over an instance method. Therefore, I would suggest refactoring your class in a more encapsulated manner as so:

*(Note that this is clearly untested, I hope I didn't miss any public/private conversions; I also had to make some guesses about which methods needed to be public to consume the class programmatically, and which were artifacts of using the prototype pattern; If I guessed incorrectly, simply follow the pattern and adjust accordingly)

/**
 *  SVGZoomNPan library 1.0
 * ========================
 *
 */
var ZoomNPan=function(){
	//private members
	var _currentState;
	var _initialized;
	var _options;
	var _this;
	var _zoomElt;
	
	//public members
    this.currentState;
	this.options;
	
	// ctor
	function ZoomNPan(zoomElt){
	    // Guard parameters
	    if(zoomElt != '[object SVGSVGElement]') {
		    console.log('Argument passed is not an SVG element');
		    return;
	    }
	    // Set defaults/private members
        _currentState={
		    state: 'none',
		    panX: 0,
		    panY: 0,
		    translateX: 0,
		    translateY: 0,
		    oldTranslateX: 0,
		    oldTranslateY: 0,
		    zoomAllowed: _options.autoRun,
		    timePrev: 0
	    };
	    _options={
		    autoRun:true,
		    autoFitViewport:true, 
		    zoomScale:1.2,
		    fastZoomMultiplier: 5,
		    toggleZoomKeyCode: 27,
		    cursorGrab: ' move',
		    cursorHand: ' default'
	    };
	    _this=this;
	    _zoomElt=zoomElt;
	    
	    // Assign public members
	    //      NOTE: omit these public assignments if you don't really need them exposed;
	    //            the class no longer uses them internally. Also delete them in the 
	    //            public member placeholder assignment above; I can't tell your intended 
	    //            usage from the class definition.
	    this.zoomElt=_zoomElt;
	    this.currentState=_currentState;
        this.options=_options;
	
	    addEvents();
	    scaleImage();
	    
	    _initialized=true;
	}
	// Call ctor, preserving instantiation scope; equivalent to your this.init() call;
	ZoomNPan.apply(this,arguments); 
	
	// Public methods
	//      NOTE: I made best guesses about which methods actually need to be exposed.
	//            If I guessed wrong, simply follow the pattern to expose more/less as needed.

    this.zoomIt=function (delta) {
	    ensureControl();
	    var timeNow = Date.now();
	    if (timeNow - _currentState.timePrev > 0) { // skip unnecessary redraw
		    var cur = _currentState;
		    var ZE = _zoomElt;
		    var oldScale = ZE.currentScale;
		    var scaleFactor = Math.pow(1 + _options.zoomScale, delta);
    		
		    // Remember translate before zooming
		    cur.oldTranslateX = ZE.currentTranslate.x;
		    cur.oldTranslateY = ZE.currentTranslate.y;

		    ZE.currentScale *= scaleFactor;
    		
		    var vp_width = ZE.viewport.width;
		    var vp_height = ZE.viewport.height;
    		
		    // Very complicated calculations :)
		    // Borrowed from http://jwatt.org/svg/tests/zoom-and-pan-controls.svg
		    ZE.currentTranslate.x = vp_width/2  - (ZE.currentScale/oldScale) * (vp_width/2  - cur.oldTranslateX);
		    ZE.currentTranslate.y = vp_height/2 - (ZE.currentScale/oldScale) * (vp_height/2 - cur.oldTranslateY);
    	
		    cur.timePrev = timeNow;
	     }
    }

    this.zoom1x=function() {
        ensureControl();
	    _zoomElt.currentScale = 1;//(1.0);
	    _zoomElt.currentTranslate.x = 0;//(1.0);
	    _zoomElt.currentTranslate.y = 0;//(1.0);
    }

    this.zoomIn=function() {
	    ensureControl();
	    this.zoomIt(0.23)
    }

    this.zoomOut=function() {
	    ensureControl();
	    this.zoomIt(-0.23)
    }
	
	// Private methods
	function addEvents(){
	    _zoomElt.addEventListener('mousedown',  handleMouseDown, false);
	    _zoomElt.addEventListener('mouseup',    handleMouseUp, false);
	    _zoomElt.addEventListener('mousemove',  handleMouseMove, false);
	    _zoomElt.addEventListener('mousewheel', handleMouseWheel, false);
	    _zoomElt.addEventListener('keyup',      handleKeyUp, false);
	    //_zoomElt.addEventListener('DOMMouseScroll', handleMouseWheel, false);// mousewheel for Firefox
	}
	
	function cancelEvent(evt){
        if(!evt)return;
        if(evt.preventDefault){
	        evt.preventDefault();
        }
        evt.returnValue = false;
	}
	
	function ensureControl(){
	    if(!_initialized)throw new Error("ZoomNPan: control has not been initialized.");
	}

    function makeViewBox(SVGElt) {
	    var doc  = SVGElt || _zoomElt; // to be able to use this function  separately
		var w  = parseFloat(doc.viewport.width) || 0;
		var h  = parseFloat(doc.viewport.height) || 0;
		var wAttr = doc.getAttributeNS(null, "width");
		var hAttr = doc.getAttributeNS(null, "height");
		var vB    = doc.viewBox.baseVal;//SVGRoot.getAttributeNS(null, "viewBox");

	    if (!wAttr && !hAttr && !(vB && vB.width)) { 
		    // If there are NO width & height & viewbox try to guess them
		    var BBox = doc.getBBox();
		    vB.x = BBox.x;
		    vB.y = BBox.y;
		    vB.width = BBox.width;
		    vB.height = BBox.height;
	    }
	    else if ((wAttr || hAttr) && !vB.width) {
	    // If there IS width or height and NO viewBox,
	    // generate viewbox based on them
		    vB.x = doc.x.baseVal.value;
		    vB.y = doc.y.baseVal.value;
		    vB.width  = w || doc.width.baseVal.value;
		    vB.height = h || doc.height.baseVal.value;
    		
		    doc.removeAttributeNS(null, 'width');
		    doc.removeAttributeNS(null, 'height');
	    }
	    else if((wAttr || hAttr) && vB.width){
	    // If there are ALL OF width/height/viewBox
	    // only remove width and height attibutes
	    // or opera won't scale to fit the image
			    doc.removeAttributeNS(null, 'width');
			    doc.removeAttributeNS(null, 'height');
	    }
    	
	    return doc;
    }
    
    function resetState(){
	    //if (!_currentState.zoomAllowed){
		    _zoomElt.style.cursor = 'default';
		    _currentState.oldTranslateX = 0;
		    _currentState.oldTranslateY = 0;
		    _currentState.state == 'none'
	    //}
    }

    function scaleImage(){
	    // Scale the image to fit screen
	    if (_options.autoFitViewport){
		    makeViewBox(_zoomElt);
	    }
    }
    
    // Events
    function handleMouseMove(evt) { 
        if (_currentState.zoomAllowed){
	        cancelEvent(evt);

	        if(_currentState.state == 'pan') {
		        // Pan mode
		        // get difference to previous position
		        _currentState.translateX = evt.x - _currentState.panX;
		        _currentState.translateY = evt.y - _currentState.panY;

		        evt.currentTarget.currentTranslate.x += _currentState.translateX;
		        evt.currentTarget.currentTranslate.y += _currentState.translateY;
        			
		        // Remember previous position
		        _currentState.panX = evt.x;
		        _currentState.panY = evt.y;
	        }
        }
    }
    
    function handleMouseDown(evt) {
  	    if (_currentState.zoomAllowed) {
	        cancelEvent(evt);
		    if(evt.currentTarget.tagName == "svg") {
			    // Pan mode
			    _currentState.state = 'pan';
			    _zoomElt.style.cursor = _options.cursorGrab;
			    _currentState.panX = evt.x;
			    _currentState.panY = evt.y;
		    }
	    }
    }
    
    function handleMouseUp(evt) {
        //alert('mouse released')
        if (_currentState.zoomAllowed){
            cancelEvent(evt);
            if (_currentState.state == 'pan') {
	            // Quit pan mode
	            _zoomElt.style.cursor = _options.cursorHand;//'default'
	            _currentState.state = '';
            }
        }
    }
    
    function handleMouseWheel(evt) {
        if (_currentState.zoomAllowed){
	        cancelEvent(evt);
	        var delta;
	        if (evt.wheelDelta) {
		        delta = evt.wheelDelta / 360; // Webkit, Opera
	        }
	        else{
		        delta = evt.detail / -9; // Mozilla, Opera
	        }
	        if (evt.altKey){
		        delta *= _options.fastZoomMultiplier;
	        }
	        _this.zoomIt(delta);
        }
    }
    
    function handleKeyUp(evt) {
	    if (evt.keyCode != _options.toggleZoomKeyCode) {
		    return
	    }
        // Global killswitch :) 
        // Toggle zoom and pan when toggle key is pressed (default "z")
        _currentState.zoomAllowed = !_currentState.zoomAllowed;
        if (!_currentState.zoomAllowed){
            resetState();
        }
    }
    
    function handleMouseOut(evt){
        if (_currentState.zoomAllowed){
        // not used, buggy
        // Handle mouse out event
	        cancelEvent(evt);
	        if (_currentState.state == 'pan') {
		        // Quit pan mode
		        _currentState.state = '';
	        }
        }
    }
}

Your refactoring





Format Copy from initial code

or Cancel