Instant Gallery

By Johan Sundström Last update Jun 25, 2008 — Installed 6,119 times.
// ==UserScript==
// @name           Instant Gallery
// @namespace      http://www.lysator.liu.se/~jhs/userscript
// @description    Load all pictures from a thumbnail gallery at once and navigate them with arrow keys or mouse. Images may be zoomed with 'z' and 'f', or dropped from the sequence with delete. Another trail of development, based off http://userscripts.org/scripts/show/2330 by Jos van den Oever.
// @include        *
// ==/UserScript==

/*
  Authors: Jos van den Oever ( jos@vandenoever.info,   ...2006-01-22 )
           Johan Sundstrom ( oyasumi+GMhacks@gmail.com 2006-04-07... )

  License: GPL

  Version history:
1.1 2008-06-26: transparent images get checkerboard background -- by znerp
1.0 2006-04-07: black background, 'delete' drops an image, 'f' for full screen
    2006-01-22: revert links when QuickGallery is 'closed' and test images
    2006-01-13: assigned 'z' to zoom (bugfix), improved image finding, added close button
    2005-12-02: updated the script to work with GreasMonkey 0.5.3 and newer
    2005-06-08: fixed RegExp problem and improved Opera key bindings
    2005-06-04: updated the script so that it works with Opera 8
    2005-06-03: added support for normal links to images
    2005-06-02: 90% rewrite. Added images as overlay, tooltip, and zooming.
    2005-06-01: Initial write.

  Possible future enhancements:
    - allow for delayed loading of images by first putting a string in the array imgs and
      replacing it with an img element when the url is requested

*/

if (!document.contentType.match(/html/i))
  return;

