diff --git a/README.md b/README.md index 35aca76..aad242b 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ const responsiveImage = require('responsive?sizes[]=100,sizes[]=200,sizes[]=300! // responsiveImage.images => [{height: 50, path: '2fefae46cb857bc750fa5e5eed4a0cde-100.jpg', width: 100}, {height: 100, path: '2fefae46cb857bc750fa5e5eed4a0cde-200.jpg', width: 200}, {height: 150, path: '2fefae46cb857bc750fa5e5eed4a0cde-300.jpg', width: 300}] // responsiveImage.src => '2fefae46cb857bc750fa5e5eed4a0cde-100.jpg' // responsiveImage.toString() => '2fefae46cb857bc750fa5e5eed4a0cde-100.jpg' -React.render(, el); +ReactDOM.render(, el); // Or you can just use it as props, `srcSet` and `src` will be set properly -React.render(, el); +ReactDOM.render(, el); ``` Or use it in CSS (only the first resized image will be used, if you use multiple `sizes`): @@ -38,13 +38,32 @@ Or use it in CSS (only the first resized image will be used, if you use multiple } ``` +```js +// Outputs placeholder image as a data URI, and three images with 100, 200, and 300px widths +const responsiveImage = require('responsive?placeholder=true&sizes[]=100,sizes[]=200,sizes[]=300!myImage.jpg'); + +// responsiveImage.placeholder => 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAIBAQE…' +ReactDOM.render( +
+ +
, el); +``` + + ### Options -- `sizes: array`: specify all widths you want to use; if a specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up). You may also declare a default `sizes` array in `responsiveLoader` in your `webpack.config.js`. -- `size: integer`: specify one width you want to use; if the specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up) -- `quality: integer`: JPEG compression quality; defaults to `95` -- `ext: string`: either `png`, `jpg`, or `gif`; use to convert to another format; defaults to original file's extension -- `background: hex`: Background fill when converting transparent to opaque images; defaults to `0xFFFFFFFF` (note: make sure this is a valid hex number) +- `sizes: array` — specify all widths you want to use; if a specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up). You may also declare a default `sizes` array in `responsiveLoader` in your `webpack.config.js`. +- `size: integer` — specify one width you want to use; if the specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up) +- `quality: integer` — JPEG compression quality; defaults to `95` +- `ext: string` — either `png`, `jpg`, or `gif`; use to convert to another format; defaults to original file's extension +- `background: hex` — Background fill when converting transparent to opaque images; defaults to `0xFFFFFFFF` (note: make sure this is a valid hex number) +- `placeholder: bool` — A true or false value to specify wether to output a placeholder image as a data URI. (Defaults to `false`) +- `placeholderSize: integer` — A number value specifying the width of the placeholder image, if enabled with the option above. (Defaults to `40`) ### Examples @@ -62,7 +81,9 @@ module.exports = { ]} }, responsiveLoader: { - sizes: [300, 600, 1200, 2000] + sizes: [300, 600, 1200, 2000], + placeholder: true, + placeholderSize: 50 } } ``` diff --git a/index.js b/index.js index 101dd98..9632939 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,8 @@ module.exports = function loader(content) { const sizes = query.sizes || query.size || options.sizes || [Number.MAX_SAFE_INTEGER]; const name = query.name || options.name || '[hash]-[width].'; const outputContext = query.context || options.context || ''; + const outputPlaceholder = query.placeholder || query.placeholder !== false && options.placeholder || false; + const placeholderSize = query.placeholderSize || options.placeholderSize || 40; // JPEG compression const quality = parseInt(query.quality, 10) || options.quality || 95; // Useful when converting from PNG to JPG @@ -87,14 +89,46 @@ module.exports = function loader(content) { } }); + if (outputPlaceholder) { + q.defer(function generatePlaceholder(queueCallback) { + img + .clone() + .resize(placeholderSize, jimp.AUTO) + .quality(quality) + .background(background) + .getBuffer(mime, function resizeCallback(queueErr, buf) { + if (err) { + return queueCallback(queueErr); + } + + const placeholder = buf.toString('base64'); + return queueCallback(null, JSON.stringify('data:' + (mime ? mime + ';' : '') + 'base64,' + placeholder)); + }); + }); + } + return q.awaitAll((queueErr, files) => { + 'use strict'; // eslint-disable-line + let placeholder; + if (outputPlaceholder) { + placeholder = files.pop(); + } + const srcset = files.map(f => f.src).join('+","+'); const images = files.map(f => '{path:' + f.path + ',width:' + f.width + ',height:' + f.height + '}').join(','); - const firstImagePath = files[0].path; - - loaderCallback(null, 'module.exports = {srcSet:' + srcset + ',images:[' + images + '],src:' + firstImagePath + ',toString:function(){return ' + firstImagePath + '}};'); + const firstImage = files[0]; + + loaderCallback(null, 'module.exports = {' + + 'srcSet:' + srcset + ',' + + 'images:[' + images + '],' + + 'src:' + firstImage.path + ',' + + 'toString:function(){return ' + firstImage.path + '},' + + 'placeholder: ' + placeholder + ',' + + 'width:' + firstImage.width + ',' + + 'height:' + firstImage.height + + '};'); }); }); }; diff --git a/test/index.js b/test/index.js index 45b20a1..321d9b1 100644 --- a/test/index.js +++ b/test/index.js @@ -46,3 +46,16 @@ test('output should be relative to context', t => { t.equal(multi.toString(), 'foobar/test/9a7bdeb1304946f1fcd4a691409f286a-500.jpg'); t.end(); }); + +test('with placeholder image', t => { + const output = require('../index?placeholder=true!./cat-1000.jpg'); + t.equal(output.placeholder, 'data:image/jpeg;base64,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'); + t.end(); +}); + +test('output first resized image height & width', t => { + const output = require('../index?size=500!./cat-1000.jpg'); + t.equal(output.height, 450); + t.equal(output.width, 500); + t.end(); +});