├── .gitignore ├── LICENSE.md ├── jquery.responsImg.min.js ├── demos └── index.html ├── README.md ├── jquery.responsImg.js └── jquery.responsImg.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files 2 | .DS_Store* 3 | ._* 4 | ehthumbs.db 5 | Thumbs.db 6 | Desktop.ini 7 | 8 | # Files that might appear on external disk 9 | .Spotlight-V100 10 | .Trashes -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Etienne Talbot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /jquery.responsImg.min.js: -------------------------------------------------------------------------------- 1 | jQuery.responsImg=function(e,n){var r,i,t,o,a,s,u,c,l,d,w,f,p,g,v,m,h;return i={allowDownsize:!1,elementQuery:!1,delay:200,breakpoints:null,considerDevice:!1},n&&jQuery.extend(i,n),h=jQuery(window),e=jQuery(e),g={},a=null,f=null,d=0,p=!1,l=function(){a=u(e),window.devicePixelRatio<1.5||(p=!0),g[0]=Array("IMG"===a?e.attr("src"):s(e)),h.on("resize.responsImg orientationchange.responsImg",w),o()},s=function(e){var n;return n=e.css("background-image"),n=n.replace("url(",""),n=n.replace(")","")},u=function(e){return jQuery(e).prop("tagName")},o=function(){var n,t,o,a,s,u,c,l;o=e.data(),u=/^responsimg/;for(a in o)if(l=o[a],u.test(a)){if(s=a.replace("responsimg",""),isNaN(s)){s=s.toLowerCase(),c=i.breakpoints;for(n in c)t=c[n],s===n&&(s=t)}else s=parseInt(s,10);g[s]=l.replace(" ","").split(",")}r()},w=function(){clearTimeout(f),f=setTimeout(r,i.delay)},t=function(){var n,r,t;return n=null,i.elementQuery===!0?(n=e.width(),null!=window.orientation&&i.considerDevice&&(t=h.width(),r=c(),n=Math.ceil(r*n/t))):n=null!=window.orientation&&i.considerDevice?c():h.width(),n},c=function(){var e;return e=0===window.orientation?window.screen.width:window.screen.height,navigator.userAgent.indexOf("Android")>=0&&window.devicePixelRatio&&(e/=window.devicePixelRatio),e},r=function(){var n,r,o,s,u,c;if(u=t(),n=0,d=0,r=!0,s="",u>d?d=u:i.allowDownsize===!1&&(r=!1),r===!0){for(o in g)c=g[o],parseInt(o,10)>u||parseInt(o,10) 2 | 3 | 4 | 5 | responsImg 6 | 16 | 17 | 18 | 19 | 54 | 55 | 56 | 57 |
58 |

responsImg demo

59 |

For this demo, the allowDownsize attribute have been set to true, so that smaller sources are loaded when resizing the browser/img-tag to a smaller size.

60 |

Media Query mode

61 |

62 | Go ahead and resize your browser :)
63 | When you stop resizing, the image will change 64 |

65 | 66 |
67 | $('.responsimg').responsImg({
68 |   allowDownsize: true
69 | }); 70 |
71 | 72 |

Used on an image tag

73 |

78 | 79 |

Used on a background-image

80 | 81 |
86 |
87 | 88 |

Element Query mode

89 |

90 | Click here to change make the image bigger
91 | Click here to change make the image smaller 92 |

93 | 94 |
95 | $('.responsimg-element').responsImg({
96 |   elementQuery: true,
97 |   allowDownsize: true
98 | }); 99 |
100 | 101 |

106 | 107 |

Keep in mind that this is the same tag. Only the width and height are changed.

