UNPKG

image-selector

Version:
231 lines (198 loc) 6.07 kB
/* Copyright 2016 Stu Kabakoff https://github.com/stutrek/imageSelector MIT Licensed */ var events = require('add-event-listener'); var isRetina = ( window.devicePixelRatio && window.devicePixelRatio > 1.5 ); var responsiveCallbacks = []; function addResponsiveCallback (callback) { if (responsiveCallbacks.length === 0) { events.addEventListener(window, 'resize', function() { for (var i=0; i < responsiveCallbacks.length; i++) { responsiveCallbacks[i](); } }); } responsiveCallbacks.push(callback); } function getStyle (el, styleProp) { var y; if (el.currentStyle) { y = el.currentStyle[styleProp]; } else if (window.getComputedStyle) { y = document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); } return y; } function getWidth (element) { var width = element.clientWidth; if (width === 0) { var widthStr = getStyle( element, 'width' ); if (widthStr === 'auto') { width = getWidth( element.parentNode ); } else if (widthStr.charAt(widthStr.length-1) === '%') { var percent = parseInt( widthStr, 10 ); var parentWidth = getWidth( element.parentNode ); width = parentWidth * (percent / 100); } else { width = parseInt( widthStr, 10 ); } } return width; } function addAt2x (url) { var indexOfDot = url.lastIndexOf('.'); return url.substr(0, indexOfDot)+'@2x'+url.substr(indexOfDot); } function rateImage (imageWidth, desiredWidth) { var ratio = imageWidth / desiredWidth; if (ratio < 1) { ratio = ratio / 2; } return Math.abs( 1 - ratio ); } var aspectRatioCache = {}; function createFilterOnAspectRatio (aspectRatio) { if (!aspectRatioCache[aspectRatio]) { aspectRatioCache[aspectRatio] = function( cut ) { return cut.aspectRatio === aspectRatio; }; } return aspectRatioCache[aspectRatio]; } var heightWidthCache = {}; function createFilterOnWidthAndHeight (width, height) { var desiredAspectRatio = width / height; var ratioString = desiredAspectRatio.toString(); if (!heightWidthCache[ratioString]) { heightWidthCache[ratioString] = function( cut ) { // if the cut is exactly the right ratio if (cut.width / cut.height === desiredAspectRatio) { return true; } // if adding one to the width is larger and subtracting one is smaller than the desired ratio if ( ((cut.width+1) / cut.height) >= desiredAspectRatio && ((cut.width-1) / cut.height) <= desiredAspectRatio) { return true; } return false; }; } return heightWidthCache[ratioString]; } exports.selectCutWithAspectRatio = function (cuts, desiredWidth, aspectRatio, worstAccepableScore) { cuts = cuts.filter( createFilterOnAspectRatio( aspectRatio ) ); return exports.selectCut( cuts, desiredWidth, worstAccepableScore); }; exports.selectCutWithWidthAndHeight = function (cuts, desiredWidth, desiredHeight, worstAccepableScore) { cuts = cuts.filter( createFilterOnWidthAndHeight( desiredWidth, desiredHeight) ); return exports.selectCut( cuts, desiredWidth, worstAccepableScore); }; exports.selectCut = function (cuts, desiredWidth, worstAccepableScore) { if (worstAccepableScore === undefined) { worstAccepableScore = 0.75; } var cutToUse, bestScore = Infinity; for (var i in cuts) { if (cuts.hasOwnProperty(i)) { var cut = cuts[i]; if (desiredWidth === cut.width) { return cut; } var score = rateImage( cut.width, desiredWidth ); if (score < worstAccepableScore) { if (score < bestScore) { cutToUse = cut; bestScore = score; } } } } return cutToUse; }; exports.addSource = function (element, srcAttribute, cuts) { srcAttribute = srcAttribute || 'src'; cuts = cuts || JSON.parse(element.getAttribute('data-cuts')); var width = getWidth( element ); var height; var aspectRatio = element.getAttribute('data-aspect-ratio'); var shouldUseHeight = false; for (var i in cuts) { if (cuts.hasOwnProperty(i) && cuts[i].height) { shouldUseHeight = true; break; } } if (element.attributes.height && element.attributes.height.specified) { height = element.height; } else { height = getStyle( element, 'height' ); } height = parseInt(height, 10); srcAttribute = element.getAttribute('data-src-attribute') || srcAttribute; // make sure it's not a missing image icon if (height < 30) { height = false; } var cut; if (aspectRatio) { cut = exports.selectCutWithAspectRatio( cuts, width, aspectRatio ); } else if (shouldUseHeight && height) { cut = exports.selectCutWithWidthAndHeight( cuts, width, height ); } else { cut = exports.selectCut( cuts, width ); } if (cut) { var src = cut.src; if (isRetina && cut.at2x && cut.width < width*1.5) { if (typeof cut.at2x === 'string') { src = cut.at2x; } else { src = addAt2x(src); } } element.setAttribute( srcAttribute, src ); } else { element.className += ' no-cut-found'; element.setAttribute( srcAttribute, '' ); } }; exports.watchImage = function (img, cuts, srcAttribute) { var currentWidth = img.offsetWidth; exports.addSource(img, srcAttribute, cuts); var callback = function () { if (img.offsetWidth !== currentWidth) { currentWidth = img.offsetWidth; exports.addSource(img, srcAttribute, cuts); } }; addResponsiveCallback(callback); return { destroy: function () { var index = responsiveCallbacks.indexOf(callback); responsiveCallbacks.splice(index, 1); }, recalculate: function () { exports.addSource(img, srcAttribute, cuts); } }; }; exports.selectImages = function (container, srcAttribute) { container = container || document.body; var elements = container.querySelectorAll('img[data-cuts]'); var element; for (var i = 0; i < elements.length; i++) { element = elements[i]; try { var cuts = JSON.parse(element.getAttribute('data-cuts')); element.removeAttribute('data-cuts'); if (element.getAttribute('data-responsive') === 'true') { exports.watchImage(element, cuts, srcAttribute); } else { exports.addSource(element, srcAttribute, cuts); } } catch (e) { console && console.error && console.error(e); } } };