├── responsive-product-image.liquid ├── examples └── collection-with-layout.liquid ├── responsive-images.js └── README.md /responsive-product-image.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Responsive product images with srcset. 3 | {% endcomment %} 4 | 5 | {% assign default_default_size = "medium" %} 6 | {% assign default_sizes = "100vw" %} 7 | 8 | {% if default_size == nil %}{% assign default_size = default_default_size %}{% endif %} 9 | {% if sizes == nil %}{% assign sizes = default_sizes %}{% endif %} 10 | 11 | {{ image.alt | escape }} 24 | -------------------------------------------------------------------------------- /examples/collection-with-layout.liquid: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Shopify Responsive Images Example 3 | https://github.com/zacwasielewski/shopify-responsive-images 4 | 5 | Grid classes and breakpoints were sustainably harvested from 6 | the Timber framework (https://github.com/Shopify/Timber) 7 | {% endcomment %} 8 | 9 | 10 | Shopify Responsive Images Example 11 | 12 | {% comment %} 13 | Step 1: Include Picturefill 14 | {% endcomment %} 15 | 16 | 17 | 18 | 19 | 20 |
21 |

{{ collection.title }}

22 |
23 | 24 |
25 | 26 | {% for product in collection.products %} 27 | 28 |
29 | 30 | 31 | {% comment %} 32 | Step 3: Include responsive-product-image snippet 33 | {% endcomment %} 34 | {% include 'responsive-product-image', 35 | image: product.featured_image, 36 | sizes: '(max-width: 640px) 50vw, (min-width: 641px) and (max-width: 960px) 33vw, 25vw' %} 37 | 38 | 39 |
{{ product.title }}
40 |
{{ product.price | money }}
41 |
42 | 43 | {% else %} 44 | 45 |

There are no products in this collection.

