|
1 /* |
|
2 |
|
3 Holder - 1.9 - client side image placeholders |
|
4 (c) 2012-2013 Ivan Malopinsky / http://imsky.co |
|
5 |
|
6 Provided under the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 |
|
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 |
|
18 //getElementsByClassName polyfill |
|
19 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}) |
|
20 |
|
21 //getComputedStyle polyfill |
|
22 window.getComputedStyle||(window.getComputedStyle=function(e,t){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}) |
|
23 |
|
24 //http://javascript.nwbox.com/ContentLoaded by Diego Perini with modifications |
|
25 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)}}; |
|
26 |
|
27 //https://gist.github.com/991057 by Jed Schmidt with modifications |
|
28 function selector(a){ |
|
29 a=a.match(/^(\W)?(.*)/);var b=document["getElement"+(a[1]?a[1]=="#"?"ById":"sByClassName":"sByTagName")](a[2]); |
|
30 var ret=[]; b!=null&&(b.length?ret=b:b.length==0?ret=b:ret=[b]); return ret; |
|
31 } |
|
32 |
|
33 //shallow object property extend |
|
34 function extend(a,b){var c={};for(var d in a)c[d]=a[d];for(var e in b)c[e]=b[e];return c} |
|
35 |
|
36 //hasOwnProperty polyfill |
|
37 if (!Object.prototype.hasOwnProperty) |
|
38 Object.prototype.hasOwnProperty = function(prop) { |
|
39 var proto = this.__proto__ || this.constructor.prototype; |
|
40 return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]); |
|
41 } |
|
42 |
|
43 function text_size(width, height, template) { |
|
44 var dimension_arr = [height, width].sort(); |
|
45 var maxFactor = Math.round(dimension_arr[1] / 16), |
|
46 minFactor = Math.round(dimension_arr[0] / 16); |
|
47 var text_height = Math.max(template.size, maxFactor); |
|
48 return { |
|
49 height: text_height |
|
50 } |
|
51 } |
|
52 |
|
53 function draw(ctx, dimensions, template, ratio) { |
|
54 var ts = text_size(dimensions.width, dimensions.height, template); |
|
55 var text_height = ts.height; |
|
56 var width = dimensions.width * ratio, height = dimensions.height * ratio; |
|
57 var font = template.font ? template.font : "sans-serif"; |
|
58 canvas.width = width; |
|
59 canvas.height = height; |
|
60 ctx.textAlign = "center"; |
|
61 ctx.textBaseline = "middle"; |
|
62 ctx.fillStyle = template.background; |
|
63 ctx.fillRect(0, 0, width, height); |
|
64 ctx.fillStyle = template.foreground; |
|
65 ctx.font = "bold " + text_height + "px "+font; |
|
66 var text = template.text ? template.text : (dimensions.width + "x" + dimensions.height); |
|
67 if (ctx.measureText(text).width / width > 1) { |
|
68 text_height = template.size / (ctx.measureText(text).width / width); |
|
69 } |
|
70 //Resetting font size if necessary |
|
71 ctx.font = "bold " + (text_height * ratio) + "px "+font; |
|
72 ctx.fillText(text, (width / 2), (height / 2), width); |
|
73 return canvas.toDataURL("image/png"); |
|
74 } |
|
75 |
|
76 function render(mode, el, holder, src) { |
|
77 var dimensions = holder.dimensions, |
|
78 theme = holder.theme, |
|
79 text = holder.text ? decodeURIComponent(holder.text) : holder.text; |
|
80 var dimensions_caption = dimensions.width + "x" + dimensions.height; |
|
81 theme = (text ? extend(theme, { text: text }) : theme); |
|
82 theme = (holder.font ? extend(theme, {font: holder.font}) : theme); |
|
83 |
|
84 var ratio = 1; |
|
85 if(window.devicePixelRatio && window.devicePixelRatio > 1){ |
|
86 ratio = window.devicePixelRatio; |
|
87 } |
|
88 |
|
89 if (mode == "image") { |
|
90 el.setAttribute("data-src", src); |
|
91 el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); |
|
92 |
|
93 if(fallback || !holder.auto){ |
|
94 el.style.width = dimensions.width + "px"; |
|
95 el.style.height = dimensions.height + "px"; |
|
96 } |
|
97 |
|
98 if (fallback) { |
|
99 el.style.backgroundColor = theme.background; |
|
100 |
|
101 } |
|
102 else{ |
|
103 el.setAttribute("src", draw(ctx, dimensions, theme, ratio)); |
|
104 } |
|
105 } else { |
|
106 if (!fallback) { |
|
107 el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")"; |
|
108 el.style.backgroundSize = dimensions.width+"px "+dimensions.height+"px"; |
|
109 } |
|
110 } |
|
111 }; |
|
112 |
|
113 function fluid(el, holder, src) { |
|
114 var dimensions = holder.dimensions, |
|
115 theme = holder.theme, |
|
116 text = holder.text; |
|
117 var dimensions_caption = dimensions.width + "x" + dimensions.height; |
|
118 theme = (text ? extend(theme, { |
|
119 text: text |
|
120 }) : theme); |
|
121 |
|
122 var fluid = document.createElement("div"); |
|
123 |
|
124 fluid.style.backgroundColor = theme.background; |
|
125 fluid.style.color = theme.foreground; |
|
126 fluid.className = el.className + " holderjs-fluid"; |
|
127 fluid.style.width = holder.dimensions.width + (holder.dimensions.width.indexOf("%")>0?"":"px"); |
|
128 fluid.style.height = holder.dimensions.height + (holder.dimensions.height.indexOf("%")>0?"":"px"); |
|
129 fluid.id = el.id; |
|
130 |
|
131 el.style.width=0; |
|
132 el.style.height=0; |
|
133 |
|
134 if (theme.text) { |
|
135 fluid.appendChild(document.createTextNode(theme.text)) |
|
136 } else { |
|
137 fluid.appendChild(document.createTextNode(dimensions_caption)) |
|
138 fluid_images.push(fluid); |
|
139 setTimeout(fluid_update, 0); |
|
140 } |
|
141 |
|
142 el.parentNode.insertBefore(fluid, el.nextSibling) |
|
143 |
|
144 if(window.jQuery){ |
|
145 jQuery(function($){ |
|
146 $(el).on("load", function(){ |
|
147 el.style.width = fluid.style.width; |
|
148 el.style.height = fluid.style.height; |
|
149 $(el).show(); |
|
150 $(fluid).remove(); |
|
151 }); |
|
152 }) |
|
153 } |
|
154 } |
|
155 |
|
156 function fluid_update() { |
|
157 for (i in fluid_images) { |
|
158 if(!fluid_images.hasOwnProperty(i)) continue; |
|
159 var el = fluid_images[i], |
|
160 label = el.firstChild; |
|
161 |
|
162 el.style.lineHeight = el.offsetHeight+"px"; |
|
163 label.data = el.offsetWidth + "x" + el.offsetHeight; |
|
164 } |
|
165 } |
|
166 |
|
167 function parse_flags(flags, options) { |
|
168 |
|
169 var ret = { |
|
170 theme: settings.themes.gray |
|
171 }, render = false; |
|
172 |
|
173 for (sl = flags.length, j = 0; j < sl; j++) { |
|
174 var flag = flags[j]; |
|
175 if (app.flags.dimensions.match(flag)) { |
|
176 render = true; |
|
177 ret.dimensions = app.flags.dimensions.output(flag); |
|
178 } else if (app.flags.fluid.match(flag)) { |
|
179 render = true; |
|
180 ret.dimensions = app.flags.fluid.output(flag); |
|
181 ret.fluid = true; |
|
182 } else if (app.flags.colors.match(flag)) { |
|
183 ret.theme = app.flags.colors.output(flag); |
|
184 } else if (options.themes[flag]) { |
|
185 //If a theme is specified, it will override custom colors |
|
186 ret.theme = options.themes[flag]; |
|
187 } else if (app.flags.text.match(flag)) { |
|
188 ret.text = app.flags.text.output(flag); |
|
189 } else if(app.flags.font.match(flag)){ |
|
190 ret.font = app.flags.font.output(flag); |
|
191 } |
|
192 else if(app.flags.auto.match(flag)){ |
|
193 ret.auto = true; |
|
194 } |
|
195 } |
|
196 |
|
197 return render ? ret : false; |
|
198 |
|
199 }; |
|
200 |
|
201 if (!canvas.getContext) { |
|
202 fallback = true; |
|
203 } else { |
|
204 if (canvas.toDataURL("image/png") |
|
205 .indexOf("data:image/png") < 0) { |
|
206 //Android doesn't support data URI |
|
207 fallback = true; |
|
208 } else { |
|
209 var ctx = canvas.getContext("2d"); |
|
210 } |
|
211 } |
|
212 |
|
213 var fluid_images = []; |
|
214 |
|
215 var settings = { |
|
216 domain: "holder.js", |
|
217 images: "img", |
|
218 bgnodes: ".holderjs", |
|
219 themes: { |
|
220 "gray": { |
|
221 background: "#eee", |
|
222 foreground: "#aaa", |
|
223 size: 12 |
|
224 }, |
|
225 "social": { |
|
226 background: "#3a5a97", |
|
227 foreground: "#fff", |
|
228 size: 12 |
|
229 }, |
|
230 "industrial": { |
|
231 background: "#434A52", |
|
232 foreground: "#C2F200", |
|
233 size: 12 |
|
234 } |
|
235 }, |
|
236 stylesheet: ".holderjs-fluid {font-size:16px;font-weight:bold;text-align:center;font-family:sans-serif;margin:0}" |
|
237 }; |
|
238 |
|
239 |
|
240 app.flags = { |
|
241 dimensions: { |
|
242 regex: /^(\d+)x(\d+)$/, |
|
243 output: function (val) { |
|
244 var exec = this.regex.exec(val); |
|
245 return { |
|
246 width: +exec[1], |
|
247 height: +exec[2] |
|
248 } |
|
249 } |
|
250 }, |
|
251 fluid: { |
|
252 regex: /^([0-9%]+)x([0-9%]+)$/, |
|
253 output: function (val) { |
|
254 var exec = this.regex.exec(val); |
|
255 return { |
|
256 width: exec[1], |
|
257 height: exec[2] |
|
258 } |
|
259 } |
|
260 }, |
|
261 colors: { |
|
262 regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, |
|
263 output: function (val) { |
|
264 var exec = this.regex.exec(val); |
|
265 return { |
|
266 size: settings.themes.gray.size, |
|
267 foreground: "#" + exec[2], |
|
268 background: "#" + exec[1] |
|
269 } |
|
270 } |
|
271 }, |
|
272 text: { |
|
273 regex: /text\:(.*)/, |
|
274 output: function (val) { |
|
275 return this.regex.exec(val)[1]; |
|
276 } |
|
277 }, |
|
278 font: { |
|
279 regex: /font\:(.*)/, |
|
280 output: function(val){ |
|
281 return this.regex.exec(val)[1]; |
|
282 } |
|
283 }, |
|
284 auto: { |
|
285 regex: /^auto$/ |
|
286 } |
|
287 } |
|
288 |
|
289 for (var flag in app.flags) { |
|
290 if(!app.flags.hasOwnProperty(flag)) continue; |
|
291 app.flags[flag].match = function (val) { |
|
292 return val.match(this.regex) |
|
293 } |
|
294 } |
|
295 |
|
296 app.add_theme = function (name, theme) { |
|
297 name != null && theme != null && (settings.themes[name] = theme); |
|
298 return app; |
|
299 }; |
|
300 |
|
301 app.add_image = function (src, el) { |
|
302 var node = selector(el); |
|
303 if (node.length) { |
|
304 for (var i = 0, l = node.length; i < l; i++) { |
|
305 var img = document.createElement("img") |
|
306 img.setAttribute("data-src", src); |
|
307 node[i].appendChild(img); |
|
308 } |
|
309 } |
|
310 return app; |
|
311 }; |
|
312 |
|
313 app.run = function (o) { |
|
314 var options = extend(settings, o), images = []; |
|
315 |
|
316 if(options.images instanceof window.NodeList){ |
|
317 imageNodes = options.images; |
|
318 } |
|
319 else if(options.images instanceof window.Node){ |
|
320 imageNodes = [options.images]; |
|
321 } |
|
322 else{ |
|
323 imageNodes = selector(options.images); |
|
324 } |
|
325 |
|
326 if(options.elements instanceof window.NodeList){ |
|
327 bgnodes = options.bgnodes; |
|
328 } |
|
329 else if(options.bgnodes instanceof window.Node){ |
|
330 bgnodes = [options.bgnodes]; |
|
331 } |
|
332 else{ |
|
333 bgnodes = selector(options.bgnodes); |
|
334 } |
|
335 |
|
336 preempted = true; |
|
337 |
|
338 for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); |
|
339 |
|
340 var holdercss = document.getElementById("holderjs-style"); |
|
341 |
|
342 if(!holdercss){ |
|
343 holdercss = document.createElement("style"); |
|
344 holdercss.setAttribute("id", "holderjs-style"); |
|
345 holdercss.type = "text/css"; |
|
346 document.getElementsByTagName("head")[0].appendChild(holdercss); |
|
347 } |
|
348 |
|
349 if(holdercss.styleSheet){ |
|
350 holdercss.styleSheet += options.stylesheet; |
|
351 } |
|
352 else{ |
|
353 holdercss.textContent+= options.stylesheet; |
|
354 } |
|
355 |
|
356 var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); |
|
357 |
|
358 for (var l = bgnodes.length, i = 0; i < l; i++) { |
|
359 var src = window.getComputedStyle(bgnodes[i], null) |
|
360 .getPropertyValue("background-image"); |
|
361 var flags = src.match(cssregex); |
|
362 if (flags) { |
|
363 var holder = parse_flags(flags[1].split("/"), options); |
|
364 if (holder) { |
|
365 render("background", bgnodes[i], holder, src); |
|
366 } |
|
367 } |
|
368 } |
|
369 |
|
370 for (var l = images.length, i = 0; i < l; i++) { |
|
371 var src = images[i].getAttribute("src") || images[i].getAttribute("data-src"); |
|
372 if (src != null && src.indexOf(options.domain) >= 0) { |
|
373 var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) |
|
374 .split("/"), options); |
|
375 if (holder) { |
|
376 if (holder.fluid) { |
|
377 fluid(images[i], holder, src); |
|
378 } else { |
|
379 render("image", images[i], holder, src); |
|
380 } |
|
381 } |
|
382 } |
|
383 } |
|
384 return app; |
|
385 }; |
|
386 |
|
387 contentLoaded(win, function () { |
|
388 if (window.addEventListener) { |
|
389 window.addEventListener("resize", fluid_update, false); |
|
390 window.addEventListener("orientationchange", fluid_update, false); |
|
391 } else { |
|
392 window.attachEvent("onresize", fluid_update) |
|
393 } |
|
394 preempted || app.run(); |
|
395 }); |
|
396 |
|
397 if ( typeof define === "function" && define.amd ) { |
|
398 define( "Holder", [], function () { return app; } ); |
|
399 } |
|
400 |
|
401 })(Holder, window); |