1 /* |
|
2 |
|
3 Holder - 2.2 - client side image placeholders |
|
4 (c) 2012-2013 Ivan Malopinsky / http://imsky.co |
|
5 |
|
6 Provided under the MIT License. |
|
7 Commercial use requires attribution. |
|
8 |
|
9 */ |
|
10 |
|
11 var Holder = Holder || {}; |
|
12 (function (app, win) { |
|
13 |
|
14 var preempted = false, |
|
15 fallback = false, |
|
16 canvas = document.createElement('canvas'); |
|
17 var dpr = 1, bsr = 1; |
|
18 var resizable_images = []; |
|
19 |
|
20 if (!canvas.getContext) { |
|
21 fallback = true; |
|
22 } else { |
|
23 if (canvas.toDataURL("image/png") |
|
24 .indexOf("data:image/png") < 0) { |
|
25 //Android doesn't support data URI |
|
26 fallback = true; |
|
27 } else { |
|
28 var ctx = canvas.getContext("2d"); |
|
29 } |
|
30 } |
|
31 |
|
32 if(!fallback){ |
|
33 dpr = window.devicePixelRatio || 1, |
|
34 bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; |
|
35 } |
|
36 |
|
37 var ratio = dpr / bsr; |
|
38 |
|
39 var settings = { |
|
40 domain: "holder.js", |
|
41 images: "img", |
|
42 bgnodes: ".holderjs", |
|
43 themes: { |
|
44 "gray": { |
|
45 background: "#eee", |
|
46 foreground: "#aaa", |
|
47 size: 12 |
|
48 }, |
|
49 "social": { |
|
50 background: "#3a5a97", |
|
51 foreground: "#fff", |
|
52 size: 12 |
|
53 }, |
|
54 "industrial": { |
|
55 background: "#434A52", |
|
56 foreground: "#C2F200", |
|
57 size: 12 |
|
58 }, |
|
59 "sky": { |
|
60 background: "#0D8FDB", |
|
61 foreground: "#fff", |
|
62 size: 12 |
|
63 }, |
|
64 "vine": { |
|
65 background: "#39DBAC", |
|
66 foreground: "#1E292C", |
|
67 size: 12 |
|
68 }, |
|
69 "lava": { |
|
70 background: "#F8591A", |
|
71 foreground: "#1C2846", |
|
72 size: 12 |
|
73 } |
|
74 }, |
|
75 stylesheet: "" |
|
76 }; |
|
77 app.flags = { |
|
78 dimensions: { |
|
79 regex: /^(\d+)x(\d+)$/, |
|
80 output: function (val) { |
|
81 var exec = this.regex.exec(val); |
|
82 return { |
|
83 width: +exec[1], |
|
84 height: +exec[2] |
|
85 } |
|
86 } |
|
87 }, |
|
88 fluid: { |
|
89 regex: /^([0-9%]+)x([0-9%]+)$/, |
|
90 output: function (val) { |
|
91 var exec = this.regex.exec(val); |
|
92 return { |
|
93 width: exec[1], |
|
94 height: exec[2] |
|
95 } |
|
96 } |
|
97 }, |
|
98 colors: { |
|
99 regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, |
|
100 output: function (val) { |
|
101 var exec = this.regex.exec(val); |
|
102 return { |
|
103 size: settings.themes.gray.size, |
|
104 foreground: "#" + exec[2], |
|
105 background: "#" + exec[1] |
|
106 } |
|
107 } |
|
108 }, |
|
109 text: { |
|
110 regex: /text\:(.*)/, |
|
111 output: function (val) { |
|
112 return this.regex.exec(val)[1]; |
|
113 } |
|
114 }, |
|
115 font: { |
|
116 regex: /font\:(.*)/, |
|
117 output: function (val) { |
|
118 return this.regex.exec(val)[1]; |
|
119 } |
|
120 }, |
|
121 auto: { |
|
122 regex: /^auto$/ |
|
123 }, |
|
124 textmode: { |
|
125 regex: /textmode\:(.*)/, |
|
126 output: function(val){ |
|
127 return this.regex.exec(val)[1]; |
|
128 } |
|
129 } |
|
130 } |
|
131 |
|
132 //getElementsByClassName polyfill |
|
133 document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i<n.length;i++)r.test(n[i].className)&&s.push(n[i])}return s}) |
|
134 |
|
135 //getComputedStyle polyfill |
|
136 window.getComputedStyle||(window.getComputedStyle=function(e){return this.el=e,this.getPropertyValue=function(t){var n=/(\-([a-z]){1})/g;return t=="float"&&(t="styleFloat"),n.test(t)&&(t=t.replace(n,function(){return arguments[2].toUpperCase()})),e.currentStyle[t]?e.currentStyle[t]:null},this}) |
|
137 |
|
138 //http://javascript.nwbox.com/ContentLoaded by Diego Perini with modifications |
|
139 function contentLoaded(n,t){var l="complete",s="readystatechange",u=!1,h=u,c=!0,i=n.document,a=i.documentElement,e=i.addEventListener?"addEventListener":"attachEvent",v=i.addEventListener?"removeEventListener":"detachEvent",f=i.addEventListener?"":"on",r=function(e){(e.type!=s||i.readyState==l)&&((e.type=="load"?n:i)[v](f+e.type,r,u),!h&&(h=!0)&&t.call(n,null))},o=function(){try{a.doScroll("left")}catch(n){setTimeout(o,50);return}r("poll")};if(i.readyState==l)t.call(n,"lazy");else{if(i.createEventObject&&a.doScroll){try{c=!n.frameElement}catch(y){}c&&o()}i[e](f+"DOMContentLoaded",r,u),i[e](f+s,r,u),n[e](f+"load",r,u)}} |
|
140 |
|
141 //https://gist.github.com/991057 by Jed Schmidt with modifications |
|
142 function selector(a){ |
|
143 a=a.match(/^(\W)?(.*)/);var b=document["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2]); |
|
144 var ret=[]; b!==null&&(b.length?ret=b:b.length===0?ret=b:ret=[b]); return ret; |
|
145 } |
|
146 |
|
147 //shallow object property extend |
|
148 function extend(a,b){ |
|
149 var c={}; |
|
150 for(var i in a){ |
|
151 if(a.hasOwnProperty(i)){ |
|
152 c[i]=a[i]; |
|
153 } |
|
154 } |
|
155 for(var i in b){ |
|
156 if(b.hasOwnProperty(i)){ |
|
157 c[i]=b[i]; |
|
158 } |
|
159 } |
|
160 return c |
|
161 } |
|
162 |
|
163 //hasOwnProperty polyfill |
|
164 if (!Object.prototype.hasOwnProperty) |
|
165 /*jshint -W001, -W103 */ |
|
166 Object.prototype.hasOwnProperty = function(prop) { |
|
167 var proto = this.__proto__ || this.constructor.prototype; |
|
168 return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]); |
|
169 } |
|
170 /*jshint +W001, +W103 */ |
|
171 |
|
172 function text_size(width, height, template) { |
|
173 height = parseInt(height, 10); |
|
174 width = parseInt(width, 10); |
|
175 var bigSide = Math.max(height, width) |
|
176 var smallSide = Math.min(height, width) |
|
177 var scale = 1 / 12; |
|
178 var newHeight = Math.min(smallSide * 0.75, 0.75 * bigSide * scale); |
|
179 return { |
|
180 height: Math.round(Math.max(template.size, newHeight)) |
|
181 } |
|
182 } |
|
183 |
|
184 function draw(args) { |
|
185 var ctx = args.ctx; |
|
186 var dimensions = args.dimensions; |
|
187 var template = args.template; |
|
188 var ratio = args.ratio; |
|
189 var holder = args.holder; |
|
190 var literal = holder.textmode == "literal"; |
|
191 var exact = holder.textmode == "exact"; |
|
192 |
|
193 var ts = text_size(dimensions.width, dimensions.height, template); |
|
194 var text_height = ts.height; |
|
195 var width = dimensions.width * ratio, |
|
196 height = dimensions.height * ratio; |
|
197 var font = template.font ? template.font : "sans-serif"; |
|
198 canvas.width = width; |
|
199 canvas.height = height; |
|
200 ctx.textAlign = "center"; |
|
201 ctx.textBaseline = "middle"; |
|
202 ctx.fillStyle = template.background; |
|
203 ctx.fillRect(0, 0, width, height); |
|
204 ctx.fillStyle = template.foreground; |
|
205 ctx.font = "bold " + text_height + "px " + font; |
|
206 var text = template.text ? template.text : (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); |
|
207 if (literal) { |
|
208 var dimensions = holder.dimensions; |
|
209 text = dimensions.width + "x" + dimensions.height; |
|
210 } |
|
211 else if(exact && holder.exact_dimensions){ |
|
212 var dimensions = holder.exact_dimensions; |
|
213 text = (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); |
|
214 } |
|
215 var text_width = ctx.measureText(text).width; |
|
216 if (text_width / width >= 0.75) { |
|
217 text_height = Math.floor(text_height * 0.75 * (width / text_width)); |
|
218 } |
|
219 //Resetting font size if necessary |
|
220 ctx.font = "bold " + (text_height * ratio) + "px " + font; |
|
221 ctx.fillText(text, (width / 2), (height / 2), width); |
|
222 return canvas.toDataURL("image/png"); |
|
223 } |
|
224 |
|
225 function render(mode, el, holder, src) { |
|
226 |
|
227 var dimensions = holder.dimensions, |
|
228 theme = holder.theme, |
|
229 text = holder.text ? decodeURIComponent(holder.text) : holder.text; |
|
230 var dimensions_caption = dimensions.width + "x" + dimensions.height; |
|
231 theme = (text ? extend(theme, { |
|
232 text: text |
|
233 }) : theme); |
|
234 theme = (holder.font ? extend(theme, { |
|
235 font: holder.font |
|
236 }) : theme); |
|
237 el.setAttribute("data-src", src); |
|
238 holder.theme = theme; |
|
239 el.holder_data = holder; |
|
240 |
|
241 if (mode == "image") { |
|
242 el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); |
|
243 if (fallback || !holder.auto) { |
|
244 el.style.width = dimensions.width + "px"; |
|
245 el.style.height = dimensions.height + "px"; |
|
246 } |
|
247 if (fallback) { |
|
248 el.style.backgroundColor = theme.background; |
|
249 } else { |
|
250 el.setAttribute("src", draw({ctx: ctx, dimensions: dimensions, template: theme, ratio:ratio, holder: holder})); |
|
251 |
|
252 if(holder.textmode && holder.textmode == "exact"){ |
|
253 resizable_images.push(el); |
|
254 resizable_update(el); |
|
255 } |
|
256 |
|
257 } |
|
258 } else if (mode == "background") { |
|
259 if (!fallback) { |
|
260 el.style.backgroundImage = "url(" + draw({ctx:ctx, dimensions: dimensions, template: theme, ratio: ratio, holder: holder}) + ")"; |
|
261 el.style.backgroundSize = dimensions.width + "px " + dimensions.height + "px"; |
|
262 } |
|
263 } else if (mode == "fluid") { |
|
264 el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); |
|
265 if (dimensions.height.slice(-1) == "%") { |
|
266 el.style.height = dimensions.height |
|
267 } else { |
|
268 el.style.height = dimensions.height + "px" |
|
269 } |
|
270 if (dimensions.width.slice(-1) == "%") { |
|
271 el.style.width = dimensions.width |
|
272 } else { |
|
273 el.style.width = dimensions.width + "px" |
|
274 } |
|
275 if (el.style.display == "inline" || el.style.display === "" || el.style.display == "none") { |
|
276 el.style.display = "block"; |
|
277 } |
|
278 if (fallback) { |
|
279 el.style.backgroundColor = theme.background; |
|
280 } else { |
|
281 resizable_images.push(el); |
|
282 resizable_update(el); |
|
283 } |
|
284 } |
|
285 } |
|
286 |
|
287 function dimension_check(el, callback) { |
|
288 var dimensions = { |
|
289 height: el.clientHeight, |
|
290 width: el.clientWidth |
|
291 }; |
|
292 if (!dimensions.height && !dimensions.width) { |
|
293 if (el.hasAttribute("data-holder-invisible")) { |
|
294 throw new Error("Holder: placeholder is not visible"); |
|
295 } else { |
|
296 el.setAttribute("data-holder-invisible", true) |
|
297 setTimeout(function () { |
|
298 callback.call(this, el) |
|
299 }, 1) |
|
300 return null; |
|
301 } |
|
302 } else { |
|
303 el.removeAttribute("data-holder-invisible") |
|
304 } |
|
305 return dimensions; |
|
306 } |
|
307 |
|
308 function resizable_update(element) { |
|
309 var images; |
|
310 if (element.nodeType == null) { |
|
311 images = resizable_images; |
|
312 } else { |
|
313 images = [element] |
|
314 } |
|
315 for (var i in images) { |
|
316 if (!images.hasOwnProperty(i)) { |
|
317 continue; |
|
318 } |
|
319 var el = images[i] |
|
320 if (el.holder_data) { |
|
321 var holder = el.holder_data; |
|
322 var dimensions = dimension_check(el, resizable_update) |
|
323 if(dimensions){ |
|
324 if(holder.fluid){ |
|
325 el.setAttribute("src", draw({ |
|
326 ctx: ctx, |
|
327 dimensions: dimensions, |
|
328 template: holder.theme, |
|
329 ratio: ratio, |
|
330 holder: holder |
|
331 })) |
|
332 } |
|
333 if(holder.textmode && holder.textmode == "exact"){ |
|
334 holder.exact_dimensions = dimensions; |
|
335 el.setAttribute("src", draw({ |
|
336 ctx: ctx, |
|
337 dimensions: holder.dimensions, |
|
338 template: holder.theme, |
|
339 ratio: ratio, |
|
340 holder: holder |
|
341 })) |
|
342 } |
|
343 } |
|
344 } |
|
345 } |
|
346 } |
|
347 |
|
348 function parse_flags(flags, options) { |
|
349 var ret = { |
|
350 theme: extend(settings.themes.gray, {}) |
|
351 }; |
|
352 var render = false; |
|
353 for (sl = flags.length, j = 0; j < sl; j++) { |
|
354 var flag = flags[j]; |
|
355 if (app.flags.dimensions.match(flag)) { |
|
356 render = true; |
|
357 ret.dimensions = app.flags.dimensions.output(flag); |
|
358 } else if (app.flags.fluid.match(flag)) { |
|
359 render = true; |
|
360 ret.dimensions = app.flags.fluid.output(flag); |
|
361 ret.fluid = true; |
|
362 } else if (app.flags.textmode.match(flag)) { |
|
363 ret.textmode = app.flags.textmode.output(flag) |
|
364 } else if (app.flags.colors.match(flag)) { |
|
365 ret.theme = app.flags.colors.output(flag); |
|
366 } else if (options.themes[flag]) { |
|
367 //If a theme is specified, it will override custom colors |
|
368 if(options.themes.hasOwnProperty(flag)){ |
|
369 ret.theme = extend(options.themes[flag], {}); |
|
370 } |
|
371 } else if (app.flags.font.match(flag)) { |
|
372 ret.font = app.flags.font.output(flag); |
|
373 } else if (app.flags.auto.match(flag)) { |
|
374 ret.auto = true; |
|
375 } else if (app.flags.text.match(flag)) { |
|
376 ret.text = app.flags.text.output(flag); |
|
377 } |
|
378 } |
|
379 return render ? ret : false; |
|
380 } |
|
381 |
|
382 for (var flag in app.flags) { |
|
383 if (!app.flags.hasOwnProperty(flag)) continue; |
|
384 app.flags[flag].match = function (val) { |
|
385 return val.match(this.regex) |
|
386 } |
|
387 } |
|
388 app.add_theme = function (name, theme) { |
|
389 name != null && theme != null && (settings.themes[name] = theme); |
|
390 return app; |
|
391 }; |
|
392 app.add_image = function (src, el) { |
|
393 var node = selector(el); |
|
394 if (node.length) { |
|
395 for (var i = 0, l = node.length; i < l; i++) { |
|
396 var img = document.createElement("img") |
|
397 img.setAttribute("data-src", src); |
|
398 node[i].appendChild(img); |
|
399 } |
|
400 } |
|
401 return app; |
|
402 }; |
|
403 app.run = function (o) { |
|
404 preempted = true; |
|
405 |
|
406 var options = extend(settings, o), |
|
407 images = [], |
|
408 imageNodes = [], |
|
409 bgnodes = []; |
|
410 if (typeof (options.images) == "string") { |
|
411 imageNodes = selector(options.images); |
|
412 } else if (window.NodeList && options.images instanceof window.NodeList) { |
|
413 imageNodes = options.images; |
|
414 } else if (window.Node && options.images instanceof window.Node) { |
|
415 imageNodes = [options.images]; |
|
416 } |
|
417 |
|
418 if (typeof (options.bgnodes) == "string") { |
|
419 bgnodes = selector(options.bgnodes); |
|
420 } else if (window.NodeList && options.elements instanceof window.NodeList) { |
|
421 bgnodes = options.bgnodes; |
|
422 } else if (window.Node && options.bgnodes instanceof window.Node) { |
|
423 bgnodes = [options.bgnodes]; |
|
424 } |
|
425 for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); |
|
426 var holdercss = document.getElementById("holderjs-style"); |
|
427 if (!holdercss) { |
|
428 holdercss = document.createElement("style"); |
|
429 holdercss.setAttribute("id", "holderjs-style"); |
|
430 holdercss.type = "text/css"; |
|
431 document.getElementsByTagName("head")[0].appendChild(holdercss); |
|
432 } |
|
433 if (!options.nocss) { |
|
434 if (holdercss.styleSheet) { |
|
435 holdercss.styleSheet.cssText += options.stylesheet; |
|
436 } else { |
|
437 holdercss.appendChild(document.createTextNode(options.stylesheet)); |
|
438 } |
|
439 } |
|
440 var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); |
|
441 for (var l = bgnodes.length, i = 0; i < l; i++) { |
|
442 var src = window.getComputedStyle(bgnodes[i], null) |
|
443 .getPropertyValue("background-image"); |
|
444 var flags = src.match(cssregex); |
|
445 var bgsrc = bgnodes[i].getAttribute("data-background-src"); |
|
446 if (flags) { |
|
447 var holder = parse_flags(flags[1].split("/"), options); |
|
448 if (holder) { |
|
449 render("background", bgnodes[i], holder, src); |
|
450 } |
|
451 } else if (bgsrc != null) { |
|
452 var holder = parse_flags(bgsrc.substr(bgsrc.lastIndexOf(options.domain) + options.domain.length + 1) |
|
453 .split("/"), options); |
|
454 if (holder) { |
|
455 render("background", bgnodes[i], holder, src); |
|
456 } |
|
457 } |
|
458 } |
|
459 for (l = images.length, i = 0; i < l; i++) { |
|
460 var attr_data_src, attr_src; |
|
461 attr_src = attr_data_src = src = null; |
|
462 try { |
|
463 attr_src = images[i].getAttribute("src"); |
|
464 attr_datasrc = images[i].getAttribute("data-src"); |
|
465 } catch (e) {} |
|
466 if (attr_datasrc == null && !! attr_src && attr_src.indexOf(options.domain) >= 0) { |
|
467 src = attr_src; |
|
468 } else if ( !! attr_datasrc && attr_datasrc.indexOf(options.domain) >= 0) { |
|
469 src = attr_datasrc; |
|
470 } |
|
471 if (src) { |
|
472 var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) |
|
473 .split("/"), options); |
|
474 if (holder) { |
|
475 if (holder.fluid) { |
|
476 render("fluid", images[i], holder, src) |
|
477 } else { |
|
478 render("image", images[i], holder, src); |
|
479 } |
|
480 } |
|
481 } |
|
482 } |
|
483 return app; |
|
484 }; |
|
485 contentLoaded(win, function () { |
|
486 if (window.addEventListener) { |
|
487 window.addEventListener("resize", resizable_update, false); |
|
488 window.addEventListener("orientationchange", resizable_update, false); |
|
489 } else { |
|
490 window.attachEvent("onresize", resizable_update) |
|
491 } |
|
492 preempted || app.run(); |
|
493 }); |
|
494 if (typeof define === "function" && define.amd) { |
|
495 define([], function () { |
|
496 return app; |
|
497 }); |
|
498 } |
|
499 |
|
500 })(Holder, window); |
|