This repository has been archived by the owner on Feb 13, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
image.js
219 lines (195 loc) · 7.1 KB
/
image.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
(function(w){
/* We can request an image at every possible width, but let's limit it to a reasonable number
We can set these so they correspond to our more common sizes.
*/
var IMAGE_URL = w.BETTY_IMAGE_URL || "{{ BETTY_IMAGE_URL }}",
RATIOS = {{ BETTY_RATIOS|safe }},
ASPECT_RATIO_TOLERANCE = .1, // 10% tolerance.
MAX_WIDTH = {{ BETTY_MAX_WIDTH }},
PICTUREFILL_SELECTOR = w.PICTUREFILL_SELECTOR || "div",
breakpoints = [{{ BETTY_WIDTHS|join:","}}];
// Credit to https://remysharp.com/2010/07/21/throttling-function-calls
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
w.picturefill = function(elements, forceRerender) {
// It is sometimes desirable to scroll without loading images as we go.
if (w.pauseBettyCroppyPicturefill) {
return;
}
// get elements to picturefill
var ps;
if (elements instanceof Array) {
ps = elements;
} else if (elements instanceof HTMLElement) {
ps = [elements];
} else {
ps = w.document.querySelectorAll(PICTUREFILL_SELECTOR);
}
// loop through elements and fill them in
var imageData = [];
for (var i = 0, il = ps.length; i < il; i++){
var el = ps[i];
// ensure this element is actually a image to picturefill
if(el.getAttribute("data-type") !== "image" ){
// not image to fill, skip this one
continue;
}
// check if image is in viewport for lazy loading, and
// preload images if they're within 100px of being shown above scroll,
// within 250px of being shown below scroll.
var elementRect = el.getBoundingClientRect(),
innerHeight = w.innerHeight || w.document.documentElement.clientHeight,
visible = elementRect.top <= (innerHeight + 250) && elementRect.top >= -100;
// this is a div to picturefill, start working on it if it hasn't been rendered yet
if (el.getAttribute("data-image-id") !== null
&& visible
&& (forceRerender || !el.getAttribute("data-rendered"))) {
var imageContainer = el.getElementsByTagName("div")[0],
imageId = el.getAttribute("data-image-id"),
imageCrop = el.getAttribute("data-crop"),
format = el.getAttribute("data-format") || "jpg";
// construct ID path for image
var idStr = "";
for(var ii = 0; ii < imageId.length; ii++) {
if ((ii % 4) === 0) {
idStr += "/";
}
idStr += imageId.charAt(ii);
}
// find any existing img element in the picture element
var picImg = imageContainer.getElementsByTagName("img")[0];
if(!picImg){
// for performance reasons this will be added to the dom later
picImg = w.document.createElement("img");
var alt = el.getAttribute("data-alt");
if (alt) {
picImg.alt = alt;
}
}
// determine what to do based on format
if (format === "gif") {
// for GIFs, we just dump out original
imageData.push({
'div': imageContainer,
'img': picImg,
'url': IMAGE_URL + idStr + "/animated/original.gif"
});
} else {
// determine size & crop for PNGs & JPGs.
var _w = imageContainer.offsetWidth,
_h = imageContainer.offsetHeight;
if (!imageCrop || imageCrop === "") {
imageCrop = computeAspectRatio(_w, _h);
}
// scale up to the pixel ratio if there's some pixel ratio defined
if (w.devicePixelRatio) {
_w = Math.round(w.devicePixelRatio * _w);
_h = Math.round(w.devicePixelRatio * _h);
}
// determine if a breakpoint width should be used, otherwise use previously defined width
var width = null;
for (var j = 0; j < breakpoints.length; j++) {
if (_w <= breakpoints[j]) {
width = breakpoints[j];
break;
}
}
if (width === null) {
if (_w > MAX_WIDTH) {
width = MAX_WIDTH;
} else {
width = _w;
}
}
// if the existing image is larger (or the same) than the one we're about to load, do not update.
// however if the crop changes, we need to reload.
if (width > 0) {
//ie8 doesn't support natural width, always load.
if (typeof picImg.naturalWidth === "undefined" || picImg.naturalWidth < width
|| imageCrop !== computeAspectRatio(picImg.naturalWidth, picImg.naturalHeight)) {
// put image in image data to render
imageData.push({
'div': imageContainer,
'img': picImg,
'url': IMAGE_URL + idStr + "/" + imageCrop + "/" + width + "." + format
});
}
}
}
}
}
// loop through image data and insert images, all DOM updates should probably go here
for(var i = 0; i < imageData.length; i++) {
var data = imageData[i];
data.img.src = data.url;
if (!data.img.parentNode) {
data.div.appendChild(data.img);
data.div.parentNode.setAttribute("data-rendered", "true");
}
}
};
/**
* Figure out best aspect ratio based on width, height, and given aspect ratios.
*/
function computeAspectRatio(_w, _h) {
if (_w !== 0 && _h !== 0) {
var aspectRatio = _w/_h;
for (var i in RATIOS) {
if (Math.abs(aspectRatio - RATIOS[i][1]) / RATIOS[i][1] < ASPECT_RATIO_TOLERANCE) {
return RATIOS[i][0];
}
}
} else {
return "16x9";
}
}
function addEventListener(ele, event, callback) {
if (ele.addEventListener) {
ele.addEventListener(event, callback, false);
} else if (ele.attachEvent) {
ele.attachEvent("on" + event, callback);
}
}
function removeEventListener(ele, event, callback) {
if (ele.removeEventListener) {
ele.removeEventListener(event, callback, false);
} else if (ele.detachEvent) {
ele.detachEvent("on" + event, callback);
}
}
// Run on resize and domready (w.load as a fallback)
if (!w.IMAGE_LISTENERS_DISABLED) {
addEventListener(w, "load", w.picturefill);
addEventListener(w, "DOMContentLoaded", function () {
w.picturefill();
removeEventListener(w, "load");
});
var resizeTimeout;
addEventListener(w, "resize", function () {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function () {
w.picturefill(null, true);
}, 100);
});
addEventListener(w, "scroll", throttle(w.picturefill, 100));
}
}(this));