imgix-optimizer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +19 -0
- data/LICENSE +7 -0
- data/README.md +123 -0
- data/Rakefile +1 -0
- data/dist/imgix-optimizer-0.0.1.min.js +1 -0
- data/dist/imgix-optimizer.js +643 -0
- data/dist/index.html +65 -0
- data/dist/main.css +10 -0
- data/imgix-optimizer.gemspec +17 -0
- data/lib/imgix-optimizer/rails/engine.rb +6 -0
- data/lib/imgix-optimizer/rails.rb +6 -0
- data/lib/imgix-optimizer/version.rb +8 -0
- data/lib/imgix-optimizer.rb +5 -0
- data/package-lock.json +1680 -0
- data/package.json +26 -0
- data/rollup.config.dev.js +6 -0
- data/rollup.config.js +50 -0
- data/src/imgix_bg_image.js +291 -0
- data/src/imgix_image.js +143 -0
- data/src/main.js +9 -0
- data/src/optimizer.js +43 -0
- data/test/test.js +53 -0
- data/vendor/assets/javascripts/.keep +0 -0
- data/vendor/assets/javascripts/imgix-optimizer.js +643 -0
- metadata +84 -0
data/package.json
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
{
|
2
|
+
"name": "imgix-optimizer",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"devDependencies": {
|
5
|
+
"babel-core": "^6.26.3",
|
6
|
+
"babel-preset-es2015-rollup": "^3.0.0",
|
7
|
+
"rollup": "^0.60.0",
|
8
|
+
"rollup-plugin-babel": "^3.0.7",
|
9
|
+
"rollup-plugin-commonjs": "^9.1.0",
|
10
|
+
"rollup-plugin-delete": "^0.1.2",
|
11
|
+
"rollup-plugin-node-resolve": "^3.0.0",
|
12
|
+
"rollup-plugin-serve": "^0.4.2",
|
13
|
+
"rollup-plugin-uglify": "^4.0.0"
|
14
|
+
},
|
15
|
+
"scripts": {
|
16
|
+
"copy": "cp dist/imgix-optimizer.js vendor/assets/javascripts/",
|
17
|
+
"build": "rollup -c && npm run copy",
|
18
|
+
"dev": "rollup -c ./rollup.config.dev.js -w",
|
19
|
+
"test": "node test/test.js",
|
20
|
+
"pretest": "npm run build"
|
21
|
+
},
|
22
|
+
"files": [
|
23
|
+
"dist/*.js"
|
24
|
+
],
|
25
|
+
"license": "MIT"
|
26
|
+
}
|
data/rollup.config.js
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
import pkg from './package.json';
|
2
|
+
|
3
|
+
import babel from 'rollup-plugin-babel';
|
4
|
+
import commonjs from 'rollup-plugin-commonjs';
|
5
|
+
import del from 'rollup-plugin-delete';
|
6
|
+
import resolve from 'rollup-plugin-node-resolve';
|
7
|
+
import { uglify } from 'rollup-plugin-uglify';
|
8
|
+
|
9
|
+
export default [{
|
10
|
+
input: 'src/main.js',
|
11
|
+
output: {
|
12
|
+
name: 'ImgixOptimizer',
|
13
|
+
file: `dist/${pkg.name}.js`,
|
14
|
+
format: 'iife'
|
15
|
+
},
|
16
|
+
plugins: [
|
17
|
+
del({ targets: ['dist/imgix-optimizer*', 'vendor/assets/javascripts/*.js'] }),
|
18
|
+
resolve(),
|
19
|
+
commonjs(),
|
20
|
+
babel({
|
21
|
+
exclude: 'node_modules/**',
|
22
|
+
babelrc: false,
|
23
|
+
presets: [
|
24
|
+
'es2015-rollup'
|
25
|
+
]
|
26
|
+
})
|
27
|
+
]
|
28
|
+
},
|
29
|
+
// --- MINIFIED ---
|
30
|
+
{
|
31
|
+
input: 'src/main.js',
|
32
|
+
output: {
|
33
|
+
name: 'ImgixOptimizer',
|
34
|
+
file: `dist/${pkg.name}-${pkg.version}.min.js`,
|
35
|
+
format: 'iife'
|
36
|
+
},
|
37
|
+
plugins: [
|
38
|
+
resolve(),
|
39
|
+
commonjs(),
|
40
|
+
babel({
|
41
|
+
exclude: 'node_modules/**',
|
42
|
+
babelrc: false,
|
43
|
+
presets: [
|
44
|
+
'es2015-rollup'
|
45
|
+
]
|
46
|
+
}),
|
47
|
+
uglify()
|
48
|
+
]
|
49
|
+
}
|
50
|
+
];
|
@@ -0,0 +1,291 @@
|
|
1
|
+
export default class ImgixBgImage {
|
2
|
+
|
3
|
+
constructor(el) {
|
4
|
+
// Length of time to complete fade-in transition.
|
5
|
+
this.timeToFade = 500;
|
6
|
+
// The primary element (i.e. the one with the background image).
|
7
|
+
this.el = $(el);
|
8
|
+
// Background image CSS property must be present.
|
9
|
+
if (this.el.css('background-image') == 'none') { return }
|
10
|
+
// Prepare the element and its container for optimization.
|
11
|
+
this.initEl();
|
12
|
+
// Kick off the optimization process.
|
13
|
+
this.initOptimization();
|
14
|
+
// Listen for window resize events.
|
15
|
+
this.initEventListeners();
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Load an image in memory (not within the DOM) with the same source as the
|
20
|
+
* placeholder image. Once that has completed, we know we're safe to begin
|
21
|
+
* processing.
|
22
|
+
*/
|
23
|
+
initOptimization() {
|
24
|
+
$('<img>')
|
25
|
+
.on('load', () => this.renderTmpPlaceholderEl())
|
26
|
+
.attr('src', this.placeholderImgUrl);
|
27
|
+
}
|
28
|
+
|
29
|
+
// ---------------------------------------- | Main Element
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Prepare the main element and its container for optimization.
|
33
|
+
*/
|
34
|
+
initEl() {
|
35
|
+
this.setPlaceholderImgUrl();
|
36
|
+
this.setContainerTmpCss();
|
37
|
+
this.setElTmpCss();
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Set reference to original image URL, which is expected to be a small
|
42
|
+
* placeholder.
|
43
|
+
*/
|
44
|
+
setPlaceholderImgUrl() {
|
45
|
+
this.placeholderImgUrl = this.el.css('background-image')
|
46
|
+
.replace('url(', '')
|
47
|
+
.replace(')', '')
|
48
|
+
.replace(/\"/gi, "")
|
49
|
+
.replace(/\'/gi, "")
|
50
|
+
.split(', ')[0];
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* The parent of our jumbotron container should be relatively positioned
|
55
|
+
* (temporarily) so that we can absolutely position the temp image in the
|
56
|
+
* correct location.
|
57
|
+
*/
|
58
|
+
setContainerTmpCss() {
|
59
|
+
this.el.parent().css('position', 'relative');
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* The main element must have a position set for it to be rendered on top of
|
64
|
+
* the temporary full-size image. We assume that if the element is not
|
65
|
+
* explicitly positioned absolutely, then it can safely be positioned
|
66
|
+
* relatively.
|
67
|
+
*/
|
68
|
+
setElTmpCss() {
|
69
|
+
if (this.el.css('position') != 'absolute') {
|
70
|
+
this.el.css('position', 'relative');
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
// ---------------------------------------- | Placeholder Image (Temp)
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Render a clone of the element with the background image directly behind
|
78
|
+
* itself.
|
79
|
+
*/
|
80
|
+
renderTmpPlaceholderEl() {
|
81
|
+
this.initTmpPlaceholderEl();
|
82
|
+
this.setTmpPlaceholderElCss();
|
83
|
+
this.addTmpPlaceholderElToDom();
|
84
|
+
this.renderFullSizeImg();
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Create a clone of the element with the background image. Remove content
|
89
|
+
* from the clone -- often elements with a background image contain content.
|
90
|
+
*/
|
91
|
+
initTmpPlaceholderEl() {
|
92
|
+
this.tmpPlaceholderEl = this.el.clone();
|
93
|
+
this.tmpPlaceholderEl.html('');
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Position the clone directly behind the main element
|
98
|
+
*/
|
99
|
+
setTmpPlaceholderElCss() {
|
100
|
+
this.tmpPlaceholderEl.css({
|
101
|
+
position: 'absolute',
|
102
|
+
top: this.el.position().top,
|
103
|
+
left: this.el.position().left,
|
104
|
+
width: this.el.outerWidth(),
|
105
|
+
height: this.el.outerHeight(),
|
106
|
+
backgroundColor: 'transparent'
|
107
|
+
});
|
108
|
+
}
|
109
|
+
|
110
|
+
/**
|
111
|
+
* Add temporary element to the DOM, directly before the main element
|
112
|
+
* containing the background image.
|
113
|
+
*/
|
114
|
+
addTmpPlaceholderElToDom() {
|
115
|
+
this.tmpPlaceholderEl.insertBefore(this.el);
|
116
|
+
}
|
117
|
+
|
118
|
+
// ---------------------------------------- | Full-Size Image (Temp)
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Create another clone, this time of the temporary placeholder image. This
|
122
|
+
* new element sits behind the other two and is responsible for loading the
|
123
|
+
* full-size image.
|
124
|
+
*/
|
125
|
+
renderFullSizeImg() {
|
126
|
+
this.removeElBgImg();
|
127
|
+
this.initTmpFullSizeEl();
|
128
|
+
this.setTmpFullSizeElImg();
|
129
|
+
this.addTmpFullSizeElToDom();
|
130
|
+
this.initTransition();
|
131
|
+
}
|
132
|
+
|
133
|
+
/**
|
134
|
+
* Remove the background color and image from the main element. The user won't
|
135
|
+
* notice this transition because the temp duplicate image is already set and
|
136
|
+
* is sitting behind the primary element.
|
137
|
+
*
|
138
|
+
* This also stores a reference to the original background color so we can put
|
139
|
+
* it back when the transition is complete.
|
140
|
+
*/
|
141
|
+
removeElBgImg() {
|
142
|
+
this.elBgColor = this.el.css('background-color');
|
143
|
+
this.el.css('background-color', 'transparent');
|
144
|
+
this.el.css('background-image', '');
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* The temporary full-size element is a clone of the temporary placeholder
|
149
|
+
* image element.
|
150
|
+
*/
|
151
|
+
initTmpFullSizeEl() {
|
152
|
+
this.tmpFullSizeEl = this.tmpPlaceholderEl.clone();
|
153
|
+
}
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Sets a reference to the full-size image URL based on the current dimensions
|
157
|
+
* of the main element.
|
158
|
+
*/
|
159
|
+
setFullSizeImgUrl() {
|
160
|
+
// Work with the placeholdler image URL, which has been pulled from the
|
161
|
+
// background-image css property of the main elements.
|
162
|
+
let url = this.placeholderImgUrl.split('?');
|
163
|
+
// q is an array of querystring parameters as ["k=v", "k=v", ...].
|
164
|
+
let q = url[url.length - 1].split('&');
|
165
|
+
// Mapping q converts the array to an object of querystring parameters as
|
166
|
+
// { k: v, k: v, ... }.
|
167
|
+
let args = {};
|
168
|
+
q.map((x) => args[x.split('=')[0]] = x.split('=')[1]);
|
169
|
+
// If the image's container is wider than it is tall, we only set width and
|
170
|
+
// unset height, and vice versa.
|
171
|
+
if (this.el.width() >= this.el.height()) {
|
172
|
+
args['w'] = this.el.width();
|
173
|
+
delete args['h'];
|
174
|
+
} else {
|
175
|
+
args['h'] = this.el.height();
|
176
|
+
delete args['w'];
|
177
|
+
}
|
178
|
+
// Redefine q and go the other direction -- take the args object and convert
|
179
|
+
// it back to an array of querystring parameters, as ["k=v", "k=v", ...].
|
180
|
+
q = [];
|
181
|
+
for (let k in args) { q.push(`${k}=${args[k]}`) }
|
182
|
+
// Store the result and return.
|
183
|
+
return this.fullSizeImgUrl = `${url[0]}?${q.join('&')}`;
|
184
|
+
}
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Change the URL of this temporary element's background image to be the
|
188
|
+
* full-size image.
|
189
|
+
*/
|
190
|
+
setTmpFullSizeElImg() {
|
191
|
+
this.setFullSizeImgUrl();
|
192
|
+
this.tmpFullSizeEl.css('background-image', `url("${this.fullSizeImgUrl}")`);
|
193
|
+
}
|
194
|
+
|
195
|
+
/**
|
196
|
+
* Add the temporary full-size element direct before the temporary placeholder
|
197
|
+
* element.
|
198
|
+
*/
|
199
|
+
addTmpFullSizeElToDom() {
|
200
|
+
this.tmpFullSizeEl.insertBefore(this.tmpPlaceholderEl);
|
201
|
+
}
|
202
|
+
|
203
|
+
// ---------------------------------------- | Transition
|
204
|
+
|
205
|
+
/**
|
206
|
+
* Load full-size image in memory. When it has loaded we can confidentally
|
207
|
+
* fade out the placeholder, knowing the full-size image will be in its place.
|
208
|
+
*/
|
209
|
+
initTransition() {
|
210
|
+
$('<img>')
|
211
|
+
.on('load', $.proxy(this.transitionImg, this))
|
212
|
+
.attr('src', this.fullSizeImgUrl);
|
213
|
+
}
|
214
|
+
|
215
|
+
/**
|
216
|
+
* Fade out the temporary placeholder, set the background-image on the main
|
217
|
+
* element to the full-size URL, then remove the temporary elements behind the
|
218
|
+
* main element
|
219
|
+
*/
|
220
|
+
transitionImg() {
|
221
|
+
this.fadeOutTmpPlaceholderEl();
|
222
|
+
setTimeout(() => {
|
223
|
+
this.updateElImg();
|
224
|
+
this.replaceElTmpCss();
|
225
|
+
this.removeTmpEls();
|
226
|
+
}, this.timeToFade);
|
227
|
+
}
|
228
|
+
|
229
|
+
/**
|
230
|
+
* Fade out the placeholder element. This was the temporary clone of the main
|
231
|
+
* element that has a placeholder background image.
|
232
|
+
*
|
233
|
+
* Rememeber the main element's background image was unset and its color set
|
234
|
+
* to transparent. That is why fading out this temporary image will work
|
235
|
+
* properly.
|
236
|
+
*/
|
237
|
+
fadeOutTmpPlaceholderEl() {
|
238
|
+
this.tmpPlaceholderEl.fadeTo(this.timeToFade, 0);
|
239
|
+
}
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Reset the image URL (this helps if the size of the element has changed),
|
243
|
+
* then set the background image to the new source.
|
244
|
+
*/
|
245
|
+
updateElImg() {
|
246
|
+
this.setFullSizeImgUrl();
|
247
|
+
this.el.css('background-image', `url('${this.fullSizeImgUrl}')`);
|
248
|
+
}
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Set the background color back to what it was before the transition.
|
252
|
+
*/
|
253
|
+
replaceElTmpCss() {
|
254
|
+
this.el.css('background-color', this.elBgColor);
|
255
|
+
}
|
256
|
+
|
257
|
+
/**
|
258
|
+
* Remove both temporary elements from the DOM.
|
259
|
+
*/
|
260
|
+
removeTmpEls() {
|
261
|
+
this.tmpPlaceholderEl.remove();
|
262
|
+
this.tmpFullSizeEl.remove();
|
263
|
+
this.tmpPlaceholderEl = undefined;
|
264
|
+
this.tmpFullSizeEl = undefined;
|
265
|
+
}
|
266
|
+
|
267
|
+
// ---------------------------------------- | Event Listeners
|
268
|
+
|
269
|
+
/**
|
270
|
+
* Listener for window resize events and update the image when the event ends.
|
271
|
+
*/
|
272
|
+
initEventListeners() {
|
273
|
+
this.initResizeEnd();
|
274
|
+
$(window).on('resizeEnd', (event) => this.updateElImg());
|
275
|
+
}
|
276
|
+
|
277
|
+
/**
|
278
|
+
* Trigger "resizeEnd" event on the window object after resizing has ceased
|
279
|
+
* for at least 0.5 seconds.
|
280
|
+
*/
|
281
|
+
initResizeEnd() {
|
282
|
+
$(window).resize(function() {
|
283
|
+
if (this.resizeTo) {
|
284
|
+
clearTimeout(this.resizeTo)
|
285
|
+
}
|
286
|
+
this.resizeTo = setTimeout(function() {
|
287
|
+
$(this).trigger('resizeEnd');
|
288
|
+
}, 500);
|
289
|
+
});
|
290
|
+
}
|
291
|
+
}
|
data/src/imgix_image.js
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
export default class ImgixImage {
|
2
|
+
|
3
|
+
constructor(img) {
|
4
|
+
// Length of crossfade transition.
|
5
|
+
this.timeToFade = 500;
|
6
|
+
// Main (pixellated placeholder) image.
|
7
|
+
this.placeholderImg = $(img);
|
8
|
+
// Kick off the optimization process.
|
9
|
+
this.initOptimization();
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Load an image in memory (not within the DOM) with the same source as the
|
14
|
+
* placeholder image. Once that has completed, we know we're safe to begin
|
15
|
+
* processing.
|
16
|
+
*/
|
17
|
+
initOptimization() {
|
18
|
+
$('<img>')
|
19
|
+
.on('load', $.proxy(this.renderFullSizeImg, this))
|
20
|
+
.attr('src', this.placeholderImg.attr('src'));
|
21
|
+
}
|
22
|
+
|
23
|
+
// ---------------------------------------- | Full-Size Image
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Render the full-size image behind the placeholder image.
|
27
|
+
*/
|
28
|
+
renderFullSizeImg() {
|
29
|
+
this.initFullSizeImg();
|
30
|
+
this.setFullSizeImgTempCss();
|
31
|
+
this.setFullSizeImgSrc();
|
32
|
+
this.addFullSizeImgToDom();
|
33
|
+
this.initTransition();
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* The full-size image is a clone of the placeholder image. This enables us to
|
38
|
+
* easily replace it without losing any necessary styles or attributes.
|
39
|
+
*/
|
40
|
+
initFullSizeImg() {
|
41
|
+
this.fullSizeImg = this.placeholderImg.clone();
|
42
|
+
}
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Give the full-size image a temporary set of CSS rules so that it can sit
|
46
|
+
* directly behind the placeholder image while loading.
|
47
|
+
*/
|
48
|
+
setFullSizeImgTempCss() {
|
49
|
+
this.fullSizeImg.css({
|
50
|
+
position: 'absolute',
|
51
|
+
top: this.placeholderImg.position().top,
|
52
|
+
left: this.placeholderImg.position().left,
|
53
|
+
width: this.placeholderImg.width(),
|
54
|
+
height: this.placeholderImg.height()
|
55
|
+
});
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Prep the full-size image with the attributes necessary to become its full
|
60
|
+
* size. Right now it is still just a replica of the placeholder, sitting
|
61
|
+
* right behind the placeholder.
|
62
|
+
*
|
63
|
+
* We set the src directly even though we're using imgix.js because older
|
64
|
+
* browsers don't support the srcset attribute which is what imgix.js relies
|
65
|
+
* upon.
|
66
|
+
*/
|
67
|
+
setFullSizeImgSrc() {
|
68
|
+
var newSrc = this.placeholderImg.attr('src')
|
69
|
+
.replace(/(\?|\&)(w=)(\d+)/i, '$1$2' + this.placeholderImg.width())
|
70
|
+
.replace(/(\?|\&)(h=)(\d+)/i, '$1$2' + this.placeholderImg.height());
|
71
|
+
this.fullSizeImg.attr('ix-src', newSrc);
|
72
|
+
// TODO: Make this a configurable option or document it as a more semantic temporary class
|
73
|
+
this.fullSizeImg.addClass('img-responsive tmp-img-placeholder');
|
74
|
+
// TODO: This should respect the option from the Optimizer class for the select
|
75
|
+
this.fullSizeImg.removeAttr('data-optimize-img');
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Render the full-size image in the DOM.
|
80
|
+
*/
|
81
|
+
addFullSizeImgToDom() {
|
82
|
+
this.fullSizeImg.insertBefore(this.placeholderImg);
|
83
|
+
}
|
84
|
+
|
85
|
+
// ---------------------------------------- | Image Transition
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Once the full-size image is loaded, begin the transition. This is the
|
89
|
+
* critical piece of this process. Imgix.js uses the ix-src attribute to build
|
90
|
+
* out the srcset attribute. Then, based on the sizes attribute, the browser
|
91
|
+
* determines which source to render. Therefore we can't preload in memory
|
92
|
+
* because we need imgix to do its thing directly in the DOM.
|
93
|
+
*/
|
94
|
+
initTransition() {
|
95
|
+
this.fullSizeImg.on('load', () => this.transitionImg());
|
96
|
+
imgix.init();
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Fade out the placeholder image, effectively showing the image behind it.
|
101
|
+
*
|
102
|
+
* Once the fade out transition has completed, remove any temporary properties
|
103
|
+
* from the full-size image (so it gets back to being a clone of the
|
104
|
+
* placeholder, with the full-size src).
|
105
|
+
*
|
106
|
+
* Finally, remove the placeholder image from the DOM since we don't need it
|
107
|
+
* any more.
|
108
|
+
*/
|
109
|
+
transitionImg() {
|
110
|
+
if (!this.placeholderImg) return true;
|
111
|
+
this.fadeOutPlaceholder();
|
112
|
+
setTimeout(() => {
|
113
|
+
this.removeFullSizeImgProperties();
|
114
|
+
this.removeImg();
|
115
|
+
}, this.timeToFade);
|
116
|
+
}
|
117
|
+
|
118
|
+
/**
|
119
|
+
* Fade out the placeholder image.
|
120
|
+
*/
|
121
|
+
fadeOutPlaceholder() {
|
122
|
+
this.placeholderImg.fadeTo(this.timeToFade, 0);
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* Remove temporary styles and class from the full-size image, which
|
127
|
+
* effectively means it has replaced the placeholder image.
|
128
|
+
*/
|
129
|
+
removeFullSizeImgProperties() {
|
130
|
+
this.fullSizeImg.removeAttr('style');
|
131
|
+
// TODO: Update this with how the class is handled above.
|
132
|
+
this.fullSizeImg.removeClass('tmp-img-placeholder');
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Remove the placeholder image from the DOM since we no longer need it.
|
137
|
+
*/
|
138
|
+
removeImg() {
|
139
|
+
if(!this.placeholderImg) { return }
|
140
|
+
this.placeholderImg.remove();
|
141
|
+
this.placeholderImg = undefined;
|
142
|
+
}
|
143
|
+
}
|
data/src/main.js
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
import ImgixBgImage from './imgix_bg_image';
|
2
|
+
import ImgixImage from './imgix_image';
|
3
|
+
import Optimizer from './optimizer';
|
4
|
+
|
5
|
+
window['Imgix'] = window['Imgix'] || {};
|
6
|
+
|
7
|
+
Imgix.ImgixBgImage = ImgixBgImage;
|
8
|
+
Imgix.ImgixImage = ImgixImage;
|
9
|
+
Imgix.Optimizer = Optimizer;
|
data/src/optimizer.js
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
import ImgixImage from './imgix_image';
|
2
|
+
import ImgixBgImage from './imgix_bg_image';
|
3
|
+
|
4
|
+
export default class Optimizer {
|
5
|
+
|
6
|
+
constructor(options = {}) {
|
7
|
+
this.initOptions(options);
|
8
|
+
this.optimizeImages();
|
9
|
+
this.optimizeBgImages();
|
10
|
+
}
|
11
|
+
|
12
|
+
// ---------------------------------------- | Options
|
13
|
+
|
14
|
+
initOptions(options = {}) {
|
15
|
+
this.options = options;
|
16
|
+
const defaultOptions = {
|
17
|
+
parent: 'body'
|
18
|
+
}
|
19
|
+
for (let key in defaultOptions) {
|
20
|
+
if (defaultOptions.hasOwnProperty(key) && !this.options[key]) {
|
21
|
+
this.options[key] = defaultOptions[key];
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
// ---------------------------------------- | Inline Images
|
27
|
+
|
28
|
+
optimizeImages() {
|
29
|
+
$(`${this.options.parent} img[data-optimize-img]`).each((idx, img) => {
|
30
|
+
new ImgixImage(img);
|
31
|
+
});
|
32
|
+
}
|
33
|
+
|
34
|
+
// ---------------------------------------- | Background Images
|
35
|
+
|
36
|
+
optimizeBgImages() {
|
37
|
+
$(`${this.options.parent} [data-optimize-bg-img]`).each((idx, img) => {
|
38
|
+
new ImgixBgImage(img);
|
39
|
+
});
|
40
|
+
return true;
|
41
|
+
}
|
42
|
+
|
43
|
+
}
|
data/test/test.js
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
const assert = require('assert');
|
2
|
+
const howLongTillLunch = require('..');
|
3
|
+
|
4
|
+
function MockDate () {
|
5
|
+
this.date = 0;
|
6
|
+
this.hours = 0;
|
7
|
+
this.minutes = 0;
|
8
|
+
this.seconds = 0;
|
9
|
+
this.milliseconds = 0;
|
10
|
+
};
|
11
|
+
|
12
|
+
Object.assign(MockDate.prototype, {
|
13
|
+
getDate () { return this.date; },
|
14
|
+
setDate (date) { this.date = date; },
|
15
|
+
setHours (h) { this.hours = h; },
|
16
|
+
setMinutes (m) { this.minutes = m; },
|
17
|
+
setSeconds (s) { this.seconds = s; },
|
18
|
+
setMilliseconds (ms) { this.milliseconds = ms; },
|
19
|
+
valueOf () {
|
20
|
+
return (
|
21
|
+
this.milliseconds +
|
22
|
+
this.seconds * 1e3 +
|
23
|
+
this.minutes * 1e3 * 60 +
|
24
|
+
this.hours * 1e3 * 60 * 60 +
|
25
|
+
this.date * 1e3 * 60 * 60 * 24
|
26
|
+
);
|
27
|
+
}
|
28
|
+
});
|
29
|
+
|
30
|
+
const now = new MockDate();
|
31
|
+
MockDate.now = () => now.valueOf();
|
32
|
+
|
33
|
+
global.Date = MockDate;
|
34
|
+
|
35
|
+
function test(hours, minutes, seconds, expected) {
|
36
|
+
now.setHours(hours);
|
37
|
+
now.setMinutes(minutes);
|
38
|
+
now.setSeconds(seconds);
|
39
|
+
|
40
|
+
assert.equal(howLongTillLunch(...lunchtime), expected);
|
41
|
+
console.log(`\u001B[32m✓\u001B[39m ${expected}`);
|
42
|
+
}
|
43
|
+
|
44
|
+
let lunchtime = [ 12, 30 ];
|
45
|
+
test(11, 30, 0, '1 hour');
|
46
|
+
test(10, 30, 0, '2 hours');
|
47
|
+
test(12, 25, 0, '5 minutes');
|
48
|
+
test(12, 29, 15, '45 seconds');
|
49
|
+
test(13, 30, 0, '23 hours');
|
50
|
+
|
51
|
+
// some of us like an early lunch
|
52
|
+
lunchtime = [ 11, 0 ];
|
53
|
+
test(10, 30, 0, '30 minutes');
|
File without changes
|