46 | 47 | {% endfor %} 48 | 49 |
50 | 51 | {% comment %} 52 | Step 2: Include responsive-images.js 53 | {% endcomment %} 54 | {{ 'responsive-images.js' | asset_url | script_tag }} 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /responsive-images.js: -------------------------------------------------------------------------------- 1 | var ShopifyResponsiveImages = (function( window, undefined ) { 2 | 3 | var sizes = { 4 | "small": 100, 5 | "compact": 160, 6 | "medium": 240, 7 | "large": 480, 8 | "grande": 600, 9 | "1024x1024": 1024 10 | }; 11 | 12 | function load() { 13 | ready(init); 14 | } 15 | 16 | function init() { 17 | var images = document.querySelectorAll('img[data-js-responsive]'); 18 | forEach(images, function(index, img) { 19 | img.addEventListener('load', function() { 20 | var scaledImage = scaleImgSrcset(this); 21 | //triggerPictureFill(scaledImage); 22 | }, false); 23 | }); 24 | window.addEventListener('load', function() { 25 | //triggerPictureFill(); 26 | }, false); 27 | } 28 | 29 | function scaleImgSrcset(img) { 30 | var srcset, 31 | srcset_orig, 32 | srcset_json, 33 | ratio = img.naturalWidth / img.naturalHeight, 34 | scale = getImageScale(img), 35 | is_portrait = (ratio < 1); 36 | 37 | if (!is_portrait) { return; } 38 | if (img.getAttribute('data-js-srcset-scaled') === 'true') { return; } 39 | 40 | // parse the original srcset attribute into a JSON object 41 | srcset_orig = img.getAttribute('srcset') || (img.picturefill || {}).srcset; 42 | srcset_json = srcsetToJSON(srcset_orig); 43 | 44 | // scale srcset sizes and convert back to a string 45 | srcset = srcset_json.map(function(rule) { 46 | return scaleSrcsetRule(rule, scale); 47 | }).join(','); 48 | 49 | img.setAttribute('srcset', srcset); 50 | img.setAttribute('data-js-srcset-scaled', 'true'); // prevent rescaling if load event is triggered again 51 | 52 | return img; 53 | } 54 | 55 | function triggerPictureFill(img) { 56 | if (typeof picturefill === 'undefined') { return; } 57 | if (img) { 58 | picturefill({ reevaluate: true, elements: [img] }); 59 | } else { 60 | picturefill({ reevaluate: true }); 61 | } 62 | } 63 | 64 | function getImageScale(img) { 65 | 66 | var longest_edge = Math.max(img.naturalWidth, img.naturalHeight); 67 | for (var key in sizes) { 68 | var size = sizes[key]; 69 | if (img.src.indexOf( "_" +key+ "." )) { 70 | if (size > longest_edge) { 71 | return longest_edge / size; 72 | } 73 | } 74 | } 75 | return 1; 76 | } 77 | 78 | function srcsetToJSON(srcset) { 79 | return srcset.split(',').map(function(rule){ 80 | return rule.trim().split(/\s+/).map(function(s) { return s.trim(); }); 81 | }); 82 | } 83 | 84 | function scaleSrcsetRule(rule, scale) { 85 | var url = rule[0], 86 | width = parseInt(rule[1].slice(0,-1)); 87 | if (width > 160) { width = Math.floor(width * scale); } // don't scale square thumbnails 88 | return [url, width.toString()+"w"].join(" "); 89 | } 90 | 91 | // Utility methods 92 | 93 | function ready(fn) { 94 | if (document.readyState != 'loading'){ 95 | fn(); 96 | } else { 97 | document.addEventListener('DOMContentLoaded', fn); 98 | } 99 | } 100 | 101 | function forEach(array, callback, scope) { 102 | for (var i = 0; i < array.length; i++) { 103 | callback.call(scope, i, array[i]); 104 | } 105 | } 106 | 107 | // Public methods 108 | 109 | return { 110 | load: load 111 | }; 112 | 113 | })( window ); 114 | 115 | ShopifyResponsiveImages.load(); // undefined 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Responsive Shopify product images 2 | === 3 | 4 | Use this `.liquid` snippet in your Shopify theme to generate responsive product images. 5 | 6 | Quick start 7 | --- 8 | 9 | 1. Copy `responsive-product-image.liquid` to your theme's `snippets/` folder and include it wherever you would normally include a product image. You *must* include the `image` parameter, specifying which product image to use: 10 | 11 | ```ruby 12 | {% include 'responsive-product-image', 13 | image: product.featured_image %} 14 | ``` 15 | 16 | 2. Copy `responsive-images.js` to `assets/` and include it with your other scripts (usually at the bottom of `theme.liquid`): 17 | 18 | ```ruby 19 | {{ 'responsive-images.js' | asset_url | script_tag }} 20 | ``` 21 | 22 | That will output: 23 | 24 | ```html 25 | Product title 36 | ``` 37 | 38 | Advanced usage 39 | --- 40 | 41 | The snippet above is fine, but you'll get better results by estimating how much real estate your images will occupy at specific screen sizes. 42 | 43 | For example, let's assume your theme has a typical responsive product grid layout that changes depending on the browser size: 44 | 45 | | Screen size | Grid columns | % of browser width | 46 | |--------|--------------:|--------------------:| 47 | |Mobile (<= 480px)|1|100%| 48 | |Tablet (> 480px and <= 768px)|2|50%| 49 | |Desktop (> 768px)|4|25%| 50 | 51 | You would give the `sizes` option this list of media queries (`vw` stands for viewport width): 52 | 53 | ```ruby 54 | {% include 'responsive-product-image', 55 | image: product.featured_image, 56 | sizes: '(max-width: 480px) 100vw, (min-width: 481px) and (max-width: 768px) 50vw, 25vw'%} 57 | ``` 58 | 59 | More about `srcset` and `sizes`: 60 | - https://dev.opera.com/articles/native-responsive-images/ 61 | 62 | Options 63 | --- 64 | 65 | | Option | Description | Default | 66 | |--------|-------------|---------| 67 | | `image` | Product image to display | **None (required)** 68 | | `sizes` | Media queries describing how large the image will be at various screen sizes. | `'100vw'` | 69 | | `default_size` | [Shopify image size](https://docs.shopify.com/themes/liquid-documentation/filters/url-filters#size-parameters) to load as default `` src | `'medium'` | 70 | | `attributes` | String of additional HTML attributes for the `` tag | `''` | 71 | 72 | FAQ 73 | --- 74 | 75 | - **Why should I use responsive images?** 76 | 77 | Your site will load faster. With responsive images, the user's web browser chooses to download the most appropriate image size for the device and layout. 78 | 79 | - **What's up with that chunk of Javascript?** 80 | 81 | It's a necessary hack. Shopify unfortunately doesn't tell us the width of an image, only the length of its *longest side*. But `srcset` *must* know an image's width to work accurately. So as a workaround, that bit of Javascript analyzes the image as soon as it loads, determine its actual width, and updates the srcset attribute. 82 | 83 | - **Will this work in all browsers?** 84 | 85 | Yes. However, [native `srcset` support](http://caniuse.com/#feat=srcset) is relatively new, so if you need to support older browsers, then also include the [Picturefill](http://scottjehl.github.io/picturefill/) library. 86 | 87 | Reponsive image resources 88 | --- 89 | 90 | - http://alistapart.com/article/responsive-images-in-practice 91 | - http://ericportis.com/posts/2014/srcset-sizes/ 92 | - http://martinwolf.org/2014/05/07/the-new-srcset-and-sizes-explained/ 93 | - http://dev.opera.com/articles/native-responsive-images/ 94 | --------------------------------------------------------------------------------