WaBis

walter.bislins.ch

Datei: Zoom: zoom.js

Inhalt der Datei: javascript/zoom/src/zoom.js
// 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)); };

More Page Infos / Sitemap
Created Dienstag, 17. Juni 2014
Scroll to Top of Page
Changed Sonntag, 21. August 2016