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