├── .babelrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── dist ├── index.js ├── index.js.map ├── index.m.js ├── index.m.js.map ├── index.umd.js └── index.umd.js.map ├── package.json ├── rollup.config.js ├── scripts └── post_install.js ├── src └── index.js └── yarn.lock /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | test: { 4 | plugins: [ 5 | [ 6 | 'istanbul', 7 | { 8 | exclude: ['spec/**/*.js'] 9 | } 10 | ] 11 | ] 12 | } 13 | }, 14 | presets: [ 15 | [ 16 | '@babel/preset-env', 17 | { 18 | targets: { 19 | node: 'current' 20 | } 21 | } 22 | ] 23 | ], 24 | plugins: [ 25 | '@babel/plugin-proposal-class-properties', 26 | '@babel/plugin-transform-classes', 27 | '@babel/plugin-proposal-object-rest-spread' 28 | ] 29 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /node_modules 3 | yarn-error.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .DS_Store 3 | .gitignore 4 | .yarn.lock 5 | /.git 6 | /node_modules 7 | rollup.config.js 8 | .babelrc.js 9 | .prettierrc.json 10 | /.vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 leastbad 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Stimulus Image Grid

2 |

3 | 4 | npm version 5 | 6 |

7 | 8 |

9 | A Stimulus controller for beautiful image grids
10 | Tiny at <100 LOC 11 |

