// zoom.js, (C) Walter Bislin, Januar 2010 // // description and download: // http://walter.bislins.ch/doku/zoom // // dependecies: // include: <script src="x.js" type="text/javascript"></script> // include: <script src="ic.js" type="text/javascript"></script> // // History: // 01.12.2014: Improved: setTimeout replaced by requestAnimationFrame for better performance // 15.10.2014: Fix: unexpected zoom behavior on doubletabs on mobile devices // 14.10.2014: BugFix: xArgsToArray must be used when passing 'arguments' to a function which test for xArray // 13.10.2014: New: ZoomSpeed; Speed improved: using CSS Transform for zooming instead HTML position and size // 01.09.2014: BugFix; New: SizeLimit; Default = 'WindowWidth' // 29.04.2014: Global Constants moved to Zoom-Object -> Zoom.CState // 06.01.2010: Renamed CLupe -> CZoom, CLupeObj -> Zoom, removed old stuff // 24.11.2009: ZoomInit/Zoom.AutoPreload: Automatische Suche nach Zoombildern für das Preload. // ZoomDebug: Warnung, wenn gleiche ID in verschiedenen ZoomIn() verwendet wird. // Neu: NewWindowOnDblClick, ZoomWindowName, ZoomWindowFeatures // 22.05.2009: ZoomInit( path, [ file1,...,fileN ] ) oder ZoomInit( url1,...,UrlN ) // 11.12.2008: EnableDblClick neu // 15.08.2008: Relative und individuelle Positionierung des Zoombildes programmiert // 29.01.2008: Problem mit IE6 behoben (Umlaute im Kommentaren können auf best. Servern Script korrumpieren!) // 13.02.2007: Zoom-Source (small picture) no more limited to image type object // New properties AddPosX, AddPosY, ZIndex // 15.02.2007: new function ZoomInit replaces ZoomHTML // ZoomInit can be placed anywhere on the page and can have a list of zoom pics to preload // ZoomInit sets up an onload-event (see xOnLoad) to create the html objects for the Zoom // new function ZoomPics replaces ZoomPreload // ZoomPics is usefull, if url's of zoom pics is not known yet on ZoomInit // ZoomPics can be placed anywhere. it installs an onload-event (see xOnLoad) handler to preload the zoom pics // 23.12.2007: new function ZoomDebug // Self diagnosis implemented // interface var Zoom = null; // new init functions function ZoomInit() { Zoom.Init(xArgsToArray(arguments)); } function ZoomPics() { Zoom.LoadPicsOnPageLoad(xArgsToArray(arguments)); } function ZoomDebug() { Zoom.DebugOn = true; } // zoom functions function ZoomIn(aImgName,aBigImgUrl,aXOffset,aYOffset,aRelEleID) { Zoom.ZoomIn(aImgName,aBigImgUrl,aXOffset,aYOffset,aRelEleID); } function ZoomOut() { Zoom.ZoomOut(); } function ZoomEnable() { Zoom.Enable(); } function ZoomDisable() { Zoom.Disable(); } // implementation function CZoom() { if (Zoom) {return;} // only one instance allowed! // constants this.CState = { Hidden: 0, Loading: 1, ZoomIn: 2, Zoomed: 3, ZoomOut: 4 }; // public properties this.DebugOn = false; this.AutoPreload = true; this.EnableInitOnClick = false; this.EnableDblClick = true; this.ZoomWindowName = 'Zoom'; this.ZoomWindowFeatures = ''; // e.g: 'innerWidth=%w, innerHeight=%h'; this.NewWindowOnDblClick = false; this.BorderColor = 'black'; this.BorderWidth = 1; this.SizeLimit = 'Window'; this.HideSmall = false; // to hide small image during zoom this.BaseZIndex = 1; this.ZIndex = 100; this.ZoomSpeed = 300; // time to double size in ms or 0 this.TimeSpan = 650; // milli seconds this.MinTimeSpan = 150; // milli seconds this.TimerInterval = 20; // (50 fps) not used anymore, replaced by requestAnimationFrame this.GiveUpTime = 10000; // give up unsiccessful trying to zoom after exceeding GiveUpTime in ms this.TimeModifyFunc = null; this.Enabled = true; this.VAlign = 'ToMiddle'; // Top, Middle, Bottom, ToMiddle, Relative this.HAlign = 'ToCenter'; // Left, Center, Right, ToCenter, Relative this.VMargin = 0; this.HMargin = 0; this.TargetOffsetX = 0; this.TargetOffsetY = 0; this.TargetElement = ''; this.LoadText = 'Lade...'; this.ErrorText = 'Zoom-Fehler!'; this.ErrMsg = ''; this.ZoomFunctionName = 'ZoomIn'; // private properties this.AddPosX = 0; // position correcture of smallPos this.AddPosY = 0; this.SmallPosX = 0; this.SmallPosY = 0; this.SmallWidth = 0; this.SmallHeight = 0; this.PrevPosX = 0; this.PrevPosY = 0; this.PrevWidth = 0; this.PrevHeight = 0; this.InitialScrollLeft = 0; this.IsReZooming = false; this.BigPosX = 0; this.BigPosY = 0; this.BigWidth = 0; this.BigHeight = 0; this.BigNativeWidth = 0; this.BigNativeHeight = 0; this.CurrVAlign = this.VAlign; this.CurrHAlign = this.HAlign; this.CurrTargetEle = null; this.CurrTargetOffX = 0; this.CurrTargetOffY = 0; this.StartTime = 0; this.ComputedTimeSpan = this.TimeSpan; this.Timer = null; this.AnimationTimer = null; this.BigImgID = -1; this.SmallImg = null; this.WaitObj = null; this.ErrObj = null; this.ZoomImg = null; this.State = this.CState.Hidden; this.PreloadExecuted = false; this.HtmlWritten = false; this.DblClickActive = false; this.DebugDisplayed = false; this.ImgUrlList = []; this.LastWinWidth = 0; this.LastWinHeight = 0; this.LastWinResizeWidth = 0; this.LastWinResizeHeight = 0; Zoom = this; // init var me = this; this.InitExecuted = false; this.InitForced = false; function AfterPageLoad() { me.CreateHtmlObjects(); if (xArray(me.ImgUrlList) && me.ImgUrlList.length > 0) { me.Preload(me.ImgUrlList); } if (me.AutoPreload) { me.FindAndPreloadImages(); } me.InitExecuted = true; } xOnLoad(AfterPageLoad); } CZoom.prototype.Init = function(aImgUrlList) { // syntax: Init( ['url1', 'url2', ...] ) or Init( ['path', [ 'file1', 'file2', ...]] ) this.ImgUrlList = aImgUrlList; }; CZoom.prototype.ForceInit = function() { if (this.InitExecuted) {return;} this.CreateHtmlObjects(); this.InitExecuted = true; this.InitForced = true; }; CZoom.prototype.FindAndPreloadImages = function() { // searches for ZoomIn calls in all onclick handlers of all html elements of the page // and all href="javascript:ZoomIn..." in all A-tags of the page, // extracts the zoom images paths and pushes the paths to the preload list. var urlList = []; var callList = this.FindZoomFunctionCalls(); for (var i = 0; i < callList.length; i++ ) { urlList.push( this.GetPathFromZoomFunctionCall(callList[i]) ); } if (urlList.length > 0 ) { this.Preload(urlList); } } CZoom.prototype.FindZoomFunctionCalls = function() { // searches for ZoomIn calls in all onclick handlers of all html elements of the page // and all href="javascript:ZoomIn..." in all A-tags of the page, // and returns a string list of this calls. var callList = []; var el = xGetByTag( '*' ); for (var i = 0; i < el.length; i++) { var e = el[i]; // check onclick handlers of all html elements if (e.onclick) { var src = this.IsZoomFunctionCall(e.onclick); if (src != '') { callList.push(src); } } // check hrefs of all A-tags if (e.tagName == 'A') { var href = e.href || ''; if (href.indexOf('javascript:') >= 0) { var src = this.IsZoomFunctionCall(href); if (src != '') { callList.push(src); } } } } return callList; } CZoom.prototype.IsZoomFunctionCall = function( aHandler ) { var s = '' + aHandler; var p = s.indexOf( this.ZoomFunctionName ); return (p >= 0) ? s : ''; } CZoom.prototype.GetPathFromZoomFunctionCall = function( aCallStr ) { var p = aCallStr.indexOf( this.ZoomFunctionName ); if (p < 0) { return ''; } p = aCallStr.indexOf( ',', p ); var sp = aCallStr.indexOf( '\'', p ); var ep = aCallStr.indexOf( '\'', sp+1 ); return aCallStr.substring( sp+1, ep ); } CZoom.prototype.GetIdFromZoomFunctionCall = function( aCallStr ) { var p = aCallStr.indexOf( this.ZoomFunctionName ); if (p < 0) { return ''; } p = aCallStr.indexOf( '(', p ); var sp = aCallStr.indexOf( '\'', p ); var ep = aCallStr.indexOf( '\'', sp+1 ); return aCallStr.substring( sp+1, ep ); } CZoom.prototype.LoadPicsOnPageLoad = function(aImgUrlList) { var me = this; function AfterPageLoad() { if (aImgUrlList.length > 0) { me.Preload(aImgUrlList); } } xOnLoad(AfterPageLoad); }; CZoom.prototype.Preload = function(aImgUrlList) { // syntax: Preload( ['url1', 'url2', ...] ) or Preload( ['path', [ 'file1', 'file2', ...]] ) if (aImgUrlList.length == 2 && xArray(aImgUrlList[1])) { IC.PreloadImages( aImgUrlList[1], aImgUrlList[0] ); } else { IC.PreloadImages( aImgUrlList ); } this.PreloadExecuted = true; }; CZoom.prototype.Diagnose = function() { var ics = IC.GetStatus(); var s = ''; if (this.ErrMsg != '') { s += 'Errors:\n'; s += this.ErrMsg + '\n'; } if (!this.PreloadExecuted) { s += 'Warning: No images preloaded!\nUse Zoom.Init to preload the zoom images or set Zoom.AutoPreload = true.'; this.PreloadExecuted = true; } if (s == '') { if (ics != '') { s = 'Zoom Status: ok.\nBut problems with some images detected.\nCheck url\'s in Zoom.Init and ZoomIn!'; } } else { s = 'Zoom Status:\n\n' + s; } if (ics != '') { s += '\n\nIC Status (IC = Image Caching and Preload):\n' + ics + '\n' + IC.ErrorMsg; IC.ResetStatus(); } if (s != '' || !this.DebugDisplayed) { if (s == '') {s = 'Zoom Status: all fine!\n\nTo remove this message,\ndelete or comment the line with ZoomDebug(); from your script.';} alert( s ); } this.ErrMsg = ''; this.DebugDisplayed = true; }; CZoom.prototype.AddError = function( aMsg ) { this.ErrMsg += aMsg + '\n'; }; CZoom.prototype.CreateHtmlObjects = function() { var me = this; var msgFailed = 'CZoom.CreateHtmlObjects: creating Zoom HTML failed '; function OnClick() { me.ZoomOut(); } function OnDblClick() { me.NewWindow(); } var oImg = xCreateElement('img'); if (!oImg || !oImg.style) { this.AddError( msgFailed + '(xCreateElement)' ); return; } oImg.id = 'ZoomPic'; oImg.style.position = 'absolute'; oImg.style.visibility = 'hidden'; oImg.style.zIndex = this.ZIndex; if (this.BorderWidth > 0) { oImg.style.border = this.BorderWidth+'px solid '+this.BorderColor; } oImg.onclick = OnClick; if (this.EnableDblClick) { oImg.ondblclick = OnDblClick; } var oDivWait = xCreateElement('div'); var oDivError = xCreateElement('div'); var oTextWait = xCreateTextNode(this.LoadText); var oTextError = xCreateTextNode(this.ErrorText); if (!oDivWait || !oDivError || !oTextWait || !oTextError) { this.AddError( msgFailed + '(xCreateTextNode)' ); return; } xAppendChild(oDivWait,oTextWait); xAppendChild(oDivError,oTextError); oDivWait.id = 'ZoomPicWait'; oDivWait.style.position = 'absolute'; oDivWait.style.visibility = 'hidden'; oDivWait.style.zIndex = this.BaseZIndex + 1; oDivWait.style.backgroundColor = 'white'; oDivWait.style.color = 'black'; oDivWait.style.padding = '0 4px'; oDivWait.style.fontSize = '10pt'; oDivWait.style.border = '1px solid black'; // oDivError.id = 'ZoomPicError'; oDivError.style.position = 'absolute'; oDivError.style.visibility = 'hidden'; oDivError.style.zIndex = this.BaseZIndex + 1; oDivError.style.backgroundColor = 'white'; oDivError.style.color = 'black'; oDivError.style.padding = '0 4px'; oDivError.style.fontSize = '10pt'; oDivError.style.border = '1px solid black'; // Objekte als erste in body einfuegen var oElements = xGetByTag('body'); if (!oElements || oElements.length < 1) { this.AddError( msgFailed + '(no body tag found)' ); return; } var oBody = oElements[0]; if (!xHasChildNodes(oBody)) { this.AddError( msgFailed + '(no html elements in body tag found)' ); return; } oElements = xChildNodes(oBody); xInsertBefore(oBody,oDivError,oElements[0]); xInsertBefore(oBody,oDivWait,oDivError); xInsertBefore(oBody,oImg,oDivWait); this.WaitObj = oDivWait; this.ErrObj = oDivError; this.ZoomImg = oImg; this.HtmlWritten = true; }; CZoom.prototype.ReZoom = function( ) { this.PrevPosX = this.BigPosX; this.PrevPosY = this.BigPosY; this.PrevWidth = this.BigWidth; this.PrevHeight = this.BigHeight; this.IsReZooming = true; this.ComputeZoomParametersAndTriggerZoom(); } CZoom.prototype.ZoomIn = function(aImgName, aBigImgUrl, aXOffset, aYOffset, aRelEleID ) { this.ResetTimer(); // fallback when xOnLoad failed: if (!this.InitExecuted) { if (this.EnableInitOnClick) { this.AddError( 'ZoomIn: Zoom not initialized - forcing init now!\nCheck Zoom.Init and ensure no onload is in body tag!' ); this.ForceInit(); if (!this.HtmlWritten) { this.AddError( 'ZoomIn: forced Init failed, give up here.' ); return; } } else { if (this.DebugOn) { this.AddError( 'ZoomIn: Zoom not initialized!\nCheck Zoom.Init and ensure no onload is in body tag\nor set Zoom.EnableInitOnClick = true;' ); this.Diagnose(); } return; } } if (this.DebugOn) {this.Diagnose();} if (!this.Enabled) {return;} if (!this.WaitObj) {this.WaitObj = xGet('ZoomPicWait');} if (!this.ErrObj) {this.ErrObj = xGet('ZoomPicError');} if (!this.ZoomImg) {this.ZoomImg = xGet('ZoomPic');} this.HideMessages(); var bigImgID = IC.FindImage(aBigImgUrl); // cancel zoom shortly after doubleclick to prevent double zoom if (this.EnableDblClick && this.DblClickActive) { return; } // init current target position and alignment this.CurrVAlign = this.VAlign; this.CurrHAlign = this.HAlign; this.CurrTargetEle = null; this.CurrTargetOffX = this.TargetOffsetX; this.CurrTargetOffY = this.TargetOffsetY; if (this.TargetElement != '') { var relEle = xGet(this.TargetElement); if (relEle) { this.CurrTargetEle = relEle; } } // overwrite global settings with local arguments if (xNum(aXOffset) || xNum(aYOffset) || xStr(aRelEleID)) { this.CurrVAlign = 'Relative'; this.CurrHAlign = 'Relative'; this.CurrTargetEle = null; this.CurrTargetOffX = 0; this.CurrTargetOffY = 0; } if (xNum(aXOffset)) {this.CurrTargetOffX = aXOffset;} if (xNum(aYOffset)) {this.CurrTargetOffY = aYOffset;} if (xStr(aRelEleID)) { var relEle = xGet(aRelEleID); if (relEle) {this.CurrTargetEle = relEle;} } if ((this.State != this.CState.Hidden) && (bigImgID != -1) && (bigImgID == this.BigImgID)) { if (this.State == this.CState.Loading) { this.State = this.CState.Hidden; return; } if (this.State == this.CState.ZoomIn || this.State == this.CState.Zoomed) { this.ZoomOut(); return; } // this.State == this.CState.ZoomOut -> stop zoom out and rezoom this.StartTime = xTimeMS() - this.ComputedTimeSpan + (xTimeMS() - this.StartTime); this.State = this.CState.ZoomIn; var me = this; // closure -> http://walter.bislins.ch/lexi/closure.html this.SetTimer( function(){me.Enlarge();}, this.TimerInterval ); return; } if (this.State == this.CState.Loading) { this.State = this.CState.Hidden; } else if (this.State != this.CState.Hidden) { this.HideZoomImg(); } this.SmallImg = xGet(aImgName); if (!this.SmallImg) { if (this.DebugOn) alert( 'ZoomIn: Nonexistent Element. ID = ' + aImgName ); return; } if ((bigImgID != -1) && IC.IsLoaded(bigImgID)) { this.BigImgID = bigImgID; this.StartZoom(); } else { // bigImgID is not loaded or unknown (-1) this.ShowLoadingMessage(); if (bigImgID != -1 && IC.ImageState(bigImgID) >= IC.CState.Error) { // error on loading bigImgID this.ShowErrorMessage(); } else { // bigImgID not preloaded or load pending this.State = this.CState.Loading; var me = this; this.BigImgID = IC.LoadImage( aBigImgUrl, function(aImgID){me.OnLoad(aImgID);} ); // handle timeout var me = this; this.SetTimer( function(){ me.ClearTimer(); me.State = me.CState.Hidden; // cancel OnLoad() me.ShowErrorMessage(); }, this.GiveUpTime ); } } }; CZoom.prototype.RequestAnimationFrame = function( fn ) { this.ResetTimer(); this.AnimationTimer = requestAnimationFrame( fn ); } CZoom.prototype.CancelAnimationFrame = function() { if (this.AnimationTimer) { cancelAnimationFrame( this.AnimationTimer ); this.AnimationTimer = null; } } CZoom.prototype.SetTimer = function( fn, t ) { this.ResetTimer(); this.Timer = setTimeout( fn, t ); } CZoom.prototype.ResetTimer = function() { if (this.Timer) { clearTimeout(this.Timer); this.Timer = null; } this.CancelAnimationFrame(); } CZoom.prototype.ShowLoadingMessage = function() { this.GetSmallImgData(); var x = this.SmallPosX + 0.5 * this.SmallWidth - 0.5 * xWidth(this.WaitObj); var y = this.SmallPosY + 0.5 * this.SmallHeight - 0.5 * xHeight(this.WaitObj); xMoveTo( this.WaitObj, x, y ); var x = this.SmallPosX + 0.5 * this.SmallWidth - 0.5 * xWidth(this.ErrObj); var y = this.SmallPosY + 0.5 * this.SmallHeight - 0.5 * xHeight(this.ErrObj); xMoveTo( this.ErrObj, x, y ); xShow( this.WaitObj ); } CZoom.prototype.ShowErrorMessage = function() { xHide( this.WaitObj ); xShow( this.ErrObj ); var me = this; // closure -> http://walter.bislins.ch/lexi/closure.html this.SetTimer( function(){xHide(me.ErrObj);}, 2500 ); } CZoom.prototype.HideMessages = function() { xHide( this.WaitObj ); xHide( this.ErrObj ); } CZoom.prototype.HideZoomImg = function() { if (this.HideSmall) xShow( this.SmallImg ); xHide( this.ZoomImg ); // make zoom image small to prevent unexpected browser behavior on mobile devices xTransformTranslateScale( this.ZoomImg, 0, 0, 10, 10, this.BigNativeWidth, this.BigNativeHeight ); this.ZoomImg.src = ''; this.State = this.CState.Hidden; }; CZoom.prototype.Enable = function() { this.Enabled = true; }; CZoom.prototype.Disable = function() { this.Enabled = false; }; CZoom.prototype.GetSmallImgData = function() { this.SmallWidth = xWidth(this.SmallImg) + 2*this.BorderWidth; this.SmallHeight = xHeight(this.SmallImg) + 2*this.BorderWidth; // correct for borders this.SmallPosX = xPageX(this.SmallImg) + (xWidth(this.SmallImg)-this.SmallWidth)/2 + this.AddPosX; this.SmallPosY = xPageY(this.SmallImg) + (xHeight(this.SmallImg)-this.SmallHeight)/2 + this.AddPosY; }; CZoom.prototype.OnLoad = function( aImgID ) { this.ResetTimer(); if ((this.State == this.CState.Loading) && (this.BigImgID == aImgID)) { var imgState = IC.Image(aImgID).CacheState; if (imgState == IC.CState.Loaded) { this.StartZoom(); } else if (imgState == IC.CState.Error || imgState == IC.CState.Abort) { this.State = this.CState.Hidden; this.ShowErrorMessage(); } } }; CZoom.prototype.Range = function( aValue, aMin, aMax ) { return aMin + (aMax-aMin)*aValue; }; CZoom.prototype.StartZoom = function() { var me = this; function go() { me.ResetTimer(); me.HideMessages(); me.ZoomImg.onload = null; me.ComputeZoomParametersAndTriggerZoom(); } function TryAgain() { this.ResetTimer(); if (me.ZoomImg.complete) { go(); } if (xTimeMS() > me.StartTime + this.GiveUpTime) { // give up me.ShowErrorMessage(); return; } me.SetTimer( TryAgain, 100 ); } this.BigImg = IC.Image(this.BigImgID); this.ZoomImg.onload = function() { me.ZoomImg.onload = null; me.SetTimer( go, 100 ); }; this.ZoomImg.src = this.BigImg.src; if (this.ZoomImg.complete) { this.ZoomImg.onload = null; this.SetTimer( go, 100 ); return; } // fallback this.ShowLoadingMessage(); this.StartTime = xTimeMS(); this.SetTimer( TryAgain, 100 ); }; CZoom.prototype.ComputeZoomParametersAndTriggerZoom = function() { this.BigWidth = this.BigImg.width + 2*this.BorderWidth; this.BigHeight = this.BigImg.height + 2*this.BorderWidth; this.BigNativeWidth = this.BigWidth; this.BigNativeHeight = this.BigHeight; this.GetSmallImgData(); if (!this.IsReZooming) { if ((this.SmallWidth >= this.BigWidth) || (this.SmallHeight >= this.BigHeight)) {return;} } // assert: this.BigImg is greater than this.SmallImg var winW = xClientWidth(); var winH = xClientHeight(); var winX = xScrollLeft(); var winY = xScrollTop(); if (this.LastWinResizeWidth != winW || this.LastWinResizeHeight != winH) { this.LastWinResizeWidth = winW; this.LastWinResizeHeigth = winH; xTriggerEventLayoutChange(); } // keep initial scroll left position when rezooming. // This forces Zoom Images to realign into page range when device orientation is altered multiple times. if (this.IsReZooming) { winX = this.InitialScrollLeft; } else { this.InitialScrollLeft = winX; } var clW = winW; var clX = winX; var clH = winH; var clY = winY; var limitEle = null; // limit size if (this.SizeLimit != '') { var maxWidth = this.BigWidth; var maxHeight = this.BigHeight; var ratio = this.BigWidth / this.BigHeight; if (this.SizeLimit == 'Window') { if (maxWidth > winW) maxWidth = winW; if (maxHeight > winH) maxHeight = winH; } else if (this.SizeLimit == 'WindowWidth') { if (maxWidth > winW) maxWidth = winW; } else if (this.SizeLimit == 'WindowHeight') { if (maxHeight > winH) maxHeight = winH; } else if (this.SizeLimit == 'WindowFill') { if (winW / winH > ratio) { if (maxWidth > winW) maxWidth = winW; } else { if (maxHeight > winH) maxHeight = winH; } } else { limitEle = xGet( this.SizeLimit ); if (limitEle) { clX = xPageX(limitEle); clY = xPageY(limitEle); clW = xWidth(limitEle); clH = xHeight(limitEle); if (maxWidth > clW) maxWidth = clW; if (maxHeight > clH) maxHeight = clH; if (maxWidth > winW) maxWidth = winW; if (maxHeight > winH) maxHeight = winH; // restrict client range into window range var clXX = clX + clW; if (clXX > winX+winW) clXX = winX + winW; if (clX < winX) clX = winX; clW = clXX - clX; var clYY = clY + clH; if (clYY > winY+winH) clYY = winY + winH; if (clY < winY) clY = winY; clH = clYY - clY; } } if (maxWidth == 0) maxWidth = winW; if (maxHeight == 0) maxHeight = winH; if (this.BigWidth > maxWidth) { this.BigWidth = maxWidth; this.BigHeight = this.BigWidth / ratio; } if (this.BigHeight > maxHeight) { this.BigHeight = maxHeight; this.BigWidth = this.BigHeight * ratio; } } if (this.CurrHAlign == 'Left') { this.BigPosX = clX + this.HMargin; } else if (this.CurrHAlign == 'Right') { this.BigPosX = (clX+clW) - this.BigWidth - this.HMargin; } else if (this.CurrHAlign == 'Relative') { var ref = this.SmallImg; if (this.CurrTargetEle) {ref = this.CurrTargetEle;} this.BigPosX = xPageX(ref) + this.CurrTargetOffX; } else { // compute big position: move big image according to its size rel. to the windows range // to the center of the windows range (client range). As smaller the image, as farther away // from the center. var dxCenter = 1; if (this.BigWidth <= clW) { // assert: this.BigImg width full inside client range dxCenter = (this.BigWidth - this.SmallWidth) / (clW - this.SmallWidth); if (dxCenter < 0) {dxCenter = 0;} } if (this.CurrHAlign == 'Center') {dxCenter = 1;} var cxBig = clW / 2; var cxSrc = this.SmallPosX - clX + (this.SmallWidth / 2); var cx = dxCenter * (cxBig - cxSrc) + cxSrc; this.BigPosX = clX + cx - this.BigWidth/2; if (this.BigPosX < 0) {this.BigPosX = 0;} } if (this.CurrVAlign == 'Top') { this.BigPosY = clY + this.VMargin; } else if (this.CurrVAlign == 'Bottom') { this.BigPosY = (clY+clH) - this.BigHeight - this.VMargin; } else if (this.CurrVAlign == 'Relative') { var ref = this.SmallImg; if (this.CurrTargetEle) {ref = this.CurrTargetEle;} this.BigPosY = xPageY(ref) + this.CurrTargetOffY; } else { var dyCenter = 1; if (this.BigHeight <= clH) { // assert: this.BigImg height full inside client range dyCenter = (this.BigHeight - this.SmallHeight) / (clH - this.SmallHeight); if (dyCenter < 0) {dyCenter = 0;} } if (this.CurrVAlign == 'Middle') {dyCenter = 1;} var cyBig = clH / 2; var cySrc = this.SmallPosY - clY + (this.SmallHeight / 2); var cy = dyCenter * (cyBig - cySrc) + cySrc; this.BigPosY = clY + cy - this.BigHeight/2; if (this.BigPosY < 0) {this.BigPosY = 0;} } // move big image into window range if ((this.BigPosX+this.BigWidth) > (winX+winW)) {this.BigPosX = (winX+winW) - this.BigWidth;} if ((this.BigPosX) < (winX) ) {this.BigPosX = winX;} if ((this.BigPosY+this.BigHeight) > (winY+winH)) {this.BigPosY = (winY+winH) - this.BigHeight;} if ((this.BigPosY) < (winY) ) {this.BigPosY = winY;} // try to center big image in window if it is bigger than window if (this.BigWidth > winW) { var winC = winX + winW / 2; this.BigPosX = winC - this.BigWidth / 2; if (this.BigPosX < 0) this.BigPosX = 0; } if (this.BigHeight > winH) { var winC = winY + winH / 2; this.BigPosY = winC - this.BigHeight / 2; if (this.BigPosY < 0) this.BigPosY = 0; } // trigger zoom this.ComputeTimeSpan(); this.StartTime = xTimeMS(); var me = this; // closure -> http://walter.bislins.ch/lexi/closure.html this.RequestAnimationFrame( function(){me.Enlarge();} ); }; CZoom.prototype.ComputeTimeSpan = function() { if (this.ZoomSpeed > 0) { var ref = this.SmallHeight; if (ref < 1) ref = 1; var scaleW = this.BigHeight / ref; var scaleX = 2 * ((this.BigPosX + this.BigWidth/2) - (this.SmallPosX + this.SmallWidth/2)) / ref; var scaleY = 2 * ((this.BigPosY + this.BigHeight/2) - (this.SmallPosY + this.SmallHeight/2)) / ref; var scale = Math.sqrt( scaleW * scaleW + scaleX * scaleX + scaleY * scaleY ); if (scale < 1) scale = 1; var time = (Math.log(scale) / Math.log(2)) * (this.ZoomSpeed/1000); // time follows curve t/(t^4+1)^(1/4) which is streched to belong in the range of MinTimeSpan to TimeSpan var deltaSpan = (this.TimeSpan - this.MinTimeSpan) / 1000; time = time / Math.pow( Math.pow( time / deltaSpan , 4) + 1 , 0.25 ) + (this.MinTimeSpan/1000); this.ComputedTimeSpan = Math.round( 1000 * time ); } else { this.ComputedTimeSpan = this.TimeSpan; } } CZoom.prototype.Enlarge = function() { this.ResetTimer(); if (this.DblClickActive) return; // note ComputeTimeSpan() is called before var param = (xTimeMS() - this.StartTime) / this.ComputedTimeSpan; var eom = param >= 1; if (param > 1) {param = 1;} if (this.TimeModifyFunc) {param = this.TimeModifyFunc(param);} if (param < 0) {param = 0;} if (param > 1) {param = 1;} if (this.IsReZooming) { var srcX = this.PrevPosX; var srcY = this.PrevPosY; var srcW = this.PrevWidth; var srcH = this.PrevHeight; } else { var srcX = this.SmallPosX; var srcY = this.SmallPosY; var srcW = this.SmallWidth; var srcH = this.SmallHeight; } var x = this.Range( param, srcX, this.BigPosX ); var y = this.Range( param, srcY, this.BigPosY ); var w = this.Range( param, srcW, this.BigWidth ); var h = this.Range( param, srcH, this.BigHeight ); xTransformTranslateScale( this.ZoomImg, x, y, w, h, this.BigNativeWidth, this.BigNativeHeight, true ); if (this.State != this.CState.ZoomIn) { this.HideMessages(); xShow( this.ZoomImg ); if (this.HideSmall) {xHide( this.SmallImg );} this.State = this.CState.ZoomIn; } var me = this; if (eom) { this.State = this.CState.Zoomed; this.IsReZooming = false; this.SetTimer( function(){me.MonitorOutOfWindowOrResize();}, 200 ); } else { this.RequestAnimationFrame( function(){me.Enlarge();} ); } }; CZoom.prototype.MonitorWindowResize = function() { var me = this; this.ResetTimer(); var winW = xClientWidth(); var winH = xClientHeight(); if (this.LastWinResizeWidth != winW || this.LastWinResizeHeight != winH) { this.LastWinResizeWidth = winW; this.LastWinResizeHeigth = winH; xTriggerEventLayoutChange(); } // check if window size changed if (winW != this.LastWinWidth || winH != this.LastWinHeight) { this.LastWinWidth = winW; this.LastWinHeight = winH; this.SetTimer( function(){me.MonitorWindowResize();}, 500 ); return; } this.LastWinWidth = 0; this.LastWinHeight = 0; if (this.BigImgID >= 0) { this.ReZoom(); } } CZoom.prototype.MonitorOutOfWindowOrResize = function() { var me = this; this.ResetTimer(); var winW = xClientWidth(); var winH = xClientHeight(); if (this.LastWinResizeWidth != winW || this.LastWinResizeHeight != winH) { this.LastWinResizeWidth = winW; this.LastWinResizeHeigth = winH; xTriggerEventLayoutChange(); } // check if window size changed if (this.LastWinWidth == 0 || this.LastWinHeight == 0) { this.LastWinWidth = winW; this.LastWinHeight = winH; } else if (winW != this.LastWinWidth || winH != this.LastWinHeight) { this.MonitorWindowResize(); return; } // check if zoom is out of window var space = (winH - this.BigHeight) / 2; var newY = xScrollTop() + space; var toleranz; if (space > 0) { toleranz = space + (this.BigHeight * 2 / 3); } else { toleranz = -space + (winH * 2 / 3); } if (Math.abs(newY-this.BigPosY) > toleranz) { this.ZoomOut(); return; } this.SetTimer( function(){me.MonitorOutOfWindowOrResize();}, 200 ); }; CZoom.prototype.NewWindow = function() { function CancelDblClick() { me.DblClickActive = false; } if (!this.HtmlWritten) {return;} // prevent zoom on double click var me = this; if (!this.DblClickActive) { this.DblClickActive = true; setTimeout( function(){ CancelDblClick(); }, 500 ); } this.HideZoomImg(); if (this.NewWindowOnDblClick) { var features = this.ZoomWindowFeatures; features = features.replace( /%w/gi, this.BigWidth.toString() ); features = features.replace( /%h/gi, this.BigHeight.toString() ); var w = window.open( IC.ImageUrl(this.BigImgID), this.ZoomWindowName, features ); } else { location.href = IC.ImageUrl(this.BigImgID); } }; CZoom.prototype.ZoomOut = function() { this.IsReZooming = false; if (!this.HtmlWritten) {return;} if (this.State == this.CState.Hidden || this.State == this.CState.ZoomOut) {return;} if (this.State == this.CState.Loading) { this.HideMessages(); this.State = this.CState.Hidden; return; } this.ResetTimer(); this.GetSmallImgData(); this.SmallPosX = xPageX(this.SmallImg) + (xWidth(this.SmallImg)-this.SmallWidth)/2 + this.AddPosX; // Raender beruecksichtigen this.SmallPosY = xPageY(this.SmallImg) + (xHeight(this.SmallImg)-this.SmallHeight)/2 + this.AddPosY; if (this.State == this.CState.ZoomIn) { this.StartTime = xTimeMS() - this.ComputedTimeSpan + (xTimeMS() - this.StartTime); } else { this.StartTime = xTimeMS(); } this.ComputeTimeSpan(); this.State = this.CState.ZoomOut; var me = this; // closure -> http://walter.bislins.ch/lexi/closure.html this.RequestAnimationFrame( function(){me.Shrink();} ); }; CZoom.prototype.Shrink = function() { this.ResetTimer(); // Note: ComputeTimeSpan is called before var param = (xTimeMS() - this.StartTime) / this.ComputedTimeSpan; var eom = param >= 1; if (param > 1) {param = 1;} if (this.TimeModifyFunc) {param = this.TimeModifyFunc(param);} if (param < 0) {param = 0;} if (param > 1) {param = 1;} var x = this.Range( param, this.BigPosX, this.SmallPosX ); var y = this.Range( param, this.BigPosY, this.SmallPosY ); var w = this.Range( param, this.BigWidth, this.SmallWidth ); var h = this.Range( param, this.BigHeight, this.SmallHeight ); xTransformTranslateScale( this.ZoomImg, x, y, w, h, this.BigNativeWidth, this.BigNativeHeight, true ); if (eom) { this.HideZoomImg(); this.BigImgID = -1; this.LastWinWidth = 0; this.LastWinHeight = 0; } else { var me = this; // closure -> http://walter.bislins.ch/lexi/closure.html this.RequestAnimationFrame( function(){me.Shrink();} ); } }; Zoom = new CZoom(); Zoom.TimeModifyFunc = function( aValue ) { return (0.5 - 0.5 * Math.cos(Math.PI*aValue)); };