1 | /*
|
---|
2 | * SVG Icon Loader 2.0
|
---|
3 | *
|
---|
4 | * jQuery Plugin for loading SVG icons from a single file
|
---|
5 | *
|
---|
6 | * Copyright (c) 2009 Alexis Deveria
|
---|
7 | * http://a.deveria.com
|
---|
8 | *
|
---|
9 | * Apache 2 License
|
---|
10 |
|
---|
11 | How to use:
|
---|
12 |
|
---|
13 | 1. Create the SVG master file that includes all icons:
|
---|
14 |
|
---|
15 | The master SVG icon-containing file is an SVG file that contains
|
---|
16 | <g> elements. Each <g> element should contain the markup of an SVG
|
---|
17 | icon. The <g> element has an ID that should
|
---|
18 | correspond with the ID of the HTML element used on the page that should contain
|
---|
19 | or optionally be replaced by the icon. Additionally, one empty element should be
|
---|
20 | added at the end with id "svg_eof".
|
---|
21 |
|
---|
22 | 2. Optionally create fallback raster images for each SVG icon.
|
---|
23 |
|
---|
24 | 3. Include the jQuery and the SVG Icon Loader scripts on your page.
|
---|
25 |
|
---|
26 | 4. Run $.svgIcons() when the document is ready:
|
---|
27 |
|
---|
28 | $.svgIcons( file [string], options [object literal]);
|
---|
29 |
|
---|
30 | File is the location of a local SVG or SVGz file.
|
---|
31 |
|
---|
32 | All options are optional and can include:
|
---|
33 |
|
---|
34 | - 'w (number)': The icon widths
|
---|
35 |
|
---|
36 | - 'h (number)': The icon heights
|
---|
37 |
|
---|
38 | - 'fallback (object literal)': List of raster images with each
|
---|
39 | key being the SVG icon ID to replace, and the value the image file name.
|
---|
40 |
|
---|
41 | - 'fallback_path (string)': The path to use for all images
|
---|
42 | listed under "fallback"
|
---|
43 |
|
---|
44 | - 'replace (boolean)': If set to true, HTML elements will be replaced by,
|
---|
45 | rather than include the SVG icon.
|
---|
46 |
|
---|
47 | - 'placement (object literal)': List with selectors for keys and SVG icon ids
|
---|
48 | as values. This provides a custom method of adding icons.
|
---|
49 |
|
---|
50 | - 'resize (object literal)': List with selectors for keys and numbers
|
---|
51 | as values. This allows an easy way to resize specific icons.
|
---|
52 |
|
---|
53 | - 'callback (function)': A function to call when all icons have been loaded.
|
---|
54 | Includes an object literal as its argument with as keys all icon IDs and the
|
---|
55 | icon as a jQuery object as its value.
|
---|
56 |
|
---|
57 | - 'id_match (boolean)': Automatically attempt to match SVG icon ids with
|
---|
58 | corresponding HTML id (default: true)
|
---|
59 |
|
---|
60 | - 'no_img (boolean)': Prevent attempting to convert the icon into an <img>
|
---|
61 | element (may be faster, help for browser consistency)
|
---|
62 |
|
---|
63 | - 'svgz (boolean)': Indicate that the file is an SVGZ file, and thus not to
|
---|
64 | parse as XML. SVGZ files add compression benefits, but getting data from
|
---|
65 | them fails in Firefox 2 and older.
|
---|
66 |
|
---|
67 | 5. To access an icon at a later point without using the callback, use this:
|
---|
68 | $.getSvgIcon(id (string));
|
---|
69 |
|
---|
70 | This will return the icon (as jQuery object) with a given ID.
|
---|
71 |
|
---|
72 | 6. To resize icons at a later point without using the callback, use this:
|
---|
73 | $.resizeSvgIcons(resizeOptions) (use the same way as the "resize" parameter)
|
---|
74 |
|
---|
75 |
|
---|
76 | Example usage #1:
|
---|
77 |
|
---|
78 | $(function() {
|
---|
79 | $.svgIcons('my_icon_set.svg'); // The SVG file that contains all icons
|
---|
80 | // No options have been set, so all icons will automatically be inserted
|
---|
81 | // into HTML elements that match the same IDs.
|
---|
82 | });
|
---|
83 |
|
---|
84 | Example usage #2:
|
---|
85 |
|
---|
86 | $(function() {
|
---|
87 | $.svgIcons('my_icon_set.svg', { // The SVG file that contains all icons
|
---|
88 | callback: function(icons) { // Custom callback function that sets click
|
---|
89 | // events for each icon
|
---|
90 | $.each(icons, function(id, icon) {
|
---|
91 | icon.click(function() {
|
---|
92 | alert('You clicked on the icon with id ' + id);
|
---|
93 | });
|
---|
94 | });
|
---|
95 | }
|
---|
96 | }); //The SVG file that contains all icons
|
---|
97 | });
|
---|
98 |
|
---|
99 | Example usage #3:
|
---|
100 |
|
---|
101 | $(function() {
|
---|
102 | $.svgIcons('my_icon_set.svgz', { // The SVGZ file that contains all icons
|
---|
103 | w: 32, // All icons will be 32px wide
|
---|
104 | h: 32, // All icons will be 32px high
|
---|
105 | fallback_path: 'icons/', // All fallback files can be found here
|
---|
106 | fallback: {
|
---|
107 | '#open_icon': 'open.png', // The "open.png" will be appended to the
|
---|
108 | // HTML element with ID "open_icon"
|
---|
109 | '#close_icon': 'close.png',
|
---|
110 | '#save_icon': 'save.png'
|
---|
111 | },
|
---|
112 | placement: {'.open_icon','open'}, // The "open" icon will be added
|
---|
113 | // to all elements with class "open_icon"
|
---|
114 | resize: function() {
|
---|
115 | '#save_icon .svg_icon': 64 // The "save" icon will be resized to 64 x 64px
|
---|
116 | },
|
---|
117 |
|
---|
118 | callback: function(icons) { // Sets background color for "close" icon
|
---|
119 | icons['close'].css('background','red');
|
---|
120 | },
|
---|
121 |
|
---|
122 | svgz: true // Indicates that an SVGZ file is being used
|
---|
123 |
|
---|
124 | })
|
---|
125 | });
|
---|
126 |
|
---|
127 | */
|
---|
128 |
|
---|
129 |
|
---|
130 | (function($) {
|
---|
131 | var svg_icons = {}, fixIDs;
|
---|
132 |
|
---|
133 | $.svgIcons = function(file, opts) {
|
---|
134 | var svgns = "http://www.w3.org/2000/svg",
|
---|
135 | xlinkns = "http://www.w3.org/1999/xlink",
|
---|
136 | icon_w = opts.w?opts.w : 24,
|
---|
137 | icon_h = opts.h?opts.h : 24,
|
---|
138 | elems, svgdoc, testImg,
|
---|
139 | icons_made = false, data_loaded = false, load_attempts = 0,
|
---|
140 | ua = navigator.userAgent, isOpera = !!window.opera, isSafari = (ua.indexOf('Safari/') > -1 && ua.indexOf('Chrome/')==-1),
|
---|
141 | data_pre = 'data:image/svg+xml;charset=utf-8;base64,';
|
---|
142 |
|
---|
143 | if(opts.svgz) {
|
---|
144 | var data_el = $('<object data="' + file + '" type=image/svg+xml>').appendTo('body').hide();
|
---|
145 | try {
|
---|
146 | svgdoc = data_el[0].contentDocument;
|
---|
147 | data_el.load(getIcons);
|
---|
148 | getIcons(0, true); // Opera will not run "load" event if file is already cached
|
---|
149 | } catch(err1) {
|
---|
150 | useFallback();
|
---|
151 | }
|
---|
152 | } else {
|
---|
153 | var parser = new DOMParser();
|
---|
154 | $.ajax({
|
---|
155 | url: file,
|
---|
156 | dataType: 'string',
|
---|
157 | success: function(data) {
|
---|
158 | if(!data) {
|
---|
159 | $(useFallback);
|
---|
160 | return;
|
---|
161 | }
|
---|
162 | svgdoc = parser.parseFromString(data, "text/xml");
|
---|
163 | $(function() {
|
---|
164 | getIcons('ajax');
|
---|
165 | });
|
---|
166 | },
|
---|
167 | error: function(err) {
|
---|
168 | // TODO: Fix Opera widget icon bug
|
---|
169 | if(window.opera) {
|
---|
170 | $(function() {
|
---|
171 | useFallback();
|
---|
172 | });
|
---|
173 | } else {
|
---|
174 | if(err.responseText) {
|
---|
175 | svgdoc = parser.parseFromString(err.responseText, "text/xml");
|
---|
176 | if(!svgdoc.childNodes.length) {
|
---|
177 | $(useFallback);
|
---|
178 | }
|
---|
179 | $(function() {
|
---|
180 | getIcons('ajax');
|
---|
181 | });
|
---|
182 | } else {
|
---|
183 | $(useFallback);
|
---|
184 | }
|
---|
185 | }
|
---|
186 | }
|
---|
187 | });
|
---|
188 | }
|
---|
189 |
|
---|
190 | function getIcons(evt, no_wait) {
|
---|
191 | if(evt !== 'ajax') {
|
---|
192 | if(data_loaded) return;
|
---|
193 | // Webkit sometimes says svgdoc is undefined, other times
|
---|
194 | // it fails to load all nodes. Thus we must make sure the "eof"
|
---|
195 | // element is loaded.
|
---|
196 | svgdoc = data_el[0].contentDocument; // Needed again for Webkit
|
---|
197 | var isReady = (svgdoc && svgdoc.getElementById('svg_eof'));
|
---|
198 | if(!isReady && !(no_wait && isReady)) {
|
---|
199 | load_attempts++;
|
---|
200 | if(load_attempts < 50) {
|
---|
201 | setTimeout(getIcons, 20);
|
---|
202 | } else {
|
---|
203 | useFallback();
|
---|
204 | data_loaded = true;
|
---|
205 | }
|
---|
206 | return;
|
---|
207 | }
|
---|
208 | data_loaded = true;
|
---|
209 | }
|
---|
210 |
|
---|
211 | elems = $(svgdoc.firstChild).children(); //.getElementsByTagName('foreignContent');
|
---|
212 |
|
---|
213 | if(!opts.no_img) {
|
---|
214 | var testSrc = data_pre + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzUiIGhlaWdodD0iMjc1Ij48L3N2Zz4%3D';
|
---|
215 |
|
---|
216 | testImg = $(new Image()).attr({
|
---|
217 | src: testSrc,
|
---|
218 | width: 0,
|
---|
219 | height: 0
|
---|
220 | }).appendTo('body')
|
---|
221 | .load(function () {
|
---|
222 | // Safari 4 crashes, Opera and Chrome don't
|
---|
223 | makeIcons(true);
|
---|
224 | }).error(function () {
|
---|
225 | makeIcons();
|
---|
226 | });
|
---|
227 | } else {
|
---|
228 | setTimeout(function() {
|
---|
229 | if(!icons_made) makeIcons();
|
---|
230 | },500);
|
---|
231 | }
|
---|
232 | }
|
---|
233 |
|
---|
234 | var setIcon = function(target, icon, id, setID) {
|
---|
235 | if(isOpera) icon.css('visibility','hidden');
|
---|
236 | if(opts.replace) {
|
---|
237 | if(setID) icon.attr('id',id);
|
---|
238 | var cl = target.attr('class');
|
---|
239 | if(cl) icon.attr('class','svg_icon '+cl);
|
---|
240 | target.replaceWith(icon);
|
---|
241 | } else {
|
---|
242 |
|
---|
243 | target.append(icon);
|
---|
244 | }
|
---|
245 | if(isOpera) {
|
---|
246 | setTimeout(function() {
|
---|
247 | icon.removeAttr('style');
|
---|
248 | },1);
|
---|
249 | }
|
---|
250 | }
|
---|
251 |
|
---|
252 | var addIcon = function(icon, id) {
|
---|
253 | if(opts.id_match === undefined || opts.id_match !== false) {
|
---|
254 | setIcon(holder, icon, id, true);
|
---|
255 | }
|
---|
256 | svg_icons[id] = icon;
|
---|
257 | }
|
---|
258 |
|
---|
259 | function makeIcons(toImage, fallback) {
|
---|
260 | if(icons_made) return;
|
---|
261 | if(opts.no_img) toImage = false;
|
---|
262 | var holder;
|
---|
263 |
|
---|
264 | if(toImage) {
|
---|
265 | var temp_holder = $(document.createElement('div'));
|
---|
266 | temp_holder.hide().appendTo('body');
|
---|
267 | }
|
---|
268 | if(fallback) {
|
---|
269 | var path = opts.fallback_path?opts.fallback_path:'';
|
---|
270 | $.each(fallback, function(id, imgsrc) {
|
---|
271 | holder = $('#' + id);
|
---|
272 | var icon = $(new Image())
|
---|
273 | .attr({
|
---|
274 | 'class':'svg_icon',
|
---|
275 | src: path + imgsrc,
|
---|
276 | 'width': icon_w,
|
---|
277 | 'height': icon_h,
|
---|
278 | 'alt': 'icon'
|
---|
279 | });
|
---|
280 |
|
---|
281 | addIcon(icon, id);
|
---|
282 | });
|
---|
283 | } else {
|
---|
284 | var len = elems.length;
|
---|
285 | for(var i = 0; i < len; i++) {
|
---|
286 | var elem = elems[i];
|
---|
287 | var id = elem.id;
|
---|
288 | if(id === 'svg_eof') break;
|
---|
289 | holder = $('#' + id);
|
---|
290 | var svg = elem.getElementsByTagNameNS(svgns, 'svg')[0];
|
---|
291 | var svgroot = document.createElementNS(svgns, "svg");
|
---|
292 | svgroot.setAttributeNS(svgns, 'viewBox', [0,0,icon_w,icon_h].join(' '));
|
---|
293 | // Make flexible by converting width/height to viewBox
|
---|
294 | var w = svg.getAttribute('width');
|
---|
295 | var h = svg.getAttribute('height');
|
---|
296 | svg.removeAttribute('width');
|
---|
297 | svg.removeAttribute('height');
|
---|
298 |
|
---|
299 | var vb = svg.getAttribute('viewBox');
|
---|
300 | if(!vb) {
|
---|
301 | svg.setAttribute('viewBox', [0,0,w,h].join(' '));
|
---|
302 | }
|
---|
303 |
|
---|
304 | // Not using jQuery to be a bit faster
|
---|
305 | svgroot.setAttribute('xmlns', svgns);
|
---|
306 | svgroot.setAttribute('width', icon_w);
|
---|
307 | svgroot.setAttribute('height', icon_h);
|
---|
308 | svgroot.setAttribute("xmlns:xlink", xlinkns);
|
---|
309 | svgroot.setAttribute("class", 'svg_icon');
|
---|
310 |
|
---|
311 | // Without cloning, Firefox will make another GET request.
|
---|
312 | // With cloning, causes issue in Opera/Win/Non-EN
|
---|
313 | if(!isOpera) svg = svg.cloneNode(true);
|
---|
314 |
|
---|
315 | svgroot.appendChild(svg);
|
---|
316 |
|
---|
317 | if(toImage) {
|
---|
318 | // Without cloning, Safari will crash
|
---|
319 | // With cloning, causes issue in Opera/Win/Non-EN
|
---|
320 | var svgcontent = isOpera?svgroot:svgroot.cloneNode(true);
|
---|
321 | temp_holder.empty().append(svgroot);
|
---|
322 | var str = data_pre + encode64(temp_holder.html());
|
---|
323 | var icon = $(new Image())
|
---|
324 | .attr({'class':'svg_icon', src:str});
|
---|
325 | } else {
|
---|
326 | var icon = fixIDs($(svgroot), i);
|
---|
327 | }
|
---|
328 | addIcon(icon, id);
|
---|
329 | }
|
---|
330 |
|
---|
331 | }
|
---|
332 |
|
---|
333 | if(opts.placement) {
|
---|
334 | $.each(opts.placement, function(sel, id) {
|
---|
335 | if(!svg_icons[id]) return;
|
---|
336 | $(sel).each(function(i) {
|
---|
337 | var copy = svg_icons[id].clone();
|
---|
338 | if(i > 0 && !toImage) copy = fixIDs(copy, i, true);
|
---|
339 | setIcon($(this), copy, id);
|
---|
340 | })
|
---|
341 | });
|
---|
342 | }
|
---|
343 | if(!fallback) {
|
---|
344 | if(toImage) temp_holder.remove();
|
---|
345 | if(data_el) data_el.remove();
|
---|
346 | if(testImg) testImg.remove();
|
---|
347 | }
|
---|
348 | if(opts.resize) $.resizeSvgIcons(opts.resize);
|
---|
349 | icons_made = true;
|
---|
350 |
|
---|
351 | if(opts.callback) opts.callback(svg_icons);
|
---|
352 | }
|
---|
353 |
|
---|
354 | fixIDs = function(svg_el, svg_num, force) {
|
---|
355 | var defs = svg_el.find('defs');
|
---|
356 | if(!defs.length) return svg_el;
|
---|
357 |
|
---|
358 | if(isOpera) {
|
---|
359 | var id_elems = defs.find('*').filter(function() {
|
---|
360 | return !!this.id;
|
---|
361 | });
|
---|
362 | } else {
|
---|
363 | var id_elems = defs.find('[id]');
|
---|
364 | }
|
---|
365 |
|
---|
366 | var all_elems = svg_el[0].getElementsByTagName('*'), len = all_elems.length;
|
---|
367 |
|
---|
368 | id_elems.each(function(i) {
|
---|
369 | var id = this.id;
|
---|
370 | var no_dupes = ($(svgdoc).find('#' + id).length <= 1);
|
---|
371 | if(isOpera) no_dupes = false; // Opera didn't clone svg_el, so not reliable
|
---|
372 | // if(!force && no_dupes) return;
|
---|
373 | var new_id = 'x' + id + svg_num + i;
|
---|
374 | this.id = new_id;
|
---|
375 |
|
---|
376 | var old_val = 'url(#' + id + ')';
|
---|
377 | var new_val = 'url(#' + new_id + ')';
|
---|
378 |
|
---|
379 | for(var i = 0; i < len; i++) {
|
---|
380 | var elem = all_elems[i];
|
---|
381 | if(elem.getAttribute('fill') === old_val) {
|
---|
382 | elem.setAttribute('fill', new_val);
|
---|
383 | }
|
---|
384 | if(elem.getAttribute('stroke') === old_val) {
|
---|
385 | elem.setAttribute('stroke', new_val);
|
---|
386 | }
|
---|
387 | if(elem.getAttribute('filter') === old_val) {
|
---|
388 | elem.setAttribute('filter', new_val);
|
---|
389 | }
|
---|
390 | }
|
---|
391 | });
|
---|
392 | return svg_el;
|
---|
393 | }
|
---|
394 |
|
---|
395 | function useFallback() {
|
---|
396 | if(file.indexOf('.svgz') != -1) {
|
---|
397 | var reg_file = file.replace('.svgz','.svg');
|
---|
398 | if(window.console) {
|
---|
399 | console.log('.svgz failed, trying with .svg');
|
---|
400 | }
|
---|
401 | $.svgIcons(reg_file, opts);
|
---|
402 | } else if(opts.fallback) {
|
---|
403 | makeIcons(false, opts.fallback);
|
---|
404 | }
|
---|
405 | }
|
---|
406 |
|
---|
407 | function encode64(input) {
|
---|
408 | // base64 strings are 4/3 larger than the original string
|
---|
409 | if(window.btoa) return window.btoa(input);
|
---|
410 | var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
---|
411 | var output = new Array( Math.floor( (input.length + 2) / 3 ) * 4 );
|
---|
412 | var chr1, chr2, chr3;
|
---|
413 | var enc1, enc2, enc3, enc4;
|
---|
414 | var i = 0, p = 0;
|
---|
415 |
|
---|
416 | do {
|
---|
417 | chr1 = input.charCodeAt(i++);
|
---|
418 | chr2 = input.charCodeAt(i++);
|
---|
419 | chr3 = input.charCodeAt(i++);
|
---|
420 |
|
---|
421 | enc1 = chr1 >> 2;
|
---|
422 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
---|
423 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
---|
424 | enc4 = chr3 & 63;
|
---|
425 |
|
---|
426 | if (isNaN(chr2)) {
|
---|
427 | enc3 = enc4 = 64;
|
---|
428 | } else if (isNaN(chr3)) {
|
---|
429 | enc4 = 64;
|
---|
430 | }
|
---|
431 |
|
---|
432 | output[p++] = _keyStr.charAt(enc1);
|
---|
433 | output[p++] = _keyStr.charAt(enc2);
|
---|
434 | output[p++] = _keyStr.charAt(enc3);
|
---|
435 | output[p++] = _keyStr.charAt(enc4);
|
---|
436 | } while (i < input.length);
|
---|
437 |
|
---|
438 | return output.join('');
|
---|
439 | }
|
---|
440 | }
|
---|
441 |
|
---|
442 | $.getSvgIcon = function(id, uniqueClone) {
|
---|
443 | var icon = svg_icons[id];
|
---|
444 | if(uniqueClone && icon) {
|
---|
445 | icon = fixIDs(icon, 0, true).clone(true);
|
---|
446 | }
|
---|
447 | return icon;
|
---|
448 | }
|
---|
449 |
|
---|
450 | $.resizeSvgIcons = function(obj) {
|
---|
451 | // FF2 and older don't detect .svg_icon, so we change it detect svg elems instead
|
---|
452 | var change_sel = !$('.svg_icon:first').length;
|
---|
453 | $.each(obj, function(sel, size) {
|
---|
454 | var arr = $.isArray(size);
|
---|
455 | var w = arr?size[0]:size,
|
---|
456 | h = arr?size[1]:size;
|
---|
457 | if(change_sel) {
|
---|
458 | sel = sel.replace(/\.svg_icon/g,'svg');
|
---|
459 | }
|
---|
460 | $(sel).each(function() {
|
---|
461 | this.setAttribute('width', w);
|
---|
462 | this.setAttribute('height', h);
|
---|
463 | if(window.opera && window.widget) {
|
---|
464 | this.parentNode.style.width = w + 'px';
|
---|
465 | this.parentNode.style.height = h + 'px';
|
---|
466 | }
|
---|
467 | });
|
---|
468 | });
|
---|
469 | }
|
---|
470 |
|
---|
471 | })(jQuery); |
---|