108 |
109 | 110 | 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | responsImg 2 | ========== 3 | 4 | jQuery plugin written in CoffeeScript to make images load the smallest possible version of itself required for the current viewport size. See it as media queries for img tags. 5 | 6 | Requirements 7 | ------------ 8 | 9 | This plugin requires jQuery 1.7 or up. 10 | 11 | 12 | Information 13 | ----------- 14 | 15 | Current version: 1.5.0 16 | 17 | - Different image sources are set as data attributes in the `` tag itself. 18 | - (NEW!) Will work on background images if not set on any tag besides an `` tag (if you need dynamic responsive background images). 19 | - You can specify @2x image sources. If specified, they will be used if the user has a retina display. Retina sizes must have a matching non-retina size in order to work. To set a retina image, add a comma and a space after the first source in a data atribute ("image.jpg, image@2x.jpg"). 20 | - Breakpoints are determined by the name of the data attribute. All of them must start with `data-responsimg` followed by the pixel value of the breakpoint (ex: `data-responsimg960`) or by a `-` plus the name of the breakpoint (ex: `data-responsimg-smalltablet`). *You must define the `breakpoints` parameter if you want to use named breakpoints.* 21 | - The `src` attribute is considered like a **0px breakpoint**. 22 | - I recommend using the smallest size as the default image (the actual `src` attribute), or else search engines won't see your image. 23 | 24 | Parameters 25 | ---------- 26 | 27 | ### allowDownsize 28 | *(boolean) default: false* - By default, if a bigger image is already loaded, responsImg will not try to load a smaller one. To override this behavior, set to true. 29 | 30 | ### elementQuery 31 | *(boolean) default: false* - By default, the sizes specified in the data attributes are related to the size of the viewport. If elementQuery is set to true, the sizes specified in the attributes will be related to the size of the image itself. 32 | 33 | ### delay 34 | *(integer) default: 200* - Delay between the window resize action and the image change. Be careful, as a low number means a more demanding process for the browser. 35 | 36 | ### breakpoints 37 | *(object) default: null* - The names of the breakpoints with their respective sizes *(int format)*. 38 | 39 | breakpoints: { 40 | foo: 480, 41 | bar: 768, 42 | baz: 960 43 | } 44 | 45 | ### considerDevice 46 | *(boolean) default: false* - Set to true if you want images loaded in the smallest size necessary to fit the number of pixels needed when the browser is zoomed out. Perfect for websites that want to keep mobile loading to a minimum and that don't have the meta viewport tag set to device-width. 47 | 48 | Methods 49 | ------- 50 | 51 | ### recheck() 52 | Ask responsImg to check the width again and trigger a `src` change if necessary 53 | 54 | $('.responsive-image').data('responsImg').recheck() 55 | 56 | 57 | Usage 58 | ----- 59 | 60 | Here is an example using all parameters. Keep in mind that they are all optional. 61 | 62 | ### JavaScript 63 | $('.responsive-image').responsImg({ 64 | allowDownsize: true, 65 | elementQuery: true, 66 | delay: 250, 67 | breakpoints: { 68 | mobile: 320, 69 | desktop: 960 70 | } 71 | }); 72 | 73 | ### CoffeeScript 74 | $('.responsive-image').responsImg 75 | allowDownsize: true 76 | elementQuery: true 77 | delay: 250 78 | breakpoints: 79 | mobile: 320 80 | desktop: 960 81 | 82 | ### HTML 83 | 88 | 89 | In this example, the default image is `default-image.png`. This image is always loaded… make sure it's pretty small. You could always remove the `src` attribute completely if you really wanted to. 90 | 91 | The image `image-320.png` is loaded and displayed if the window reaches a width of **320** pixels. If the screen used is retina, the image used will only be `image-320@2x.png`. If the window reaches **480** pixels wide, `image-480.png` will be loaded and displayed. Even if you have a retina display, this image will override the previous one. If the window reaches **960** pixels wide, `blue-car.png` will be loaded and displayed. 92 | 93 | **Important** - *Please keep in mind that if the elementQuery is set to true, the breakpoints won't be defined by the width of the window but instead by the width of the image itself.* 94 | 95 | ## Thanks 96 | Special thanks to [Léon Talbot](https://github.com/leontalbot) and [Bernard Chhun](https://github.com/bchhun) for the suggestions and support. 97 | -------------------------------------------------------------------------------- /jquery.responsImg.js: -------------------------------------------------------------------------------- 1 | /* 2 | * responsImg jQuery Plugin 3 | * Turn your tags into responsive images with retina alternatives 4 | * version 1.5.0, August 28th, 2015 5 | * by Etienne Talbot 6 | */ 7 | jQuery.responsImg = function(element, settings) { 8 | var checkSizes, config, defineWidth, determineSizes, elementType, getBackgroundImage, getElementType, getMobileWindowWidth, init, largestSize, resizeDetected, resizeTimer, retinaDisplay, rimData, setBackgroundImage, setImage, theWindow; 9 | config = { 10 | allowDownsize: false, 11 | elementQuery: false, 12 | delay: 200, 13 | breakpoints: null, 14 | considerDevice: false 15 | }; 16 | if (settings) { 17 | jQuery.extend(config, settings); 18 | } 19 | theWindow = jQuery(window); 20 | element = jQuery(element); 21 | rimData = {}; 22 | elementType = null; 23 | resizeTimer = null; 24 | largestSize = 0; 25 | retinaDisplay = false; 26 | init = function() { 27 | elementType = getElementType(element); 28 | if (window.devicePixelRatio >= 1.5) { 29 | retinaDisplay = true; 30 | } 31 | if (elementType === 'IMG') { 32 | rimData[0] = new Array(element.attr('src')); 33 | } else { 34 | rimData[0] = new Array(getBackgroundImage(element)); 35 | } 36 | theWindow.on('resize.responsImg orientationchange.responsImg', resizeDetected); 37 | determineSizes(); 38 | }; 39 | getBackgroundImage = function(element) { 40 | var bg; 41 | bg = element.css('background-image'); 42 | bg = bg.replace('url(', ''); 43 | bg = bg.replace(')', ''); 44 | return bg; 45 | }; 46 | getElementType = function(element) { 47 | return jQuery(element).prop('tagName'); 48 | }; 49 | determineSizes = function() { 50 | var breakKey, breakValue, elData, key, newKey, pattern, ref, value; 51 | elData = element.data(); 52 | pattern = /^responsimg/; 53 | for (key in elData) { 54 | value = elData[key]; 55 | if (pattern.test(key)) { 56 | newKey = key.replace('responsimg', ''); 57 | if (isNaN(newKey)) { 58 | newKey = newKey.toLowerCase(); 59 | ref = config.breakpoints; 60 | for (breakKey in ref) { 61 | breakValue = ref[breakKey]; 62 | if (newKey === breakKey) { 63 | newKey = breakValue; 64 | } 65 | } 66 | } else { 67 | newKey = parseInt(newKey, 10); 68 | } 69 | rimData[newKey] = value.replace(' ', '').split(','); 70 | } 71 | } 72 | checkSizes(); 73 | }; 74 | resizeDetected = function() { 75 | clearTimeout(resizeTimer); 76 | resizeTimer = setTimeout(checkSizes, config.delay); 77 | }; 78 | defineWidth = function() { 79 | var definedWidth, mobileWindowWidth, windowWidth; 80 | definedWidth = null; 81 | if (config.elementQuery === true) { 82 | definedWidth = element.width(); 83 | if ((window.orientation != null) && config.considerDevice) { 84 | windowWidth = theWindow.width(); 85 | mobileWindowWidth = getMobileWindowWidth(); 86 | definedWidth = Math.ceil(mobileWindowWidth * definedWidth / windowWidth); 87 | } 88 | } else { 89 | if ((window.orientation != null) && config.considerDevice) { 90 | definedWidth = getMobileWindowWidth(); 91 | } else { 92 | definedWidth = theWindow.width(); 93 | } 94 | } 95 | return definedWidth; 96 | }; 97 | getMobileWindowWidth = function() { 98 | var mobileWindowWidth; 99 | if (window.orientation === 0) { 100 | mobileWindowWidth = window.screen.width; 101 | } else { 102 | mobileWindowWidth = window.screen.height; 103 | } 104 | if (navigator.userAgent.indexOf('Android') >= 0 && window.devicePixelRatio) { 105 | mobileWindowWidth = mobileWindowWidth / window.devicePixelRatio; 106 | } 107 | return mobileWindowWidth; 108 | }; 109 | checkSizes = function() { 110 | var currentSelection, doIt, key, newSrc, theWidth, value; 111 | theWidth = defineWidth(); 112 | currentSelection = 0; 113 | largestSize = 0; 114 | doIt = true; 115 | newSrc = ''; 116 | if (theWidth > largestSize) { 117 | largestSize = theWidth; 118 | } else if (config.allowDownsize === false) { 119 | doIt = false; 120 | } 121 | if (doIt === true) { 122 | for (key in rimData) { 123 | value = rimData[key]; 124 | if (parseInt(key, 10) <= theWidth && parseInt(key, 10) >= currentSelection) { 125 | currentSelection = parseInt(key, 10); 126 | newSrc = rimData[currentSelection][0]; 127 | } 128 | } 129 | if (retinaDisplay === true && (rimData[currentSelection][1] != null)) { 130 | newSrc = rimData[currentSelection][1]; 131 | } 132 | if (elementType === 'IMG') { 133 | setImage(element, newSrc); 134 | } else { 135 | setBackgroundImage(element, newSrc); 136 | } 137 | } 138 | }; 139 | setImage = function(element, newSrc) { 140 | var oldSrc; 141 | oldSrc = element.attr('src'); 142 | if (newSrc !== oldSrc) { 143 | element.attr('src', newSrc); 144 | } 145 | }; 146 | setBackgroundImage = function(element, newSrc) { 147 | var oldSrc; 148 | oldSrc = getBackgroundImage(element); 149 | if (newSrc !== oldSrc) { 150 | element.css('background-image', 'url(' + newSrc + ')'); 151 | } 152 | }; 153 | init(); 154 | this.recheck = function() { 155 | checkSizes(); 156 | }; 157 | return this; 158 | }; 159 | 160 | jQuery.fn.responsImg = function(options) { 161 | return this.each(function() { 162 | var jQthis, plugin; 163 | jQthis = jQuery(this); 164 | if (jQthis.data('responsImg') === void 0) { 165 | plugin = new jQuery.responsImg(this, options); 166 | jQthis.data('responsImg', plugin); 167 | } 168 | }); 169 | }; 170 | 171 | // --- 172 | // generated by coffee-script 1.9.2 -------------------------------------------------------------------------------- /jquery.responsImg.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | # responsImg jQuery Plugin 3 | # Turn your tags into responsive images with retina alternatives 4 | # version 1.4.2, August 28th, 2015 5 | # by Etienne Talbot 6 | ### 7 | 8 | jQuery.responsImg = (element, settings) -> 9 | 10 | # default config values 11 | config = 12 | allowDownsize: false # If set to false, smaller images will never be loaded on resize or orientationchange 13 | elementQuery: false # False = window's width breakpoints. True = image's width breakpoints. 14 | delay: 200 # Delay between the window resize action and the image change (too low means more demanding for the browser) 15 | breakpoints: null # Object containing the different breakpoints for your page or element 16 | considerDevice: false # If true, responsImg will pick the image size while considering the zoomed-out level of mobile devices 17 | 18 | jQuery.extend config, settings if settings 19 | 20 | theWindow = jQuery window 21 | element = jQuery element 22 | rimData = {} 23 | elementType = null 24 | resizeTimer = null 25 | largestSize = 0 26 | retinaDisplay = false 27 | 28 | # initialize the plugin 29 | init = -> 30 | elementType = getElementType element 31 | retinaDisplay = true if window.devicePixelRatio >= 1.5 32 | 33 | if elementType == 'IMG' 34 | rimData[0] = new Array(element.attr 'src') 35 | else 36 | rimData[0] = new Array(getBackgroundImage element) 37 | 38 | theWindow.on 'resize.responsImg orientationchange.responsImg', resizeDetected 39 | 40 | determineSizes() 41 | 42 | return 43 | 44 | # Return the image path from a background-image style 45 | getBackgroundImage = (element) -> 46 | bg = element.css 'background-image' 47 | bg = bg.replace 'url(', '' 48 | bg = bg.replace ')', '' 49 | 50 | return bg 51 | 52 | # Return the jquery element type 53 | getElementType = (element) -> 54 | return jQuery(element).prop 'tagName' 55 | 56 | # Put in an object the responsive values of the image 57 | determineSizes = -> 58 | elData = element.data() 59 | pattern = /^responsimg/ 60 | 61 | for key, value of elData 62 | 63 | if pattern.test key 64 | newKey = key.replace 'responsimg', '' 65 | 66 | if isNaN(newKey) 67 | newKey = newKey.toLowerCase() 68 | for breakKey, breakValue of config.breakpoints 69 | if newKey == breakKey 70 | newKey = breakValue 71 | else 72 | newKey = parseInt newKey, 10 73 | 74 | rimData[newKey] = value.replace(' ', '').split ',' 75 | 76 | checkSizes() 77 | 78 | return 79 | 80 | # The browser resize or rotation has been detected 81 | resizeDetected = -> 82 | clearTimeout resizeTimer 83 | resizeTimer = setTimeout checkSizes, config.delay 84 | 85 | return 86 | 87 | # Define the actual width of the browser or the image 88 | #(wether it's in media query or element query mode) 89 | defineWidth = -> 90 | definedWidth = null 91 | 92 | if config.elementQuery is true 93 | definedWidth = element.width() 94 | 95 | if window.orientation? and config.considerDevice 96 | windowWidth = theWindow.width() 97 | mobileWindowWidth = getMobileWindowWidth() 98 | 99 | definedWidth = Math.ceil(mobileWindowWidth * definedWidth / windowWidth) 100 | 101 | else 102 | if window.orientation? and config.considerDevice 103 | definedWidth = getMobileWindowWidth() 104 | 105 | else 106 | definedWidth = theWindow.width() 107 | 108 | definedWidth 109 | 110 | # Detect the width of the mobile window 111 | getMobileWindowWidth = -> 112 | if window.orientation is 0 113 | mobileWindowWidth = window.screen.width 114 | else 115 | mobileWindowWidth = window.screen.height 116 | 117 | if navigator.userAgent.indexOf('Android') >= 0 and window.devicePixelRatio 118 | mobileWindowWidth = mobileWindowWidth / window.devicePixelRatio; 119 | 120 | mobileWindowWidth 121 | 122 | # Determine which image size is appropriate for the current situation 123 | checkSizes = -> 124 | theWidth = defineWidth() 125 | currentSelection = 0 126 | largestSize = 0 127 | doIt = true 128 | newSrc = '' 129 | 130 | if theWidth > largestSize 131 | largestSize = theWidth 132 | else if config.allowDownsize is false 133 | doIt = false 134 | 135 | if doIt is true 136 | for key, value of rimData 137 | 138 | #parseInt is used here because for some reason, keys over 999 are not exactly considered as integers 139 | if parseInt(key, 10) <= theWidth and parseInt(key, 10) >= currentSelection 140 | currentSelection = parseInt key, 10 141 | newSrc = rimData[currentSelection][0] 142 | 143 | if retinaDisplay is true and rimData[currentSelection][1]? 144 | newSrc = rimData[currentSelection][1] 145 | 146 | if elementType == 'IMG' 147 | setImage element, newSrc 148 | else 149 | setBackgroundImage element, newSrc 150 | 151 | return 152 | 153 | # Change the image's src 154 | setImage = (element, newSrc) -> 155 | oldSrc = element.attr 'src' 156 | 157 | if newSrc != oldSrc 158 | element.attr 'src', newSrc 159 | 160 | return 161 | 162 | # Change the background-image's src 163 | setBackgroundImage = (element, newSrc) -> 164 | oldSrc = getBackgroundImage element 165 | 166 | if newSrc != oldSrc 167 | element.css 'background-image', 'url('+newSrc+')' 168 | 169 | return 170 | 171 | # Punch it, Chewie! 172 | init() 173 | 174 | # Recheck now 175 | @recheck = -> 176 | checkSizes() 177 | 178 | return 179 | 180 | return this 181 | 182 | jQuery.fn.responsImg = (options) -> 183 | @each( -> 184 | jQthis = jQuery this 185 | if jQthis.data('responsImg') is undefined 186 | plugin = new jQuery.responsImg this, options 187 | jQthis.data 'responsImg', plugin 188 | return 189 | ) 190 | --------------------------------------------------------------------------------