open-science-observatory-ui/src/assets/js/custom/dense.js

399 lines
13 KiB
JavaScript
Executable File

/**
* Dense - Device pixel ratio aware images
*
* @link http://dense.rah.pw
* @license MIT
*/
/*
* Copyright (C) 2013 Jukka Svahn
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @name jQuery
* @class
*/
/**
* @name fn
* @class
* @memberOf jQuery
*/
(function (factory)
{
'use strict';
if (typeof define === 'function' && define.amd)
{
define(['jquery'], factory);
}
else
{
factory(window.jQuery || window.Zepto);
}
}(function ($)
{
'use strict';
/**
* An array of checked image URLs.
*/
var pathStack = [],
/**
* Methods.
*/
methods = {},
/**
* Regular expression to check whether the URL has a protocol.
*
* Is used to check whether the image URL is external.
*/
regexHasProtocol = /^([a-z]:)?\/\//i,
/**
* Regular expression that split extensions from the file.
*
* Is used to inject the DPR suffix to the name.
*/
regexSuffix = /\.\w+$/,
/**
* Device pixel ratio.
*/
devicePixelRatio;
/**
* Init is the default method responsible for rendering
* a pixel-ratio-aware images.
*
* This method is used to select the images that
* should display retina-size images on high pixel ratio
* devices. Dense defaults to the init method if no
* method is specified.
*
* When attached to an image, the correct image variation is
* selected based on the device's pixel ratio. If the image element
* defines <code>data-{ratio}x</code> attributes (e.g. data-1x, data-2x, data-3x),
* the most appropriate of those is selected.
*
* If no data-ratio attributes are defined, the retina image is
* constructed from the <code>src</code> attribute.
* The searched high pixel ratio images follows
* a <code>{imageName}_{ratio}x.{ext}</code> naming convention.
* For an image found in /path/to/images/image.jpg, the 2x retina
* image would be looked from /path/to/images/image_2x.jpg.
*
* When image is constructed from the src, the image existance is
* verified using HTTP HEAD request, if <code>ping</code> option is
* <code>true</code>. The check makes sure no HTTP error code is returned,
* and that the received content-type is of an image. Vector image formats,
* like svg, are skipped based on the file extension.
*
* This method can also be used to load image in semi-lazy fashion,
* and avoid larger extra HTTP requests due to retina replacements.
* The data-1x attribute can be used to supstitute the src, making
* sure the browser doesn't try to download the normal image variation
* before the JavaScript driven behaviour kicks in.
*
* Some classes are added to the selected elements while Dense is processing
* the document. These classes include <code>dense-image</code>, <code>dense-loading</code>
* and <code>dense-ready</code>. These classes can be used to style the images,
* or hide them while they are being loaded.
*
* @param {Object} [options={}] Options
* @param {Boolean} [options.ping=null] Check image existence. If the default <code>NULL</code> checks local images, <code>FALSE</code> disables checking and <code>TRUE</code> checks even external images cross-domain
* @param {String} [options.dimensions=preserve] What to do with the image's <code>width</code> and <code>height</code> attributes. Either <code>update</code>, <code>remove</code> or <code>preserve</code>
* @param {String} [options.glue=_] String that glues the retina "nx" suffix to the image. This option can be used to change the naming convention between the two commonly used practices, <code>image@2x.jpg</code> and <code>image_2x.jpg</code>
* @param {Array} [options.skipExtensions=['svg']] Skipped image file extensions. There might be situations where you might want to exclude vector image formats
* @return {Object} this
* @method init
* @memberof jQuery.fn.dense
* @fires jQuery.fn.dense#denseRetinaReady.dense
* @example
* $('img').dense({
* ping: false,
* dimension: 'update'
* });
*/
methods.init = function (options)
{
options = $.extend({
ping: null,
dimensions: 'preserve',
glue: '_',
skipExtensions: ['svg']
}, options);
this.each(function ()
{
var $this = $(this);
if (!$this.is('img') || $this.hasClass('dense-image'))
{
return;
}
$this.addClass('dense-image dense-loading');
var image = methods.getImageAttribute.call(this),
originalImage = $this.attr('src'),
ping = false,
updateImage;
if (!image)
{
if (!originalImage || devicePixelRatio === 1 || $.inArray(originalImage.split('.').pop().split(/[\?\#]/).shift(), options.skipExtensions) !== -1)
{
$this.removeClass('dense-image dense-loading');
return;
}
image = originalImage.replace(regexSuffix, function (extension)
{
var pixelRatio = $this.attr('data-dense-cap') ? $this.attr('data-dense-cap') : devicePixelRatio;
return options.glue + pixelRatio + 'x' + extension;
});
ping = options.ping !== false && $.inArray(image, pathStack) === -1 && (options.ping === true || !regexHasProtocol.test(image) || image.indexOf('//'+document.domain) === 0 || image.indexOf(document.location.protocol+'//'+document.domain) === 0);
}
updateImage = function ()
{
var readyImage = function ()
{
$this.removeClass('dense-loading').addClass('dense-ready').trigger('denseRetinaReady.dense');
};
$this.attr('src', image);
if (options.dimensions === 'update')
{
$this.dense('updateDimensions').one('denseDimensionChanged', readyImage);
}
else
{
if (options.dimensions === 'remove')
{
$this.removeAttr('width height');
}
readyImage();
}
};
if (ping)
{
$.ajax({
url : image,
type : 'HEAD'
})
.done(function (data, textStatus, jqXHR)
{
var type = jqXHR.getResponseHeader('Content-type');
if (!type || type.indexOf('image/') === 0)
{
pathStack.push(image);
updateImage();
}
});
}
else
{
updateImage();
}
});
return this;
};
/**
* Sets an image's width and height attributes to its native values.
*
* Updates an img element's dimensions to the source image's
* real values. This method is asynchronous, so you can not directly
* return its values. Instead, use the 'dense-dimensions-updated'
* event to detect when the action is done.
*
* @return {Object} this
* @method updateDimensions
* @memberof jQuery.fn.dense
* @fires jQuery.fn.dense#denseDimensionChanged.dense
* @example
* var image = $('img').dense('updateDimensions');
*/
methods.updateDimensions = function ()
{
return this.each(function ()
{
var img, $this = $(this), src = $this.attr('src');
if (src)
{
img = new Image();
img.src = src;
$(img).on('load.dense', function ()
{
$this.attr({
width: img.width,
height: img.height
}).trigger('denseDimensionChanged.dense');
});
}
});
};
/**
* Gets device pixel ratio rounded up to the closest integer.
*
* @return {Integer} The pixel ratio
* @method devicePixelRatio
* @memberof jQuery.fn.dense
* @example
* var ratio = $(window).dense('devicePixelRatio');
* alert(ratio);
*/
methods.devicePixelRatio = function ()
{
var pixelRatio = 1;
if ($.type(window.devicePixelRatio) !== 'undefined')
{
pixelRatio = window.devicePixelRatio;
}
else if ($.type(window.matchMedia) !== 'undefined')
{
$.each([1.3, 2, 3, 4, 5, 6], function (key, ratio)
{
var mediaQuery = [
'(-webkit-min-device-pixel-ratio: '+ratio+')',
'(min-resolution: '+Math.floor(ratio*96)+'dpi)',
'(min-resolution: '+ratio+'dppx)'
].join(',');
if (!window.matchMedia(mediaQuery).matches)
{
return false;
}
pixelRatio = ratio;
});
}
return Math.ceil(pixelRatio);
};
/**
* Gets an appropriate URL for the pixel ratio from the data attribute list.
*
* Selects the most appropriate <code>data-{ratio}x</code> attribute from
* the given element's attributes. If the devices pixel ratio is greater
* than the largest specified image, the largest one of the available is used.
*
* @return {String|Boolean} The attribute value
* @method getImageAttribute
* @memberof jQuery.fn.dense
* @example
* var image = $('<div data-1x="image.jpg" data-2x="image_2x.jpg" />').dense('getImageAttribute');
* $('body').css('background-image', 'url(' + image + ')');
*/
methods.getImageAttribute = function ()
{
var $this = $(this).eq(0), image = false, url;
for (var i = 1; i <= devicePixelRatio; i++)
{
url = $this.attr('data-' + i + 'x');
if (url)
{
image = url;
}
}
return image;
};
devicePixelRatio = methods.devicePixelRatio();
/**
* Dense offers few methods and options that can be used to both customize the
* plugin's functionality and return resulting values. All interaction is done through
* the <code>$.fn.dense()</code> method, that accepts a called method and its options
* object as its arguments. Both arguments are optional, and either one can be omitted.
*
* @param {String} [method=init] The called method
* @param {Object} [options={}] Options passed to the method
* @class dense
* @memberof jQuery.fn
*/
$.fn.dense = function (method, options)
{
if ($.type(method) !== 'string' || $.type(methods[method]) !== 'function')
{
options = method;
method = 'init';
}
return methods[method].call(this, options);
};
/**
* Initialize automatically when document is ready.
*
* Dense is initialized automatically if the body element
* has a <code>dense-retina</code> class.
*/
$(function ()
{
$('body.dense-retina img').dense();
});
/**
* This event is invoked when a retina image has finished loading.
*
* @event jQuery.fn.dense#denseRetinaReady.dense
* @type {Object}
*/
/**
* This event is invoked when an image's dimension values
* have been updated by the <code>updateDimensions</code>
* method.
*
* @event jQuery.fn.dense#denseDimensionChanged.dense
* @type {Object}
*/
}));