/******************************************************************************************* * zoomify * Written by Craig Francis * Absolutely minimal version of GSIV to work with touch screens and very slow processors. ******************************************************************************************** /*global window, document, setTimeout, getComputedStyle */ /*jslint white: true */ ;(function(document, window, undefined) { 'use strict'; //-------------------------------------------------- // Variables var html_ref = null, div_ref = null, div_half_width = null, div_half_height = null, img_ref = null, img_original_width = null, img_original_height = null, img_current_width = null, img_current_height = null, img_current_left = null, img_current_top = null, zoom_control_refs = {}, zoom_levels = [], zoom_level_count = 0, zoom_limit = null, zoom_min_width = null, zoom_max_width = null, zoom_origin_coords = [], zoom_origin_distance = null, zoom_origin_width = null, move_origin_cords = null, move_origin_left = null, move_origin_top = null, click_last = 0; //-------------------------------------------------- // IE9 Bug ... if loading an iframe which is then // moved in the DOM (as done in lightboxMe, line 51), // then IE looses the reference and decides to do // an early garbage collection: // http://stackoverflow.com/q/8389261 if (typeof(Math) === 'undefined') { return false; // No need to window.reload, as IE9 will reload the page anyway. } //-------------------------------------------------- // Event listener helpers: // http://dustindiaz.com/rock-solid-addevent function addEventListener(obj, type, fn) { if (obj.addEventListener) { obj.addEventListener(type, fn, false); } else if (obj.attachEvent) { obj['e'+type+fn] = fn; obj[type+fn] = function() { obj['e'+type+fn](window.event); } obj.attachEvent('on'+type, obj[type+fn]); } else { obj['on'+type] = obj['e'+type+fn]; } } function removeEventListener(obj, type, fn) { if (obj.removeEventListener) { obj.removeEventListener(type, fn, false); } else if (obj.detachEvent) { obj.detachEvent('on'+type, obj[type+fn]); } else { obj['on'+type] = null; } } function preventDefault(e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; // IE: http://stackoverflow.com/questions/1000597/ } return false; } function preventPropagationAndDefault(e) { if (e.stopPropagation) { e.stopPropagation(); } return preventDefault(e); } function event_move_coords(e) { var coords = []; if (e.touches && e.touches.length) { coords[0] = e.touches[0].clientX; coords[1] = e.touches[0].clientY; } else { coords[0] = e.clientX; coords[1] = e.clientY; } return coords; } function event_zoom_coords(e) { var coords = [0, 0]; if (e.touches && e.touches.length >= 2) { coords[0] = ((e.touches[0].clientX + e.touches[1].clientX) / 2); coords[1] = ((e.touches[0].clientY + e.touches[1].clientY) / 2); } return coords; } function event_zoom_distance(e) { // http://stackoverflow.com/a/11183333/6632 var distance = 0; if (e.touches && e.touches.length >= 2) { distance = Math.sqrt( (e.touches[0].clientX-e.touches[1].clientX) * (e.touches[0].clientX-e.touches[1].clientX) + (e.touches[0].clientY-e.touches[1].clientY) * (e.touches[0].clientY-e.touches[1].clientY)); } return distance; } //-------------------------------------------------- // Default styles for JS enabled version html_ref = document.getElementsByTagName('html'); if (html_ref[0]) { html_ref[0].className = html_ref[0].className + ' js-enabled'; } //-------------------------------------------------- // Zooming function image_zoom_update(new_width) { //-------------------------------------------------- // At width limit var new_limit = 0, new_height = 0, ratio = 0; if (new_width == zoom_max_width) new_limit = (new_limit | 1); if (new_width == zoom_min_width) new_limit = (new_limit | 2); //-------------------------------------------------- // Hitting limit flash if (new_limit != 0 && new_limit != 3 && new_limit != zoom_limit && zoom_limit !== null) { // At a limit (not both), has just hit that limit, and isn't happening on page load. div_ref.style.opacity = 0.5; setTimeout(function() {div_ref.style.opacity = 1;}, 150); } //-------------------------------------------------- // Zoom controls if (new_limit != zoom_limit) { // Has changed if (new_limit & 1) { zoom_control_refs['in-on'].style.display = 'none'; zoom_control_refs['in-off'].style.display = 'block'; } else { zoom_control_refs['in-on'].style.display = 'block'; zoom_control_refs['in-off'].style.display = 'none'; } if (new_limit & 2) { zoom_control_refs['out-on'].style.display = 'none'; zoom_control_refs['out-off'].style.display = 'block'; } else { zoom_control_refs['out-on'].style.display = 'block'; zoom_control_refs['out-off'].style.display = 'none'; } zoom_limit = new_limit; } else if (new_limit != 0) { // No limit change, and still at limit. return false; } //-------------------------------------------------- // Change new_height = ((img_original_height / img_original_width) * new_width); if (img_current_left === null) { // Position in the middle (initial page load) img_current_left = (div_half_width - (new_width / 2)); img_current_top = (div_half_height - (new_height / 2)); } else { ratio = (new_width / img_current_width); img_current_left = (div_half_width - ((div_half_width - img_current_left) * ratio)); img_current_top = (div_half_height - ((div_half_height - img_current_top) * ratio)); } img_current_width = new_width; img_current_height = new_height; img_ref.width = img_current_width; img_ref.height = img_current_height; img_ref.style.left = img_current_left + 'px'; img_ref.style.top = img_current_top + 'px'; //-------------------------------------------------- // Success return true; } function image_zoom_event(e) { //-------------------------------------------------- // Calculations var new_width = ((event_zoom_distance(e) / zoom_origin_distance) * zoom_origin_width); if (new_width < zoom_min_width) new_width = zoom_min_width; if (new_width > zoom_max_width) new_width = zoom_max_width; //-------------------------------------------------- // Update if (image_zoom_update(new_width) === false) { // Hit resize change limit, so reset reference points (i.e. zoom in to limit, keep going, then reverse... it should immediately start scaling again) zoom_origin_distance = event_zoom_distance(e); zoom_origin_width = new_width; } //-------------------------------------------------- // Prevent default return preventDefault(e); } function image_zoom_change(change) { //-------------------------------------------------- // Zoom level var current_zoom = 0, new_zoom = null, new_width = null, k = 0; for (k = zoom_level_count; k >= 0; k--) { if (zoom_levels[k] <= img_current_width) { current_zoom = k; break; } } //-------------------------------------------------- // New zoom/width new_zoom = (current_zoom + change); if (new_zoom < 0) new_zoom = 0; if (new_zoom > zoom_level_count) new_zoom = zoom_level_count; new_width = zoom_levels[new_zoom]; //-------------------------------------------------- // Update position image_zoom_update(new_width); } function image_zoom_in(e) { image_zoom_change(1); return preventPropagationAndDefault(e); } function image_zoom_out(e) { image_zoom_change(-1); return preventPropagationAndDefault(e); } function scroll_event(e) { var wheelData = 0; if (e.wheelDelta) wheelData = e.wheelDelta / -40; if (e.deltaY) wheelData = e.deltaY; if (e.detail) wheelData = e.detail; image_zoom_change(wheelData > 0 ? -1 : 1); return preventPropagationAndDefault(e); } //-------------------------------------------------- // Move function image_move_update(new_left, new_top) { //-------------------------------------------------- // Boundary check var max_left = (div_half_width - img_current_width), max_top = (div_half_height - img_current_height); if (new_left > div_half_width) { new_left = div_half_width; } if (new_top > div_half_height) { new_top = div_half_height; } if (new_left < max_left) { new_left = max_left; } if (new_top < max_top) { new_top = max_top; } //-------------------------------------------------- // Update img_current_left = new_left; img_current_top = new_top; img_ref.style.left = img_current_left + 'px'; img_ref.style.top = img_current_top + 'px'; } function image_move_event(e) { //-------------------------------------------------- // Calculations var new_cords = event_move_coords(e), new_left = (move_origin_left + (new_cords[0] - move_origin_cords[0])), new_top = (move_origin_top + (new_cords[1] - move_origin_cords[1])); image_move_update(new_left, new_top); //-------------------------------------------------- // Prevent default return preventDefault(e); } //-------------------------------------------------- // Image events function image_event_start(e) { //-------------------------------------------------- // End current events... on zoom, typically 1 // finger goes down first (move), and we need to // cleanup before starting a 2 finger zoom. image_event_end(); //-------------------------------------------------- // Event if (e.type === 'touchstart' && e.touches.length == 2) { // zoom mode //-------------------------------------------------- // Starting position zoom_origin_coords = event_zoom_coords(e); zoom_origin_distance = event_zoom_distance(e); zoom_origin_width = img_current_width; //-------------------------------------------------- // Events addEventListener(img_ref, 'touchmove', image_zoom_event); addEventListener(img_ref, 'touchend', image_event_end); } else { //-------------------------------------------------- // Double tap/click event var now = new Date().getTime(); if (click_last > (now - 200)) { image_zoom_in(e); } else { click_last = now; } //-------------------------------------------------- // Starting position move_origin_left = img_current_left; move_origin_top = img_current_top; move_origin_cords = event_move_coords(e); //-------------------------------------------------- // Events // http://www.quirksmode.org/blog/archives/2010/02/the_touch_actio.html // http://www.quirksmode.org/m/tests/drag.html if (e.type === 'touchstart') { addEventListener(img_ref, 'touchmove', image_move_event); addEventListener(img_ref, 'touchend', image_event_end); } else { addEventListener(document, 'mousemove', image_move_event); addEventListener(document, 'mouseup', image_event_end); } } //-------------------------------------------------- // Prevent default // On small touch screens, it is // good to be able to use the grey // background to scroll the page. if (e.type !== 'touchstart') { return preventDefault(e); } else if (e.target == img_ref) { return preventDefault(e); // The target is the image, so they are trying to move the image. } else if (e.touches && e.touches.length > 1) { return preventDefault(e); // 2 or more fingers, so they are trying to zoom. } else { return true; } } function image_event_end() { removeEventListener(img_ref, 'touchmove', image_zoom_event); removeEventListener(img_ref, 'touchmove', image_move_event); removeEventListener(document, 'mousemove', image_move_event); removeEventListener(img_ref, 'touchend', image_event_end); removeEventListener(document, 'mouseup', image_event_end); } //-------------------------------------------------- // Keyboard event function keyboard_event(e) { var keyCode = (e ? e.which : window.event.keyCode); if (keyCode === 37 || keyCode === 39) { // left or right image_move_update((img_current_left + (keyCode === 39 ? 50 : -50)), img_current_top); } else if (keyCode === 38 || keyCode === 40) { // up or down image_move_update(img_current_left, (img_current_top + (keyCode === 40 ? 50 : -50))); } else if (keyCode === 107 || keyCode === 187 || keyCode === 61) { // + or = (http://www.javascripter.net/faq/keycodes.htm) image_zoom_in(e); } else if (keyCode === 109 || keyCode === 189) { // - or _ image_zoom_out(e); } } //-------------------------------------------------- // Initialisation function init() { div_ref = document.getElementById('image-zoom-wrapper'); img_ref = document.getElementById('image-zoom'); if (div_ref && img_ref) { //-------------------------------------------------- // Variables var div_style, div_width, div_height, width, height, button, buttons, name, len, k, wheel; //-------------------------------------------------- // Wrapper size try { div_style = getComputedStyle(div_ref, ''); div_half_width = div_style.getPropertyValue('width'); div_half_height = div_style.getPropertyValue('height'); } catch(e) { div_half_width = div_ref.currentStyle.width; div_half_height = div_ref.currentStyle.height; } div_half_width = Math.round(parseInt(div_half_width, 10) / 2); div_half_height = Math.round(parseInt(div_half_height, 10) / 2); //-------------------------------------------------- // Original image size img_original_width = img_ref.width; img_original_height = img_ref.height; //-------------------------------------------------- // Add zoom controls buttons = [{'t' : 'in', 's' : 'on'}, {'t' : 'in', 's' : 'off'}, {'t' : 'out', 's' : 'on'}, {'t' : 'out', 's' : 'off'}]; for (k = 0, len = buttons.length; k < len; k = k + 1) { button = buttons[k]; name = button.t + '-' + button.s; zoom_control_refs[name] = document.createElement('div'); zoom_control_refs[name].className = 'zoom-control zoom-' + button.t + ' zoom-' + button.s; if (button.t === 'in') { addEventListener(zoom_control_refs[name], 'mousedown', image_zoom_in); addEventListener(zoom_control_refs[name], 'touchstart', image_zoom_in); } else { if (button.s === 'on') { addEventListener(zoom_control_refs[name], 'mousedown', image_zoom_out); addEventListener(zoom_control_refs[name], 'touchstart', image_zoom_out); } else { addEventListener(zoom_control_refs[name], 'mousedown', preventPropagationAndDefault); addEventListener(zoom_control_refs[name], 'touchstart', preventPropagationAndDefault); } } if (button.s === 'on') { try { zoom_control_refs[name].style.cursor = 'pointer'; } catch(err) { zoom_control_refs[name].style.cursor = 'hand'; // Yes, even IE5 support } } else { zoom_control_refs[name].style.cursor = 'auto'; } div_ref.appendChild(zoom_control_refs[name]); } //-------------------------------------------------- // Zoom div_width = (div_half_width * 2); div_height = (div_half_height * 2); width = img_original_width; height = img_original_height; zoom_levels[zoom_levels.length] = Math.round(img_original_width * 1.75); // Oversize support zoom_levels[zoom_levels.length] = width; while (width > div_width || height > div_height) { width = (width * 0.75); height = (height * 0.75); zoom_levels[zoom_levels.length] = Math.round(width); } zoom_levels.reverse(); // IE5.0 does not support unshift. zoom_level_count = (zoom_levels.length - 1); zoom_min_width = zoom_levels[0]; zoom_max_width = zoom_levels[zoom_level_count]; image_zoom_update(zoom_levels[0]); //-------------------------------------------------- // Make visible img_ref.style.visibility = 'visible'; div_ref.className = div_ref.className + ' js-active'; //-------------------------------------------------- // Mouse / touch events wheel = 'onwheel' in document.createElement('div') ? 'wheel' : // Modern browsers document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support 'DOMMouseScroll'; // Older Firefox addEventListener(div_ref, wheel, scroll_event); addEventListener(div_ref, 'mousedown', image_event_start); addEventListener(div_ref, 'touchstart', image_event_start); //-------------------------------------------------- // Keyboard support div_ref.tabIndex = '0'; addEventListener(div_ref, 'keyup', keyboard_event); } } addEventListener(window, 'load', init); // Not DOM ready, as we need the image to have loaded })(document, window);