imgix-optimizer 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +2 -2
- data/dist/crds.html +57 -0
- data/dist/imgix-optimizer-0.0.6.min.js +1 -0
- data/dist/imgix-optimizer.js +1063 -191
- data/dist/index.html +209 -85
- data/package-lock.json +328 -332
- data/package.json +2 -1
- data/src/{imgix_bg_image.js → background_image.js} +55 -16
- data/src/{imgix_image.js → image.js} +80 -10
- data/src/main.js +0 -4
- data/src/optimizer.js +15 -7
- data/src/polyfills/object_assign.js +32 -0
- metadata +7 -6
- data/dist/imgix-optimizer-0.0.5.min.js +0 -1
- data/vendor/assets/javascripts/imgix-optimizer.js +0 -695
data/package.json
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"name": "imgix-optimizer",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.6",
|
4
4
|
"devDependencies": {
|
5
5
|
"babel-core": "^6.26.3",
|
6
6
|
"babel-preset-es2015-rollup": "^3.0.0",
|
7
|
+
"intersection-observer": "^0.5.1",
|
7
8
|
"rollup": "^0.60.0",
|
8
9
|
"rollup-plugin-babel": "^3.0.7",
|
9
10
|
"rollup-plugin-commonjs": "^9.1.0",
|
@@ -1,14 +1,20 @@
|
|
1
|
-
export default class
|
2
|
-
|
1
|
+
export default class BackgroundImage {
|
3
2
|
constructor(el) {
|
4
3
|
// Length of time to complete fade-in transition.
|
5
4
|
this.timeToFade = 500;
|
5
|
+
// Data attribute applied before processing.
|
6
|
+
this.processingAttr = 'data-imgix-bg-processed';
|
6
7
|
// Device pixel ratio assumes 1 if not set.
|
7
8
|
this.dpr = window['devicePixelRatio'] || 1;
|
9
|
+
// The largest image that has been loaded. This assumes the height of the
|
10
|
+
// container will not change.
|
11
|
+
this.largestImageWidth = 0;
|
8
12
|
// The primary element (i.e. the one with the background image).
|
9
13
|
this.el = $(el);
|
10
14
|
// Background image CSS property must be present.
|
11
|
-
if (this.el.css('background-image') == 'none') {
|
15
|
+
if (this.el.css('background-image') == 'none') {
|
16
|
+
return;
|
17
|
+
}
|
12
18
|
// Prepare the element and its container for optimization.
|
13
19
|
this.initEl();
|
14
20
|
// Kick off the optimization process.
|
@@ -24,10 +30,32 @@ export default class ImgixBgImage {
|
|
24
30
|
*/
|
25
31
|
initOptimization() {
|
26
32
|
$('<img>')
|
27
|
-
.on('load', (
|
33
|
+
.on('load', $.proxy(this.listenForIntersection, this))
|
28
34
|
.attr('src', this.placeholderImgUrl);
|
29
35
|
}
|
30
36
|
|
37
|
+
/**
|
38
|
+
* When the element intersects the viewport, begin processing.
|
39
|
+
* (IntersectionObserver and Object.assign() are not supported by IE, but the
|
40
|
+
* polyfills are loaded by Imgix.Optimizer.)
|
41
|
+
*/
|
42
|
+
listenForIntersection() {
|
43
|
+
const observer = new IntersectionObserver($.proxy(this.onIntersection, this));
|
44
|
+
observer.observe(this.el[0]);
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* When the element intersects the viewport, check if it is in the viewport
|
49
|
+
* and has not yet been processed. If those conditions are true, begin
|
50
|
+
* rendering the full size image and the transition process.
|
51
|
+
*/
|
52
|
+
onIntersection(entries, observer) {
|
53
|
+
let el = $(entries[0].target);
|
54
|
+
if (!entries[0].isIntersecting || $(el).attr(this.processingAttr)) return;
|
55
|
+
$(el).attr(this.processingAttr, true);
|
56
|
+
this.renderTmpPlaceholderEl();
|
57
|
+
}
|
58
|
+
|
31
59
|
// ---------------------------------------- | Main Element
|
32
60
|
|
33
61
|
/**
|
@@ -44,11 +72,12 @@ export default class ImgixBgImage {
|
|
44
72
|
* placeholder.
|
45
73
|
*/
|
46
74
|
setPlaceholderImgUrl() {
|
47
|
-
this.placeholderImgUrl = this.el
|
75
|
+
this.placeholderImgUrl = this.el
|
76
|
+
.css('background-image')
|
48
77
|
.replace('url(', '')
|
49
78
|
.replace(')', '')
|
50
|
-
.replace(/\"/gi,
|
51
|
-
.replace(/\'/gi,
|
79
|
+
.replace(/\"/gi, '')
|
80
|
+
.replace(/\'/gi, '')
|
52
81
|
.split(', ')[0];
|
53
82
|
}
|
54
83
|
|
@@ -61,7 +90,7 @@ export default class ImgixBgImage {
|
|
61
90
|
this.parentStyles = {
|
62
91
|
display: this.el.parent().css('display'),
|
63
92
|
position: this.el.parent().css('position')
|
64
|
-
}
|
93
|
+
};
|
65
94
|
this.el.parent().css({
|
66
95
|
display: 'block',
|
67
96
|
position: 'relative'
|
@@ -167,7 +196,13 @@ export default class ImgixBgImage {
|
|
167
196
|
* of the main element.
|
168
197
|
*/
|
169
198
|
setFullSizeImgUrl() {
|
170
|
-
//
|
199
|
+
// If the full size image URL exists and if the new size is going to be
|
200
|
+
// smaller than the largest size loaded, then we stick with the largest size
|
201
|
+
// that has been used.
|
202
|
+
if (this.fullSizeImgUrl && this.el.outerWidth() * this.dpr <= this.largestImageWidth) return;
|
203
|
+
// Assume that the new width will be the largest size used.
|
204
|
+
this.largestImageWidth = this.el.outerWidth() * this.dpr;
|
205
|
+
// Work with the placeholder image URL, which has been pulled from the
|
171
206
|
// background-image css property of the main elements.
|
172
207
|
let url = this.placeholderImgUrl.split('?');
|
173
208
|
// q is an array of querystring parameters as ["k=v", "k=v", ...].
|
@@ -175,11 +210,11 @@ export default class ImgixBgImage {
|
|
175
210
|
// Mapping q converts the array to an object of querystring parameters as
|
176
211
|
// { k: v, k: v, ... }.
|
177
212
|
let args = {};
|
178
|
-
q.map(
|
213
|
+
q.map(x => (args[x.split('=')[0]] = x.split('=')[1]));
|
179
214
|
// If the image's container is wider than it is tall, we only set width and
|
180
215
|
// unset height, and vice versa.
|
181
216
|
if (this.el.outerWidth() >= this.el.outerHeight()) {
|
182
|
-
args['w'] = this.
|
217
|
+
args['w'] = this.largestImageWidth;
|
183
218
|
delete args['h'];
|
184
219
|
} else {
|
185
220
|
args['h'] = this.el.outerHeight() * this.dpr;
|
@@ -188,9 +223,11 @@ export default class ImgixBgImage {
|
|
188
223
|
// Redefine q and go the other direction -- take the args object and convert
|
189
224
|
// it back to an array of querystring parameters, as ["k=v", "k=v", ...].
|
190
225
|
q = [];
|
191
|
-
for (let k in args) {
|
226
|
+
for (let k in args) {
|
227
|
+
q.push(`${k}=${args[k]}`);
|
228
|
+
}
|
192
229
|
// Store the result and return.
|
193
|
-
return this.fullSizeImgUrl = `${url[0]}?${q.join('&')}
|
230
|
+
return (this.fullSizeImgUrl = `${url[0]}?${q.join('&')}`);
|
194
231
|
}
|
195
232
|
|
196
233
|
/**
|
@@ -255,7 +292,9 @@ export default class ImgixBgImage {
|
|
255
292
|
*/
|
256
293
|
updateElImg() {
|
257
294
|
this.setFullSizeImgUrl();
|
258
|
-
|
295
|
+
$('<img>')
|
296
|
+
.on('load', event => this.el.css('background-image', `url('${this.fullSizeImgUrl}')`))
|
297
|
+
.attr('src', this.placeholderImgUrl);
|
259
298
|
}
|
260
299
|
|
261
300
|
/**
|
@@ -292,7 +331,7 @@ export default class ImgixBgImage {
|
|
292
331
|
*/
|
293
332
|
initEventListeners() {
|
294
333
|
this.initResizeEnd();
|
295
|
-
$(window).on('resizeEnd',
|
334
|
+
$(window).on('resizeEnd', event => this.updateElImg());
|
296
335
|
}
|
297
336
|
|
298
337
|
/**
|
@@ -302,7 +341,7 @@ export default class ImgixBgImage {
|
|
302
341
|
initResizeEnd() {
|
303
342
|
$(window).resize(function() {
|
304
343
|
if (this.resizeTo) {
|
305
|
-
clearTimeout(this.resizeTo)
|
344
|
+
clearTimeout(this.resizeTo);
|
306
345
|
}
|
307
346
|
this.resizeTo = setTimeout(function() {
|
308
347
|
$(this).trigger('resizeEnd');
|
@@ -1,8 +1,9 @@
|
|
1
|
-
export default class
|
2
|
-
|
1
|
+
export default class Image {
|
3
2
|
constructor(img) {
|
4
3
|
// Length of crossfade transition.
|
5
4
|
this.timeToFade = 500;
|
5
|
+
// Data attribute applied before processing.
|
6
|
+
this.processingAttr = 'data-imgix-img-processed';
|
6
7
|
// The main image (pixelated placeholder).
|
7
8
|
this.placeholderImg = $(img);
|
8
9
|
// Configure the main placeholder image.
|
@@ -14,14 +15,36 @@ export default class ImgixImage {
|
|
14
15
|
/**
|
15
16
|
* Load an image in memory (not within the DOM) with the same source as the
|
16
17
|
* placeholder image. Once that has completed, we know we're safe to begin
|
17
|
-
*
|
18
|
+
* listening for the image to intersect the viewport.
|
18
19
|
*/
|
19
20
|
initOptimization() {
|
20
21
|
$('<img>')
|
21
|
-
.on('load', $.proxy(this.
|
22
|
+
.on('load', $.proxy(this.listenForIntersection, this))
|
22
23
|
.attr('src', this.placeholderImg.attr('src'));
|
23
24
|
}
|
24
25
|
|
26
|
+
/**
|
27
|
+
* When the placeholder image intersects the viewport, begin processing.
|
28
|
+
* (IntersectionObserver and Object.assign() are not supported by IE, but the
|
29
|
+
* polyfills are loaded by Imgix.Optimizer.)
|
30
|
+
*/
|
31
|
+
listenForIntersection() {
|
32
|
+
const observer = new IntersectionObserver($.proxy(this.onIntersection, this));
|
33
|
+
observer.observe(this.placeholderImg[0]);
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* When the placeholder image intersects the viewport, check if it is in the
|
38
|
+
* viewport and has not yet been processed. If those conditions are true,
|
39
|
+
* begin rendering the full size image and the transition process.
|
40
|
+
*/
|
41
|
+
onIntersection(entries, observer) {
|
42
|
+
let img = $(entries[0].target);
|
43
|
+
if (!entries[0].isIntersecting || $(img).attr(this.processingAttr)) return;
|
44
|
+
img.attr(this.processingAttr, true);
|
45
|
+
this.renderFullSizeImg();
|
46
|
+
}
|
47
|
+
|
25
48
|
// ---------------------------------------- | Placeholder Image
|
26
49
|
|
27
50
|
/**
|
@@ -29,6 +52,7 @@ export default class ImgixImage {
|
|
29
52
|
*/
|
30
53
|
initPlaceholder() {
|
31
54
|
this.setPlaceholderCss();
|
55
|
+
this.setPlaceholderParentTmpCss();
|
32
56
|
}
|
33
57
|
|
34
58
|
/**
|
@@ -42,6 +66,21 @@ export default class ImgixImage {
|
|
42
66
|
}
|
43
67
|
}
|
44
68
|
|
69
|
+
/**
|
70
|
+
* The parent of the image container should be relatively positioned
|
71
|
+
* (temporarily) so temp image can be absolutely positioned.
|
72
|
+
*/
|
73
|
+
setPlaceholderParentTmpCss() {
|
74
|
+
this.parentStyles = {
|
75
|
+
display: this.placeholderImg.parent().css('display'),
|
76
|
+
position: this.placeholderImg.parent().css('position')
|
77
|
+
};
|
78
|
+
this.placeholderImg.parent().css({
|
79
|
+
display: 'block',
|
80
|
+
position: 'relative'
|
81
|
+
});
|
82
|
+
}
|
83
|
+
|
45
84
|
// ---------------------------------------- | Full-Size Image
|
46
85
|
|
47
86
|
/**
|
@@ -72,11 +111,23 @@ export default class ImgixImage {
|
|
72
111
|
position: 'absolute',
|
73
112
|
top: this.placeholderImg.position().top,
|
74
113
|
left: this.placeholderImg.position().left,
|
75
|
-
width:
|
76
|
-
height:
|
114
|
+
width: '100%',
|
115
|
+
height: '100%'
|
77
116
|
});
|
78
117
|
}
|
79
118
|
|
119
|
+
/**
|
120
|
+
* Return the width and height of the placeholder image, including decimals.
|
121
|
+
* Uses precise measurements like this helps ensure the element doesn't slide
|
122
|
+
* when transitioning to the full size image.
|
123
|
+
*/
|
124
|
+
getPlaceholderImgRect() {
|
125
|
+
return {
|
126
|
+
width: this.placeholderImg[0].getBoundingClientRect().width,
|
127
|
+
height: this.placeholderImg[0].getBoundingClientRect().height
|
128
|
+
};
|
129
|
+
}
|
130
|
+
|
80
131
|
/**
|
81
132
|
* Prep the full-size image with the attributes necessary to become its full
|
82
133
|
* size. Right now it is still just a replica of the placeholder, sitting
|
@@ -87,9 +138,15 @@ export default class ImgixImage {
|
|
87
138
|
* upon.
|
88
139
|
*/
|
89
140
|
setFullSizeImgSrc() {
|
90
|
-
|
91
|
-
.
|
92
|
-
.replace(/(\?|\&)(
|
141
|
+
let newSrc = this.placeholderImg
|
142
|
+
.attr('src')
|
143
|
+
.replace(/(\?|\&)(w=)(\d+)/i, '$1$2' + this.getPlaceholderImgRect().width)
|
144
|
+
.replace(/(\?|\&)(h=)(\d+)/i, '$1$2' + this.getPlaceholderImgRect().height);
|
145
|
+
// Add a height attribute if it is missing. This is the key to the image not
|
146
|
+
// jumping around after transitioning to the full-size image.
|
147
|
+
if (newSrc.search(/(\?|\&)(h=)(\d+)/i) < 0) {
|
148
|
+
newSrc = `${newSrc}&h=${this.getPlaceholderImgRect().height}&fit=crop`;
|
149
|
+
}
|
93
150
|
this.fullSizeImg.attr('ix-src', newSrc);
|
94
151
|
// TODO: Make this a configurable option or document it as a more semantic temporary class
|
95
152
|
this.fullSizeImg.addClass('img-responsive imgix-optimizing');
|
@@ -133,6 +190,7 @@ export default class ImgixImage {
|
|
133
190
|
this.fadeOutPlaceholder();
|
134
191
|
setTimeout(() => {
|
135
192
|
this.removeFullSizeImgProperties();
|
193
|
+
this.replacePlaceholderParentTmpCss();
|
136
194
|
this.removeImg();
|
137
195
|
}, this.timeToFade);
|
138
196
|
}
|
@@ -154,11 +212,23 @@ export default class ImgixImage {
|
|
154
212
|
this.fullSizeImg.removeClass('imgix-optimizing');
|
155
213
|
}
|
156
214
|
|
215
|
+
/**
|
216
|
+
* Reset the container's adjusted CSS properties.
|
217
|
+
*/
|
218
|
+
replacePlaceholderParentTmpCss() {
|
219
|
+
this.placeholderImg.parent().css({
|
220
|
+
display: this.parentStyles.display,
|
221
|
+
position: this.parentStyles.position
|
222
|
+
});
|
223
|
+
}
|
224
|
+
|
157
225
|
/**
|
158
226
|
* Remove the placeholder image from the DOM since we no longer need it.
|
159
227
|
*/
|
160
228
|
removeImg() {
|
161
|
-
if(!this.placeholderImg) {
|
229
|
+
if (!this.placeholderImg) {
|
230
|
+
return;
|
231
|
+
}
|
162
232
|
this.placeholderImg.remove();
|
163
233
|
this.placeholderImg = undefined;
|
164
234
|
}
|
data/src/main.js
CHANGED
@@ -1,9 +1,5 @@
|
|
1
|
-
import ImgixBgImage from './imgix_bg_image';
|
2
|
-
import ImgixImage from './imgix_image';
|
3
1
|
import Optimizer from './optimizer';
|
4
2
|
|
5
3
|
window['Imgix'] = window['Imgix'] || {};
|
6
4
|
|
7
|
-
Imgix.ImgixBgImage = ImgixBgImage;
|
8
|
-
Imgix.ImgixImage = ImgixImage;
|
9
5
|
Imgix.Optimizer = Optimizer;
|
data/src/optimizer.js
CHANGED
@@ -1,21 +1,30 @@
|
|
1
|
-
import
|
2
|
-
import
|
1
|
+
import Image from './image';
|
2
|
+
import BackgroundImage from './background_image';
|
3
|
+
import * as IntObs from 'intersection-observer';
|
4
|
+
import * as ObjectAssign from './polyfills/object_assign';
|
3
5
|
|
4
6
|
export default class Optimizer {
|
5
|
-
|
6
7
|
constructor(options = {}) {
|
8
|
+
this.initDependencies();
|
7
9
|
this.initOptions(options);
|
8
10
|
this.optimizeImages();
|
9
11
|
this.optimizeBgImages();
|
10
12
|
}
|
11
13
|
|
14
|
+
// ---------------------------------------- | Dependencies
|
15
|
+
|
16
|
+
initDependencies() {
|
17
|
+
IntObs;
|
18
|
+
ObjectAssign;
|
19
|
+
}
|
20
|
+
|
12
21
|
// ---------------------------------------- | Options
|
13
22
|
|
14
23
|
initOptions(options = {}) {
|
15
24
|
this.options = options;
|
16
25
|
const defaultOptions = {
|
17
26
|
parent: 'body'
|
18
|
-
}
|
27
|
+
};
|
19
28
|
for (let key in defaultOptions) {
|
20
29
|
if (defaultOptions.hasOwnProperty(key) && !this.options[key]) {
|
21
30
|
this.options[key] = defaultOptions[key];
|
@@ -27,7 +36,7 @@ export default class Optimizer {
|
|
27
36
|
|
28
37
|
optimizeImages() {
|
29
38
|
$(`${this.options.parent} img[data-optimize-img]`).each((idx, img) => {
|
30
|
-
new
|
39
|
+
new Image(img);
|
31
40
|
});
|
32
41
|
}
|
33
42
|
|
@@ -35,9 +44,8 @@ export default class Optimizer {
|
|
35
44
|
|
36
45
|
optimizeBgImages() {
|
37
46
|
$(`${this.options.parent} [data-optimize-bg-img]`).each((idx, img) => {
|
38
|
-
new
|
47
|
+
new BackgroundImage(img);
|
39
48
|
});
|
40
49
|
return true;
|
41
50
|
}
|
42
|
-
|
43
51
|
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
if (typeof Object.assign != 'function') {
|
2
|
+
// Must be writable: true, enumerable: false, configurable: true
|
3
|
+
Object.defineProperty(Object, 'assign', {
|
4
|
+
value: function assign(target, varArgs) {
|
5
|
+
// .length of function is 2
|
6
|
+
'use strict';
|
7
|
+
if (target == null) {
|
8
|
+
// TypeError if undefined or null
|
9
|
+
throw new TypeError('Cannot convert undefined or null to object');
|
10
|
+
}
|
11
|
+
|
12
|
+
var to = Object(target);
|
13
|
+
|
14
|
+
for (var index = 1; index < arguments.length; index++) {
|
15
|
+
var nextSource = arguments[index];
|
16
|
+
|
17
|
+
if (nextSource != null) {
|
18
|
+
// Skip over if undefined or null
|
19
|
+
for (var nextKey in nextSource) {
|
20
|
+
// Avoid bugs when hasOwnProperty is shadowed
|
21
|
+
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
22
|
+
to[nextKey] = nextSource[nextKey];
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
return to;
|
28
|
+
},
|
29
|
+
writable: true,
|
30
|
+
configurable: true
|
31
|
+
});
|
32
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imgix-optimizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean C Davis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -37,7 +37,8 @@ files:
|
|
37
37
|
- LICENSE
|
38
38
|
- README.md
|
39
39
|
- Rakefile
|
40
|
-
- dist/
|
40
|
+
- dist/crds.html
|
41
|
+
- dist/imgix-optimizer-0.0.6.min.js
|
41
42
|
- dist/imgix-optimizer.js
|
42
43
|
- dist/index.html
|
43
44
|
- dist/main.css
|
@@ -50,13 +51,13 @@ files:
|
|
50
51
|
- package.json
|
51
52
|
- rollup.config.dev.js
|
52
53
|
- rollup.config.js
|
53
|
-
- src/
|
54
|
-
- src/
|
54
|
+
- src/background_image.js
|
55
|
+
- src/image.js
|
55
56
|
- src/main.js
|
56
57
|
- src/optimizer.js
|
58
|
+
- src/polyfills/object_assign.js
|
57
59
|
- test/test.js
|
58
60
|
- vendor/assets/javascripts/.keep
|
59
|
-
- vendor/assets/javascripts/imgix-optimizer.js
|
60
61
|
homepage: https://www.ample.co/
|
61
62
|
licenses:
|
62
63
|
- MIT
|