window.addEventListener("load", function(ev) {
  if (typeof imgs != "object") return;
  for (var i = 0; i < imgs.length; i++)
    imgs[i] = null;
  imgs = [];
}, false);
window.addEventListener("load", function(ev) {
var i; // define variable that remembers the current image position

var zoom = 0; // the variable holding the state of the zoom toggle

var full = 0; // full screen zoom mode?

var black = true; // whether to black out the background when browsing

var quickgallery_on = true;

var overflow = getComputedStyle( document.body, '' ).overflow;

var dialog_initialized = false;

// function definitions

var setVisible = function(img, mouseevent) {
  document.body.style.overflow = 'hidden';
  i = img.n;
  if( mouseevent && img.deleted )
    img.deleted = 0;
  img.style.display = 'block';
  img.style.position = 'absolute';
  img.style.zIndex = 100;
  doZoom(img);
  // try to show the middle of the img under the middle of the cursor
  if (mouseevent) {
    centerMouse(img, mouseevent);
  } else {
    centerScreen(img);
  }
}

// resize the image if we are in zoom mode
var doZoom = function(img) {
  if (!img.owidth) {
    img.owidth=img.width;
    img.oheight=img.height;
  }
  if (zoom) {
    var sw = window.innerWidth;
    var sh = window.innerHeight;
    if ((img.owidth/sw < img.oheight/sh) ^ (full || zoom == 2) ) {
      img.style.height = sh+"px";
      img.style.width = 'auto';
    } else {
      img.style.height = 'auto';
      img.style.width = sw+"px";
    }
  } else {
    img.style.width = 'auto';
    img.style.height = 'auto';
  }
}
// display the image as close as possible to the mouse as possible
// and also try to keep it inside of the browser window
var centerMouse = function(img, e) {
  var iw = img.width;
  var sw = window.innerWidth;
  var mx = e.pageX;
  var sx = window.pageXOffset;
  var left;
  if (iw > sw) {
    left = sx + (sw-iw)/2;
  } else {
    left = mx-iw/2;
    if (left+iw > sw+sx) {
      left = sw+sx-iw;
    } else if (left < sx) {
      left = sx;
    }
  }
  img.style.left = ((left>=0)?left:0)+"px";
  var ih = img.height;
  var sh = window.innerHeight;
  var my = e.pageY;
  var sy = window.pageYOffset;
  var top;
  if (ih > sh) {
    top = sy + (sh-ih)/2;
  } else {
    top = my-ih/2;
    if (top+ih > sh+sy) {
      top = sh+sy-ih;
    } else if (top < sy) {
      top = sy;
    }
  }
  img.style.top = ((top>=0)?top:0)+"px";
}
// display the image at the center of the screen
var centerScreen = function(img) {
  var sx = window.pageXOffset;
  var sy = window.pageYOffset;
  var sw = window.innerWidth;
  var sh = window.innerHeight;
  var iw = img.width;
  var ih = img.height;
  var top = sy + 0*(sh-ih)/2;
  var left = sx + (sw-iw)/2;
  img.style.top = top+'px';//((top>=0)?top:0)+"px";
  img.style.left = left+'px';//((left>=0)?left:0)+"px";
}
var setInvisible = function(img) {
  img.style.display='none';
}
// show the image at position pos in the imgs array
var showImg = function(pos)
{
  for(var j in imgs)
    if( j==pos && quickgallery_on )
      setVisible(imgs[j]);
    else
      setInvisible(imgs[j]);
  if( pos == imgs.length )
    document.body.style.overflow = overflow;
  if( !black || (pos == imgs.length) )
    dark.style.display = 'none';
  else
    dark.style.display = 'block';
}
// make the images clickable
var setToggle = function(a, bigimg) {
  var f1 = function(event) {
    if (quickgallery_on && a.valid) {
      setVisible(bigimg, event);
      event.preventDefault();
    }
    // return 'false' so that the hyperlink is not followed
    return !quickgallery_on;
  }
  a.addEventListener('click', f1, true);
  var f2 = function(event) {
    setInvisible(bigimg, event);
    i = imgs.length;
    event.preventDefault();
    return false;
  }
  bigimg.addEventListener('click', f2, true);
}
var first_mismatch = true;
var valid_images = 0;
var testImage = function(a, src) {
  if (src.indexOf("file:") == 0) {
    a.valid = true;
    if (++valid_images > 1) {
      showDialog();
    }
    return;
  }
  GM_xmlhttpRequest( { method:"HEAD", url:src,
  headers:{"Referer":location.href},
  onload:function(details) {
    //console.log( src, details.responseHeaders );
    var re = new RegExp('content-type:.*image/', 'i');
    if (re.test(details.responseHeaders)) {
      a.valid = true;
      if (++valid_images > 1) {
        showDialog();
      }
    }
  }
  });
}

var img_urls = {};

var addImage = function(a, src) {
  if( img_urls[src] ) return; // ignore duplicates
  img_urls[src] = 1;

  var bigimg = document.createElement('img');
  bigimg.src = src;
  bigimg.style.display = 'none';

  // make the background checkered for user feedback that the image
  // hasnt been loaded yet (and behind transparent parts)
  bigimg.style.background = 'transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGElEQVQYV2N4DwX/oYBhgARgDJjEAAkAAEC99wFuu0VFAAAAAElFTkSuQmCC") repeat scroll 0% 0%';
  /* bigimg.style.backgroundImage =
  'url("http://e0.extreme-dm.com/s9.g?login=quickgal&j=y&jv=n")';*/
  body.appendChild(bigimg);
  setToggle(a, bigimg);
  bigimg.n = imgs.length;
  imgs[imgs.length] = bigimg;
  testImage(a, src);
}

// handle user clicks: go one further
var forward = function(event) {
  i++;
  if (i>imgs.length)
    i = 0;
  if( imgs[i] && imgs[i].deleted )
    return forward( event );
  showImg(i);
}

// go one image back
var backward = function(event) {
  i--;
  if (i < 0)
    i = imgs.length;
  if( imgs[i] && imgs[i].deleted )
    return backward( event );
  showImg(i);
}

var keyHandler = function(event) {
  if (!quickgallery_on || event.target.nodeName.match(/^(textarea|input)$/i)) {
    return true;
  }
  var key = String.fromCharCode(event.which);
  var c = event.keyCode;
  /*if( event.ctrlKey && (c == 17) )
  {
    black = !black;
    showImg(i);
  }
  else*/
  if( event.shiftKey && (key == 'Z' || key == 'N') )
    ;
  else if( event.altKey || event.ctrlKey || event.shiftKey )
    return true; // ignore keys if modifiers are used
  var more = false;
  if (typeof window.opera == 'undefined') { // greasemonkey mode
    if (c == 39 || key == ' ') { // arrow right
      forward();
    } else if (c == 37 || key == 'b') { // arrow left
      backward();
    } else if (c == 27) { // escape
      showImg(imgs.length);
    } else if (c == 46) { // delete
      imgs[i].deleted = 1;
      forward();
    } else if (key == 'F') {
      full = !full;
      if( !zoom )
	zoom = 1;
      showImg(i);
    } else if (key == '+') {
      ;
    } else if (key == 'N' || key == 'Z') {
      zoom = !zoom;
//      if( event.shiftKey )
//	zoom = [2, 2, 1][zoom];
//      else
//	zoom = [1, 0, 1][zoom];
      showImg(i);
    } else {
      more = true;
    }
  } else { // opera mode
    if (key == 'm') { // arrow right
      forward();
    } else if (key == 'B') { // arrow left
      backward();
    } else if (c == 27) { // escape
      showImg(imgs.length);
    } else if (c == 46) { // delete
      setInvisible(imgs[i]);
      imgs.splice(i, 1);
      showImg(i);
    } else if (key == 'N') {
      zoom = !zoom;
      showImg(i);
    } else {
      more = true;
    }
  }
  // signal if the event needs more processing
  if (!more) {
    event.preventDefault();
  }
  return more;
}

var showDialog = function() {
  // if links to images were found, install the keyhandler and show a tooltip
  if (!dialog_initialized && imgs.length > 1) {
    dialog_initialized = true;
    // handle arrow keys an 'z' for navigation
    document.addEventListener('keydown', keyHandler, false);

    // show a note with instructions
    var div = document.createElement('div');
    div.style.display='block';
    div.style.position='fixed';
    div.style.bottom='5px';
    div.style.right='5px';
    div.style.width='15em';
    div.style.background='#FFFFFF';
    div.style.color='#000000';
    div.style.opacity='0.8';
    div.style.border='1px solid black';
    div.style.padding='1em';
    div.style.fontFamily='sans-serif';
    div.style.fontSize='smaller';
    div.style.textAlign='justify';
    var x = document.createElement('div');
    x.style.background='black';
    x.style.cssFloat = 'right';
    x.style.color = 'white';
    x.style.width='1em';
    x.style.textAlign='center';
    x.style.marginTop = '-1em';
    x.style.marginRight = '-1em';
    x.style.cursor = 'pointer';
    div.appendChild(x);
    var title = document.createElement('em');
    var msg = "Instant Gallery: ";
    title.appendChild(document.createTextNode(msg));
    div.appendChild(title);
    if (typeof window.opera == 'undefined') {
      msg = " Use the arrow keys to navigate the images and, 'z' "+
      "to zoom and 'f' for full screen.";
    } else {
      msg = " Use 'm' for next image, 'n' to zoom, and 'b' for the "
        +"previous image.";
    }
    var t = document.createTextNode(msg);
    div.appendChild(t);
    x.innerHTML = 'x';
    div.style.zIndex=50;
    // exit when one clicks on it box
    x.addEventListener('click', function() {
      quickgallery_on = false;
      body.removeChild(div);
    }, true);
    body.appendChild(div);
  }
}

// main functionality

var re = /(.*\.(jpe?g?|png|bmp|gif))$/i; // filtering on likely extensions
var re2 = /((ht|f)tp:[^&]*)[&'"]/i; // 'extracting direct links to images
var imgs = [];

// add all full-size images to the page as hidden img elements
// this loop should be fast because it is performed for _every_ webpage
var body = document.body, dark;
for( var a in document.links ) {
  a = document.links[a];
  var match = re2.exec(a.href);
  if (match == null)
    match = re.exec(a.href);
  else
    match = re.exec(match[0]);
  if (match != null) {
    addImage(a, match[0]);
    if (imgs.length > 32) {
      break;
    }
  }
}
i = imgs.length;
if( i ) {
  dark = document.createElement( 'div' );
  dark.style.top = dark.style.left = dark.style.right = dark.style.bottom='0';
  dark.style.background = '#000';
  dark.style.position = 'fixed';
  dark.style.display = 'none';
  dark.style.zIndex = 99;
  dark.id = 'darkness';
  body.appendChild( dark );
}

}, false);