12 | 13 |
14 | 15 | - **Simple**: with only three optional parameters, this is a drop-in, code-free solution 16 | - **Unstyled**: Completely free of CSS opinions 17 | - **Responsive**: Scales to whatever bounding container you give it 18 | - **Backend Agnostic**: 100% client-side 19 | - **Performant AF**: uses the ResizeObserver so there's zero screen flicker 20 | - **Turbolinks**: compatible with Turbolinks by design 21 | - **MIT Licensed**: free for personal and commercial use 22 | 23 | [![Youtube](http://img.youtube.com/vi/e08sFfBYoiE/0.jpg)](http://www.youtube.com/watch?v=e08sFfBYoiE "Stimulus Image Grid") 24 | 25 | ## Built for StimulusJS 26 | 27 | This [Stimulus](https://stimulusjs.org/) controller allows you to make any configurations for the image grid directly with data attributes in your HTML. Once registered in your Stimulus application, you can use it anywhere you like. 28 | 29 | Here is a simple example: 30 | 31 | ```html 32 |
33 | 34 | 35 | 36 | 37 | 38 |
39 | ``` 40 | Yes, that's really it. 41 | 42 | ### Credit where credit is due 43 | 44 | I don't know who wrote the original image-grid.js library. It shipped with a bunch of premium Bootstrap themes, but it relied on jQuery. While stimulus-image-grid is an improvement on the original in several significant ways, the actual meat and potatoes of the algorithm is 100% adapted from the code of a stranger. If you know who wrote [image-grid.js](https://github.com/Pactum/pactum.io/blob/9f2d162bc21f26d62c5d4ba801309bdeb8b9fa9e/v4/js/custom/image-grid.js), please let me know! 45 | 46 | ## Setup 47 | 48 | Note: **stimulus-image-grid requires StimulusJS v2.0+** 49 | 50 | *If you are reading this in the past* (Stimulus 2 isn't out yet) you can change your `stimulus` package in `package.json` to point to [this commit](https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz). 51 | 52 | Add image-grid to your main JS entry point or Stimulus controllers root folder: 53 | 54 | ```js 55 | import { Application } from 'stimulus' 56 | import ImageGrid from 'stimulus-image-grid' 57 | 58 | import { definitionsFromContext } from 'stimulus/webpack-helpers' 59 | const application = Application.start() 60 | const context = require.context('../controllers', true, /\.js$/) 61 | application.load(definitionsFromContext(context)) 62 | 63 | // Manually register ImageGrid as a Stimulus controller 64 | application.register('image-grid', ImageGrid) 65 | ``` 66 | 67 | ## HTML Markup 68 | 69 | For the image grid to work properly, it needs the raw image dimensions. If you know the dimensions at render time, set the `data-width` and `data-height` attributes. Otherwise, the library will calculate the size when it loads. This is slower and could cause a flicker, but it will only happen once - even across Turbolinks visits. 70 | 71 | ```html 72 |
73 | 74 | 75 | 76 | 77 | 78 |
79 | ``` 80 | 81 | The library tries really hard to not be opinionated about HTML structure. The basic idea is that every child of the container element that has the image-grid controller declared upon it will have [zero or one] image(s), somewhere in its DOM hierarchy. So for example, you could have a scenario where images are wrapped in DIV tags and it will find [zero or one] image(s) in the hierarchy: 82 | 83 | ```html 84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 |
92 | 93 |
94 |
95 | 96 |
97 |
98 | 99 |
100 |
101 | ``` 102 | 103 | This library is fully responsive in that it will automatically re-flow the images to the ideal layout in real-time as the container it lives in changes size. If you're using a CSS library such as Bootstrap, this is usually managed with the [responsive breakpoint classes](https://getbootstrap.com/docs/4.4/layout/grid/#grid-options). 104 | 105 | ## Optional Parameters 106 | 107 | There are only three configurable properties, all of which are set on the DOM element using data attributes: 108 | 109 | Property | Default Value 110 | -------- | ------------- 111 | padding | 10 112 | targetHeight | 150 113 | display | inline-block 114 | 115 | ```html 116 |
121 | ... 122 |
123 | ``` 124 | 125 | Padding is applied to the bottom of each image as well as the right edge of each image *that isn't the right-most image of its row*. Target height is applied to the row and you can tweak this value to suit the look of your application. 126 | 127 | ### Obtaining a reference to the Stimulus controller instance 128 | 129 | When you place an image-grid controller on a DOM element, it hangs a variable on the element called `imageGrid` which allows you to access the internal state of the controller. 130 | 131 | The only method you should ever call directly is `processImages()`, which shouldn't be necessary but I'm not a fucking oracle so I've got your back. Here's an example of forcing a grid re-flow on an element with the id `grid`: 132 | 133 | ```javascript 134 | document.getElementById('grid').imageGrid.processImages() 135 | ``` 136 | 137 | ## Contributing 138 | 139 | Bug reports and pull requests are welcome. 140 | 141 | ## License 142 | 143 | This package is available as open source under the terms of the MIT License. -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var stimulus = require('stimulus'); 4 | 5 | function _classCallCheck(instance, Constructor) { 6 | if (!(instance instanceof Constructor)) { 7 | throw new TypeError("Cannot call a class as a function"); 8 | } 9 | } 10 | 11 | function _defineProperties(target, props) { 12 | for (var i = 0; i < props.length; i++) { 13 | var descriptor = props[i]; 14 | descriptor.enumerable = descriptor.enumerable || false; 15 | descriptor.configurable = true; 16 | if ("value" in descriptor) descriptor.writable = true; 17 | Object.defineProperty(target, descriptor.key, descriptor); 18 | } 19 | } 20 | 21 | function _createClass(Constructor, protoProps, staticProps) { 22 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 23 | if (staticProps) _defineProperties(Constructor, staticProps); 24 | return Constructor; 25 | } 26 | 27 | function _defineProperty(obj, key, value) { 28 | if (key in obj) { 29 | Object.defineProperty(obj, key, { 30 | value: value, 31 | enumerable: true, 32 | configurable: true, 33 | writable: true 34 | }); 35 | } else { 36 | obj[key] = value; 37 | } 38 | 39 | return obj; 40 | } 41 | 42 | function _inherits(subClass, superClass) { 43 | if (typeof superClass !== "function" && superClass !== null) { 44 | throw new TypeError("Super expression must either be null or a function"); 45 | } 46 | 47 | subClass.prototype = Object.create(superClass && superClass.prototype, { 48 | constructor: { 49 | value: subClass, 50 | writable: true, 51 | configurable: true 52 | } 53 | }); 54 | if (superClass) _setPrototypeOf(subClass, superClass); 55 | } 56 | 57 | function _getPrototypeOf(o) { 58 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 59 | return o.__proto__ || Object.getPrototypeOf(o); 60 | }; 61 | return _getPrototypeOf(o); 62 | } 63 | 64 | function _setPrototypeOf(o, p) { 65 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 66 | o.__proto__ = p; 67 | return o; 68 | }; 69 | 70 | return _setPrototypeOf(o, p); 71 | } 72 | 73 | function _isNativeReflectConstruct() { 74 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 75 | if (Reflect.construct.sham) return false; 76 | if (typeof Proxy === "function") return true; 77 | 78 | try { 79 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 80 | return true; 81 | } catch (e) { 82 | return false; 83 | } 84 | } 85 | 86 | function _assertThisInitialized(self) { 87 | if (self === void 0) { 88 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 89 | } 90 | 91 | return self; 92 | } 93 | 94 | function _possibleConstructorReturn(self, call) { 95 | if (call && (typeof call === "object" || typeof call === "function")) { 96 | return call; 97 | } 98 | 99 | return _assertThisInitialized(self); 100 | } 101 | 102 | function _createSuper(Derived) { 103 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 104 | 105 | return function () { 106 | var Super = _getPrototypeOf(Derived), 107 | result; 108 | 109 | if (hasNativeReflectConstruct) { 110 | var NewTarget = _getPrototypeOf(this).constructor; 111 | 112 | result = Reflect.construct(Super, arguments, NewTarget); 113 | } else { 114 | result = Super.apply(this, arguments); 115 | } 116 | 117 | return _possibleConstructorReturn(this, result); 118 | }; 119 | } 120 | 121 | let _default = /*#__PURE__*/function (_Controller) { 122 | _inherits(_default, _Controller); 123 | 124 | var _super = _createSuper(_default); 125 | 126 | function _default() { 127 | _classCallCheck(this, _default); 128 | 129 | return _super.apply(this, arguments); 130 | } 131 | 132 | _createClass(_default, [{ 133 | key: "initialize", 134 | value: function initialize() { 135 | this.element['imageGrid'] = this; 136 | Array.prototype.filter.call(this.element.childNodes, node => node.nodeType == 3 && !/\S/.test(node.nodeValue)).forEach(node => node.remove()); 137 | if (!this.hasPaddingValue) this.paddingValue = 10; 138 | if (!this.hasTargetHeightValue) this.targetHeightValue = 150; 139 | if (!this.hasDisplayValue) this.displayValue = 'inline-block'; 140 | this.resizeObserver = new ResizeObserver(this.observed.bind(this)); 141 | } 142 | }, { 143 | key: "observed", 144 | value: function observed(elements) { 145 | this.albumWidth = elements[0].contentRect.width; 146 | this.processImages(); 147 | } 148 | }, { 149 | key: "connect", 150 | value: function connect() { 151 | this.resizeObserver.observe(this.element); 152 | } 153 | }, { 154 | key: "disconnect", 155 | value: function disconnect() { 156 | this.resizeObserver.unobserve(this.element); 157 | } 158 | }, { 159 | key: "processImages", 160 | value: function processImages() { 161 | let row = 0; 162 | this.elements = []; 163 | this.images = Array.from(this.element.children); 164 | this.images.forEach((ele, index) => { 165 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img'); 166 | let width, height; 167 | 168 | if ('width' in image.dataset && 'height' in image.dataset) { 169 | width = image.dataset.width; 170 | height = image.dataset.height; 171 | } else { 172 | const comp = window.getComputedStyle(image); 173 | width = parseFloat(comp.getPropertyValue('width').slice(0, -2)); 174 | height = parseFloat(comp.getPropertyValue('height').slice(0, -2)); 175 | image.dataset.width = width; 176 | image.dataset.height = height; 177 | } 178 | 179 | const idealW = Math.ceil(width / height * this.targetHeightValue); 180 | const idealH = Math.ceil(this.targetHeightValue); 181 | this.elements.push([ele, idealW, idealH]); 182 | row += idealW + this.paddingValue; 183 | 184 | if (row > this.albumWidth && this.elements.length) { 185 | this.resizeRow(row - this.paddingValue); 186 | row = 0; 187 | this.elements = []; 188 | } 189 | 190 | if (this.images.length - 1 == index && this.elements.length) { 191 | this.resizeRow(row); 192 | row = 0; 193 | this.elements = []; 194 | } 195 | }, this); 196 | } 197 | }, { 198 | key: "resizeRow", 199 | value: function resizeRow(row) { 200 | const imageExtras = this.paddingValue * (this.elements.length - 1); 201 | const albumWidthAdjusted = this.albumWidth - imageExtras; 202 | const overPercent = albumWidthAdjusted / (row - imageExtras); 203 | let trackWidth = imageExtras; 204 | this.elements.forEach((element, index) => { 205 | const [ele, idealW, idealH] = element; 206 | let fw = Math.floor(idealW * overPercent); 207 | let fh = Math.floor(idealH * overPercent); 208 | const isNotLast = index < this.elements.length - 1; 209 | trackWidth += fw; 210 | if (!isNotLast && trackWidth < this.albumWidth) fw += this.albumWidth - trackWidth; 211 | fw--; 212 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img'); 213 | image.style.width = fw + 'px'; 214 | image.style.height = fh + 'px'; 215 | ele.style.marginBottom = this.paddingValue + 'px'; 216 | ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0; 217 | ele.style.display = this.displayValue; 218 | ele.style.verticalAlign = 'bottom'; 219 | }, this); 220 | } 221 | }]); 222 | 223 | return _default; 224 | }(stimulus.Controller); 225 | 226 | _defineProperty(_default, "values", { 227 | padding: Number, 228 | targetHeight: Number, 229 | display: String 230 | }); 231 | 232 | module.exports = _default; 233 | //# sourceMappingURL=index.js.map 234 | -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["import { Controller } from 'stimulus'\r\n\r\nexport default class extends Controller {\r\n static values = {\r\n padding: Number,\r\n targetHeight: Number,\r\n display: String\r\n }\r\n\r\n initialize () {\r\n this.element['imageGrid'] = this\r\n Array.prototype.filter\r\n .call(\r\n this.element.childNodes,\r\n node => node.nodeType == 3 && !/\\S/.test(node.nodeValue)\r\n )\r\n .forEach(node => node.remove())\r\n if (!this.hasPaddingValue) this.paddingValue = 10\r\n if (!this.hasTargetHeightValue) this.targetHeightValue = 150\r\n if (!this.hasDisplayValue) this.displayValue = 'inline-block'\r\n this.resizeObserver = new ResizeObserver(this.observed.bind(this))\r\n }\r\n\r\n observed (elements) {\r\n this.albumWidth = elements[0].contentRect.width\r\n this.processImages()\r\n }\r\n\r\n connect () {\r\n this.resizeObserver.observe(this.element)\r\n }\r\n\r\n disconnect () {\r\n this.resizeObserver.unobserve(this.element)\r\n }\r\n\r\n processImages () {\r\n let row = 0\r\n this.elements = []\r\n this.images = Array.from(this.element.children)\r\n this.images.forEach((ele, index) => {\r\n const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img')\r\n let width, height\r\n if ('width' in image.dataset && 'height' in image.dataset) {\r\n width = image.dataset.width\r\n height = image.dataset.height\r\n } else {\r\n const comp = window.getComputedStyle(image)\r\n width = parseFloat(comp.getPropertyValue('width').slice(0, -2))\r\n height = parseFloat(comp.getPropertyValue('height').slice(0, -2))\r\n image.dataset.width = width\r\n image.dataset.height = height\r\n }\r\n const idealW = Math.ceil((width / height) * this.targetHeightValue)\r\n const idealH = Math.ceil(this.targetHeightValue)\r\n this.elements.push([ele, idealW, idealH])\r\n row += idealW + this.paddingValue\r\n if (row > this.albumWidth && this.elements.length) {\r\n this.resizeRow(row - this.paddingValue)\r\n row = 0\r\n this.elements = []\r\n }\r\n if (this.images.length - 1 == index && this.elements.length) {\r\n this.resizeRow(row)\r\n row = 0\r\n this.elements = []\r\n }\r\n }, this)\r\n }\r\n\r\n resizeRow (row) {\r\n const imageExtras = this.paddingValue * (this.elements.length - 1)\r\n const albumWidthAdjusted = this.albumWidth - imageExtras\r\n const overPercent = albumWidthAdjusted / (row - imageExtras)\r\n let trackWidth = imageExtras\r\n this.elements.forEach((element, index) => {\r\n const [ele, idealW, idealH] = element\r\n let fw = Math.floor(idealW * overPercent)\r\n let fh = Math.floor(idealH * overPercent)\r\n const isNotLast = index < this.elements.length - 1\r\n trackWidth += fw\r\n if (!isNotLast && trackWidth < this.albumWidth)\r\n fw += this.albumWidth - trackWidth\r\n fw--\r\n const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img')\r\n image.style.width = fw + 'px'\r\n image.style.height = fh + 'px'\r\n ele.style.marginBottom = this.paddingValue + 'px'\r\n ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0\r\n ele.style.display = this.displayValue\r\n ele.style.verticalAlign = 'bottom'\r\n }, this)\r\n }\r\n}\r\n"],"names":["element","Array","prototype","filter","call","childNodes","node","nodeType","test","nodeValue","forEach","remove","hasPaddingValue","paddingValue","hasTargetHeightValue","targetHeightValue","hasDisplayValue","displayValue","resizeObserver","ResizeObserver","observed","bind","elements","albumWidth","contentRect","width","processImages","observe","unobserve","row","images","from","children","ele","index","image","nodeName","querySelector","height","dataset","comp","window","getComputedStyle","parseFloat","getPropertyValue","slice","idealW","Math","ceil","idealH","push","length","resizeRow","imageExtras","albumWidthAdjusted","overPercent","trackWidth","fw","floor","fh","isNotLast","style","marginBottom","marginRight","display","verticalAlign","Controller","padding","Number","targetHeight","String"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/index.m.js: -------------------------------------------------------------------------------- 1 | import { Controller } from 'stimulus'; 2 | 3 | function _classCallCheck(instance, Constructor) { 4 | if (!(instance instanceof Constructor)) { 5 | throw new TypeError("Cannot call a class as a function"); 6 | } 7 | } 8 | 9 | function _defineProperties(target, props) { 10 | for (var i = 0; i < props.length; i++) { 11 | var descriptor = props[i]; 12 | descriptor.enumerable = descriptor.enumerable || false; 13 | descriptor.configurable = true; 14 | if ("value" in descriptor) descriptor.writable = true; 15 | Object.defineProperty(target, descriptor.key, descriptor); 16 | } 17 | } 18 | 19 | function _createClass(Constructor, protoProps, staticProps) { 20 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 21 | if (staticProps) _defineProperties(Constructor, staticProps); 22 | return Constructor; 23 | } 24 | 25 | function _defineProperty(obj, key, value) { 26 | if (key in obj) { 27 | Object.defineProperty(obj, key, { 28 | value: value, 29 | enumerable: true, 30 | configurable: true, 31 | writable: true 32 | }); 33 | } else { 34 | obj[key] = value; 35 | } 36 | 37 | return obj; 38 | } 39 | 40 | function _inherits(subClass, superClass) { 41 | if (typeof superClass !== "function" && superClass !== null) { 42 | throw new TypeError("Super expression must either be null or a function"); 43 | } 44 | 45 | subClass.prototype = Object.create(superClass && superClass.prototype, { 46 | constructor: { 47 | value: subClass, 48 | writable: true, 49 | configurable: true 50 | } 51 | }); 52 | if (superClass) _setPrototypeOf(subClass, superClass); 53 | } 54 | 55 | function _getPrototypeOf(o) { 56 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 57 | return o.__proto__ || Object.getPrototypeOf(o); 58 | }; 59 | return _getPrototypeOf(o); 60 | } 61 | 62 | function _setPrototypeOf(o, p) { 63 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 64 | o.__proto__ = p; 65 | return o; 66 | }; 67 | 68 | return _setPrototypeOf(o, p); 69 | } 70 | 71 | function _isNativeReflectConstruct() { 72 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 73 | if (Reflect.construct.sham) return false; 74 | if (typeof Proxy === "function") return true; 75 | 76 | try { 77 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 78 | return true; 79 | } catch (e) { 80 | return false; 81 | } 82 | } 83 | 84 | function _assertThisInitialized(self) { 85 | if (self === void 0) { 86 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 87 | } 88 | 89 | return self; 90 | } 91 | 92 | function _possibleConstructorReturn(self, call) { 93 | if (call && (typeof call === "object" || typeof call === "function")) { 94 | return call; 95 | } 96 | 97 | return _assertThisInitialized(self); 98 | } 99 | 100 | function _createSuper(Derived) { 101 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 102 | 103 | return function () { 104 | var Super = _getPrototypeOf(Derived), 105 | result; 106 | 107 | if (hasNativeReflectConstruct) { 108 | var NewTarget = _getPrototypeOf(this).constructor; 109 | 110 | result = Reflect.construct(Super, arguments, NewTarget); 111 | } else { 112 | result = Super.apply(this, arguments); 113 | } 114 | 115 | return _possibleConstructorReturn(this, result); 116 | }; 117 | } 118 | 119 | let _default = /*#__PURE__*/function (_Controller) { 120 | _inherits(_default, _Controller); 121 | 122 | var _super = _createSuper(_default); 123 | 124 | function _default() { 125 | _classCallCheck(this, _default); 126 | 127 | return _super.apply(this, arguments); 128 | } 129 | 130 | _createClass(_default, [{ 131 | key: "initialize", 132 | value: function initialize() { 133 | this.element['imageGrid'] = this; 134 | Array.prototype.filter.call(this.element.childNodes, node => node.nodeType == 3 && !/\S/.test(node.nodeValue)).forEach(node => node.remove()); 135 | if (!this.hasPaddingValue) this.paddingValue = 10; 136 | if (!this.hasTargetHeightValue) this.targetHeightValue = 150; 137 | if (!this.hasDisplayValue) this.displayValue = 'inline-block'; 138 | this.resizeObserver = new ResizeObserver(this.observed.bind(this)); 139 | } 140 | }, { 141 | key: "observed", 142 | value: function observed(elements) { 143 | this.albumWidth = elements[0].contentRect.width; 144 | this.processImages(); 145 | } 146 | }, { 147 | key: "connect", 148 | value: function connect() { 149 | this.resizeObserver.observe(this.element); 150 | } 151 | }, { 152 | key: "disconnect", 153 | value: function disconnect() { 154 | this.resizeObserver.unobserve(this.element); 155 | } 156 | }, { 157 | key: "processImages", 158 | value: function processImages() { 159 | let row = 0; 160 | this.elements = []; 161 | this.images = Array.from(this.element.children); 162 | this.images.forEach((ele, index) => { 163 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img'); 164 | let width, height; 165 | 166 | if ('width' in image.dataset && 'height' in image.dataset) { 167 | width = image.dataset.width; 168 | height = image.dataset.height; 169 | } else { 170 | const comp = window.getComputedStyle(image); 171 | width = parseFloat(comp.getPropertyValue('width').slice(0, -2)); 172 | height = parseFloat(comp.getPropertyValue('height').slice(0, -2)); 173 | image.dataset.width = width; 174 | image.dataset.height = height; 175 | } 176 | 177 | const idealW = Math.ceil(width / height * this.targetHeightValue); 178 | const idealH = Math.ceil(this.targetHeightValue); 179 | this.elements.push([ele, idealW, idealH]); 180 | row += idealW + this.paddingValue; 181 | 182 | if (row > this.albumWidth && this.elements.length) { 183 | this.resizeRow(row - this.paddingValue); 184 | row = 0; 185 | this.elements = []; 186 | } 187 | 188 | if (this.images.length - 1 == index && this.elements.length) { 189 | this.resizeRow(row); 190 | row = 0; 191 | this.elements = []; 192 | } 193 | }, this); 194 | } 195 | }, { 196 | key: "resizeRow", 197 | value: function resizeRow(row) { 198 | const imageExtras = this.paddingValue * (this.elements.length - 1); 199 | const albumWidthAdjusted = this.albumWidth - imageExtras; 200 | const overPercent = albumWidthAdjusted / (row - imageExtras); 201 | let trackWidth = imageExtras; 202 | this.elements.forEach((element, index) => { 203 | const [ele, idealW, idealH] = element; 204 | let fw = Math.floor(idealW * overPercent); 205 | let fh = Math.floor(idealH * overPercent); 206 | const isNotLast = index < this.elements.length - 1; 207 | trackWidth += fw; 208 | if (!isNotLast && trackWidth < this.albumWidth) fw += this.albumWidth - trackWidth; 209 | fw--; 210 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img'); 211 | image.style.width = fw + 'px'; 212 | image.style.height = fh + 'px'; 213 | ele.style.marginBottom = this.paddingValue + 'px'; 214 | ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0; 215 | ele.style.display = this.displayValue; 216 | ele.style.verticalAlign = 'bottom'; 217 | }, this); 218 | } 219 | }]); 220 | 221 | return _default; 222 | }(Controller); 223 | 224 | _defineProperty(_default, "values", { 225 | padding: Number, 226 | targetHeight: Number, 227 | display: String 228 | }); 229 | 230 | export default _default; 231 | //# sourceMappingURL=index.m.js.map 232 | -------------------------------------------------------------------------------- /dist/index.m.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.m.js","sources":["../src/index.js"],"sourcesContent":["import { Controller } from 'stimulus'\r\n\r\nexport default class extends Controller {\r\n static values = {\r\n padding: Number,\r\n targetHeight: Number,\r\n display: String\r\n }\r\n\r\n initialize () {\r\n this.element['imageGrid'] = this\r\n Array.prototype.filter\r\n .call(\r\n this.element.childNodes,\r\n node => node.nodeType == 3 && !/\\S/.test(node.nodeValue)\r\n )\r\n .forEach(node => node.remove())\r\n if (!this.hasPaddingValue) this.paddingValue = 10\r\n if (!this.hasTargetHeightValue) this.targetHeightValue = 150\r\n if (!this.hasDisplayValue) this.displayValue = 'inline-block'\r\n this.resizeObserver = new ResizeObserver(this.observed.bind(this))\r\n }\r\n\r\n observed (elements) {\r\n this.albumWidth = elements[0].contentRect.width\r\n this.processImages()\r\n }\r\n\r\n connect () {\r\n this.resizeObserver.observe(this.element)\r\n }\r\n\r\n disconnect () {\r\n this.resizeObserver.unobserve(this.element)\r\n }\r\n\r\n processImages () {\r\n let row = 0\r\n this.elements = []\r\n this.images = Array.from(this.element.children)\r\n this.images.forEach((ele, index) => {\r\n const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img')\r\n let width, height\r\n if ('width' in image.dataset && 'height' in image.dataset) {\r\n width = image.dataset.width\r\n height = image.dataset.height\r\n } else {\r\n const comp = window.getComputedStyle(image)\r\n width = parseFloat(comp.getPropertyValue('width').slice(0, -2))\r\n height = parseFloat(comp.getPropertyValue('height').slice(0, -2))\r\n image.dataset.width = width\r\n image.dataset.height = height\r\n }\r\n const idealW = Math.ceil((width / height) * this.targetHeightValue)\r\n const idealH = Math.ceil(this.targetHeightValue)\r\n this.elements.push([ele, idealW, idealH])\r\n row += idealW + this.paddingValue\r\n if (row > this.albumWidth && this.elements.length) {\r\n this.resizeRow(row - this.paddingValue)\r\n row = 0\r\n this.elements = []\r\n }\r\n if (this.images.length - 1 == index && this.elements.length) {\r\n this.resizeRow(row)\r\n row = 0\r\n this.elements = []\r\n }\r\n }, this)\r\n }\r\n\r\n resizeRow (row) {\r\n const imageExtras = this.paddingValue * (this.elements.length - 1)\r\n const albumWidthAdjusted = this.albumWidth - imageExtras\r\n const overPercent = albumWidthAdjusted / (row - imageExtras)\r\n let trackWidth = imageExtras\r\n this.elements.forEach((element, index) => {\r\n const [ele, idealW, idealH] = element\r\n let fw = Math.floor(idealW * overPercent)\r\n let fh = Math.floor(idealH * overPercent)\r\n const isNotLast = index < this.elements.length - 1\r\n trackWidth += fw\r\n if (!isNotLast && trackWidth < this.albumWidth)\r\n fw += this.albumWidth - trackWidth\r\n fw--\r\n const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img')\r\n image.style.width = fw + 'px'\r\n image.style.height = fh + 'px'\r\n ele.style.marginBottom = this.paddingValue + 'px'\r\n ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0\r\n ele.style.display = this.displayValue\r\n ele.style.verticalAlign = 'bottom'\r\n }, this)\r\n }\r\n}\r\n"],"names":["element","Array","prototype","filter","call","childNodes","node","nodeType","test","nodeValue","forEach","remove","hasPaddingValue","paddingValue","hasTargetHeightValue","targetHeightValue","hasDisplayValue","displayValue","resizeObserver","ResizeObserver","observed","bind","elements","albumWidth","contentRect","width","processImages","observe","unobserve","row","images","from","children","ele","index","image","nodeName","querySelector","height","dataset","comp","window","getComputedStyle","parseFloat","getPropertyValue","slice","idealW","Math","ceil","idealH","push","length","resizeRow","imageExtras","albumWidthAdjusted","overPercent","trackWidth","fw","floor","fh","isNotLast","style","marginBottom","marginRight","display","verticalAlign","Controller","padding","Number","targetHeight","String"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/index.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('stimulus')) : 3 | typeof define === 'function' && define.amd ? define(['stimulus'], factory) : 4 | (global = global || self, global['stimulus-image-grid'] = factory(global.Stimulus)); 5 | }(this, (function (stimulus) { 'use strict'; 6 | 7 | function _classCallCheck(instance, Constructor) { 8 | if (!(instance instanceof Constructor)) { 9 | throw new TypeError("Cannot call a class as a function"); 10 | } 11 | } 12 | 13 | function _defineProperties(target, props) { 14 | for (var i = 0; i < props.length; i++) { 15 | var descriptor = props[i]; 16 | descriptor.enumerable = descriptor.enumerable || false; 17 | descriptor.configurable = true; 18 | if ("value" in descriptor) descriptor.writable = true; 19 | Object.defineProperty(target, descriptor.key, descriptor); 20 | } 21 | } 22 | 23 | function _createClass(Constructor, protoProps, staticProps) { 24 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 25 | if (staticProps) _defineProperties(Constructor, staticProps); 26 | return Constructor; 27 | } 28 | 29 | function _defineProperty(obj, key, value) { 30 | if (key in obj) { 31 | Object.defineProperty(obj, key, { 32 | value: value, 33 | enumerable: true, 34 | configurable: true, 35 | writable: true 36 | }); 37 | } else { 38 | obj[key] = value; 39 | } 40 | 41 | return obj; 42 | } 43 | 44 | function _inherits(subClass, superClass) { 45 | if (typeof superClass !== "function" && superClass !== null) { 46 | throw new TypeError("Super expression must either be null or a function"); 47 | } 48 | 49 | subClass.prototype = Object.create(superClass && superClass.prototype, { 50 | constructor: { 51 | value: subClass, 52 | writable: true, 53 | configurable: true 54 | } 55 | }); 56 | if (superClass) _setPrototypeOf(subClass, superClass); 57 | } 58 | 59 | function _getPrototypeOf(o) { 60 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 61 | return o.__proto__ || Object.getPrototypeOf(o); 62 | }; 63 | return _getPrototypeOf(o); 64 | } 65 | 66 | function _setPrototypeOf(o, p) { 67 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 68 | o.__proto__ = p; 69 | return o; 70 | }; 71 | 72 | return _setPrototypeOf(o, p); 73 | } 74 | 75 | function _isNativeReflectConstruct() { 76 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 77 | if (Reflect.construct.sham) return false; 78 | if (typeof Proxy === "function") return true; 79 | 80 | try { 81 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 82 | return true; 83 | } catch (e) { 84 | return false; 85 | } 86 | } 87 | 88 | function _assertThisInitialized(self) { 89 | if (self === void 0) { 90 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 91 | } 92 | 93 | return self; 94 | } 95 | 96 | function _possibleConstructorReturn(self, call) { 97 | if (call && (typeof call === "object" || typeof call === "function")) { 98 | return call; 99 | } 100 | 101 | return _assertThisInitialized(self); 102 | } 103 | 104 | function _createSuper(Derived) { 105 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 106 | 107 | return function () { 108 | var Super = _getPrototypeOf(Derived), 109 | result; 110 | 111 | if (hasNativeReflectConstruct) { 112 | var NewTarget = _getPrototypeOf(this).constructor; 113 | 114 | result = Reflect.construct(Super, arguments, NewTarget); 115 | } else { 116 | result = Super.apply(this, arguments); 117 | } 118 | 119 | return _possibleConstructorReturn(this, result); 120 | }; 121 | } 122 | 123 | let _default = /*#__PURE__*/function (_Controller) { 124 | _inherits(_default, _Controller); 125 | 126 | var _super = _createSuper(_default); 127 | 128 | function _default() { 129 | _classCallCheck(this, _default); 130 | 131 | return _super.apply(this, arguments); 132 | } 133 | 134 | _createClass(_default, [{ 135 | key: "initialize", 136 | value: function initialize() { 137 | this.element['imageGrid'] = this; 138 | Array.prototype.filter.call(this.element.childNodes, node => node.nodeType == 3 && !/\S/.test(node.nodeValue)).forEach(node => node.remove()); 139 | if (!this.hasPaddingValue) this.paddingValue = 10; 140 | if (!this.hasTargetHeightValue) this.targetHeightValue = 150; 141 | if (!this.hasDisplayValue) this.displayValue = 'inline-block'; 142 | this.resizeObserver = new ResizeObserver(this.observed.bind(this)); 143 | } 144 | }, { 145 | key: "observed", 146 | value: function observed(elements) { 147 | this.albumWidth = elements[0].contentRect.width; 148 | this.processImages(); 149 | } 150 | }, { 151 | key: "connect", 152 | value: function connect() { 153 | this.resizeObserver.observe(this.element); 154 | } 155 | }, { 156 | key: "disconnect", 157 | value: function disconnect() { 158 | this.resizeObserver.unobserve(this.element); 159 | } 160 | }, { 161 | key: "processImages", 162 | value: function processImages() { 163 | let row = 0; 164 | this.elements = []; 165 | this.images = Array.from(this.element.children); 166 | this.images.forEach((ele, index) => { 167 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img'); 168 | let width, height; 169 | 170 | if ('width' in image.dataset && 'height' in image.dataset) { 171 | width = image.dataset.width; 172 | height = image.dataset.height; 173 | } else { 174 | const comp = window.getComputedStyle(image); 175 | width = parseFloat(comp.getPropertyValue('width').slice(0, -2)); 176 | height = parseFloat(comp.getPropertyValue('height').slice(0, -2)); 177 | image.dataset.width = width; 178 | image.dataset.height = height; 179 | } 180 | 181 | const idealW = Math.ceil(width / height * this.targetHeightValue); 182 | const idealH = Math.ceil(this.targetHeightValue); 183 | this.elements.push([ele, idealW, idealH]); 184 | row += idealW + this.paddingValue; 185 | 186 | if (row > this.albumWidth && this.elements.length) { 187 | this.resizeRow(row - this.paddingValue); 188 | row = 0; 189 | this.elements = []; 190 | } 191 | 192 | if (this.images.length - 1 == index && this.elements.length) { 193 | this.resizeRow(row); 194 | row = 0; 195 | this.elements = []; 196 | } 197 | }, this); 198 | } 199 | }, { 200 | key: "resizeRow", 201 | value: function resizeRow(row) { 202 | const imageExtras = this.paddingValue * (this.elements.length - 1); 203 | const albumWidthAdjusted = this.albumWidth - imageExtras; 204 | const overPercent = albumWidthAdjusted / (row - imageExtras); 205 | let trackWidth = imageExtras; 206 | this.elements.forEach((element, index) => { 207 | const [ele, idealW, idealH] = element; 208 | let fw = Math.floor(idealW * overPercent); 209 | let fh = Math.floor(idealH * overPercent); 210 | const isNotLast = index < this.elements.length - 1; 211 | trackWidth += fw; 212 | if (!isNotLast && trackWidth < this.albumWidth) fw += this.albumWidth - trackWidth; 213 | fw--; 214 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img'); 215 | image.style.width = fw + 'px'; 216 | image.style.height = fh + 'px'; 217 | ele.style.marginBottom = this.paddingValue + 'px'; 218 | ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0; 219 | ele.style.display = this.displayValue; 220 | ele.style.verticalAlign = 'bottom'; 221 | }, this); 222 | } 223 | }]); 224 | 225 | return _default; 226 | }(stimulus.Controller); 227 | 228 | _defineProperty(_default, "values", { 229 | padding: Number, 230 | targetHeight: Number, 231 | display: String 232 | }); 233 | 234 | return _default; 235 | 236 | }))); 237 | //# sourceMappingURL=index.umd.js.map 238 | -------------------------------------------------------------------------------- /dist/index.umd.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.umd.js","sources":["../src/index.js"],"sourcesContent":["import { Controller } from 'stimulus'\r\n\r\nexport default class extends Controller {\r\n static values = {\r\n padding: Number,\r\n targetHeight: Number,\r\n display: String\r\n }\r\n\r\n initialize () {\r\n this.element['imageGrid'] = this\r\n Array.prototype.filter\r\n .call(\r\n this.element.childNodes,\r\n node => node.nodeType == 3 && !/\\S/.test(node.nodeValue)\r\n )\r\n .forEach(node => node.remove())\r\n if (!this.hasPaddingValue) this.paddingValue = 10\r\n if (!this.hasTargetHeightValue) this.targetHeightValue = 150\r\n if (!this.hasDisplayValue) this.displayValue = 'inline-block'\r\n this.resizeObserver = new ResizeObserver(this.observed.bind(this))\r\n }\r\n\r\n observed (elements) {\r\n this.albumWidth = elements[0].contentRect.width\r\n this.processImages()\r\n }\r\n\r\n connect () {\r\n this.resizeObserver.observe(this.element)\r\n }\r\n\r\n disconnect () {\r\n this.resizeObserver.unobserve(this.element)\r\n }\r\n\r\n processImages () {\r\n let row = 0\r\n this.elements = []\r\n this.images = Array.from(this.element.children)\r\n this.images.forEach((ele, index) => {\r\n const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img')\r\n let width, height\r\n if ('width' in image.dataset && 'height' in image.dataset) {\r\n width = image.dataset.width\r\n height = image.dataset.height\r\n } else {\r\n const comp = window.getComputedStyle(image)\r\n width = parseFloat(comp.getPropertyValue('width').slice(0, -2))\r\n height = parseFloat(comp.getPropertyValue('height').slice(0, -2))\r\n image.dataset.width = width\r\n image.dataset.height = height\r\n }\r\n const idealW = Math.ceil((width / height) * this.targetHeightValue)\r\n const idealH = Math.ceil(this.targetHeightValue)\r\n this.elements.push([ele, idealW, idealH])\r\n row += idealW + this.paddingValue\r\n if (row > this.albumWidth && this.elements.length) {\r\n this.resizeRow(row - this.paddingValue)\r\n row = 0\r\n this.elements = []\r\n }\r\n if (this.images.length - 1 == index && this.elements.length) {\r\n this.resizeRow(row)\r\n row = 0\r\n this.elements = []\r\n }\r\n }, this)\r\n }\r\n\r\n resizeRow (row) {\r\n const imageExtras = this.paddingValue * (this.elements.length - 1)\r\n const albumWidthAdjusted = this.albumWidth - imageExtras\r\n const overPercent = albumWidthAdjusted / (row - imageExtras)\r\n let trackWidth = imageExtras\r\n this.elements.forEach((element, index) => {\r\n const [ele, idealW, idealH] = element\r\n let fw = Math.floor(idealW * overPercent)\r\n let fh = Math.floor(idealH * overPercent)\r\n const isNotLast = index < this.elements.length - 1\r\n trackWidth += fw\r\n if (!isNotLast && trackWidth < this.albumWidth)\r\n fw += this.albumWidth - trackWidth\r\n fw--\r\n const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img')\r\n image.style.width = fw + 'px'\r\n image.style.height = fh + 'px'\r\n ele.style.marginBottom = this.paddingValue + 'px'\r\n ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0\r\n ele.style.display = this.displayValue\r\n ele.style.verticalAlign = 'bottom'\r\n }, this)\r\n }\r\n}\r\n"],"names":["element","Array","prototype","filter","call","childNodes","node","nodeType","test","nodeValue","forEach","remove","hasPaddingValue","paddingValue","hasTargetHeightValue","targetHeightValue","hasDisplayValue","displayValue","resizeObserver","ResizeObserver","observed","bind","elements","albumWidth","contentRect","width","processImages","observe","unobserve","row","images","from","children","ele","index","image","nodeName","querySelector","height","dataset","comp","window","getComputedStyle","parseFloat","getPropertyValue","slice","idealW","Math","ceil","idealH","push","length","resizeRow","imageExtras","albumWidthAdjusted","overPercent","trackWidth","fw","floor","fh","isNotLast","style","marginBottom","marginRight","display","verticalAlign","Controller","padding","Number","targetHeight","String"],"mappings":"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"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stimulus-image-grid", 3 | "version": "1.0.3", 4 | "description": "A Stimulus controller for beautiful responsive image grids", 5 | "keywords": [ 6 | "stimulus", 7 | "stimulusjs", 8 | "bootstrap", 9 | "image", 10 | "grid", 11 | "responsive" 12 | ], 13 | "main": "dist/index.js", 14 | "umd:main": "dist/index.umd.js", 15 | "module": "dist/index.m.js", 16 | "source": "src/index.js", 17 | "author": "@leastbad", 18 | "license": "MIT", 19 | "external": "stimulus", 20 | "scripts": { 21 | "postinstall": "node scripts/post_install.js", 22 | "prettier-standard:check": "yarn run prettier-standard --check *.js **/*.js", 23 | "prettier-standard:format": "yarn run prettier-standard *.js **/*.js", 24 | "build": "rollup -c", 25 | "dev": "rollup -wc", 26 | "release": "np" 27 | }, 28 | "homepage": "https://leastbad.com/", 29 | "bugs": { 30 | "url": "https://github.com/leastbad/stimulus-image-grid/issues" 31 | }, 32 | "dependencies": { 33 | "stimulus": ">=2.0.0" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.6.2", 37 | "@babel/plugin-proposal-class-properties": "^7.3.4", 38 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5", 39 | "@babel/plugin-transform-classes": "^7.3.4", 40 | "@babel/plugin-transform-spread": "^7.2.2", 41 | "@babel/preset-env": "^7.6.2", 42 | "np": "^5.1.3", 43 | "prettier-standard": "^16.1.0", 44 | "rollup": "^1.20.3", 45 | "rollup-plugin-babel": "^4.3.2", 46 | "rollup-plugin-filesize": "^6.0.1", 47 | "rollup-plugin-node-resolve": "^5.2.0" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "https://github.com/leastbad/stimulus-image-grid.git" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import filesize from 'rollup-plugin-filesize' 2 | import resolve from 'rollup-plugin-node-resolve' 3 | import babel from 'rollup-plugin-babel' 4 | 5 | const pkg = require('./package.json') 6 | 7 | const name = pkg.name 8 | 9 | export default { 10 | input: 'src/index.js', 11 | external: ['stimulus'], 12 | output: [ 13 | { 14 | file: 'dist/index.js', 15 | format: 'cjs', 16 | sourcemap: true 17 | }, 18 | { 19 | file: 'dist/index.m.js', 20 | format: 'es', 21 | sourcemap: true 22 | }, 23 | { 24 | file: 'dist/index.umd.js', 25 | format: 'umd', 26 | name, 27 | sourcemap: true, 28 | globals: { 29 | stimulus: 'Stimulus' 30 | } 31 | } 32 | ], 33 | plugins: [resolve(), babel(), filesize()] 34 | } 35 | -------------------------------------------------------------------------------- /scripts/post_install.js: -------------------------------------------------------------------------------- 1 | console.log( 2 | 'Registering a Stimulus controller for use is easy!\n\n' + 3 | '1. Open index.js in your controllers folder.\n' + 4 | '2. Add the following import:\n' + 5 | " import ImageGrid from 'stimulus-image-grid'\n" + 6 | '3. Register the image-grid controller at the bottom:\n' + 7 | " application.register('image-grid', ImageGrid)\n" 8 | ) 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Controller } from 'stimulus' 2 | 3 | export default class extends Controller { 4 | static values = { 5 | padding: Number, 6 | targetHeight: Number, 7 | display: String 8 | } 9 | 10 | initialize () { 11 | this.element['imageGrid'] = this 12 | Array.prototype.filter 13 | .call( 14 | this.element.childNodes, 15 | node => node.nodeType == 3 && !/\S/.test(node.nodeValue) 16 | ) 17 | .forEach(node => node.remove()) 18 | if (!this.hasPaddingValue) this.paddingValue = 10 19 | if (!this.hasTargetHeightValue) this.targetHeightValue = 150 20 | if (!this.hasDisplayValue) this.displayValue = 'inline-block' 21 | this.resizeObserver = new ResizeObserver(this.observed.bind(this)) 22 | } 23 | 24 | observed (elements) { 25 | this.albumWidth = elements[0].contentRect.width 26 | this.processImages() 27 | } 28 | 29 | connect () { 30 | this.resizeObserver.observe(this.element) 31 | } 32 | 33 | disconnect () { 34 | this.resizeObserver.unobserve(this.element) 35 | } 36 | 37 | processImages () { 38 | let row = 0 39 | this.elements = [] 40 | this.images = Array.from(this.element.children) 41 | this.images.forEach((ele, index) => { 42 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img') 43 | let width, height 44 | if ('width' in image.dataset && 'height' in image.dataset) { 45 | width = image.dataset.width 46 | height = image.dataset.height 47 | } else { 48 | const comp = window.getComputedStyle(image) 49 | width = parseFloat(comp.getPropertyValue('width').slice(0, -2)) 50 | height = parseFloat(comp.getPropertyValue('height').slice(0, -2)) 51 | image.dataset.width = width 52 | image.dataset.height = height 53 | } 54 | const idealW = Math.ceil((width / height) * this.targetHeightValue) 55 | const idealH = Math.ceil(this.targetHeightValue) 56 | this.elements.push([ele, idealW, idealH]) 57 | row += idealW + this.paddingValue 58 | if (row > this.albumWidth && this.elements.length) { 59 | this.resizeRow(row - this.paddingValue) 60 | row = 0 61 | this.elements = [] 62 | } 63 | if (this.images.length - 1 == index && this.elements.length) { 64 | this.resizeRow(row) 65 | row = 0 66 | this.elements = [] 67 | } 68 | }, this) 69 | } 70 | 71 | resizeRow (row) { 72 | const imageExtras = this.paddingValue * (this.elements.length - 1) 73 | const albumWidthAdjusted = this.albumWidth - imageExtras 74 | const overPercent = albumWidthAdjusted / (row - imageExtras) 75 | let trackWidth = imageExtras 76 | this.elements.forEach((element, index) => { 77 | const [ele, idealW, idealH] = element 78 | let fw = Math.floor(idealW * overPercent) 79 | let fh = Math.floor(idealH * overPercent) 80 | const isNotLast = index < this.elements.length - 1 81 | trackWidth += fw 82 | if (!isNotLast && trackWidth < this.albumWidth) 83 | fw += this.albumWidth - trackWidth 84 | fw-- 85 | const image = ele.nodeName === 'IMG' ? ele : ele.querySelector('img') 86 | image.style.width = fw + 'px' 87 | image.style.height = fh + 'px' 88 | ele.style.marginBottom = this.paddingValue + 'px' 89 | ele.style.marginRight = isNotLast ? this.paddingValue + 'px' : 0 90 | ele.style.display = this.displayValue 91 | ele.style.verticalAlign = 'bottom' 92 | }, this) 93 | } 94 | } 95 | --------------------------------------------------------------------------------