24 |
25 |
26 |
27 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
Beginner
149 |
152 |
153 |
154 |
155 |
160 |
167 |
176 |
181 |
186 |
187 |
188 |
281 |
282 |
283 |
Advanced
284 |
287 |
288 |
289 |
292 |
295 |
298 |
301 |
304 |
305 |
306 |
309 |
312 |
315 |
318 |
321 |
322 |
323 |
326 |
329 |
332 |
335 |
336 |
337 | Here, we'll put a simple “sequenced scene” animation
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
--------------------------------------------------------------------------------
/js/svg-playground.js:
--------------------------------------------------------------------------------
1 |
2 | $(function() {
3 |
4 | //Determines if we're listening for click or touch events
5 | var clickEvent = Modernizr.touch ? 'touchstart' : 'click';
6 |
7 | //Helper to abstract the unfortunate setTimeouts
8 | var deferAnimation = function(paper, attrs, duration, easing, delay, callback) {
9 | setTimeout(function() {
10 | paper.stop().animate(attrs, duration, easing, callback);
11 | }, delay);
12 | };
13 |
14 | var wobble = function(t, el) {
15 | if (t <= 0) {
16 | el.stop().animate({transform: 'r0 48,96'}, 100, mina.easeout);//reset center
17 | return;
18 | }
19 | var half = Math.round(t/2);
20 |
21 | el.stop().animate({transform: 'r' +half+ ' 48,96'}, 100*half, mina.easeout, function() {
22 | el.stop().animate({transform: 'r-' +half+ ' 48,96'}, 100*half, mina.easeout, function() {
23 | --t;
24 | wobble(t, el);
25 | });
26 | });
27 | };
28 |
29 | var loadSvg = function(elSelector, size, externalSvgUrl, onSvgLoadedFn) {
30 | var $svgEl = $(elSelector);
31 | var $button = $svgEl.closest('.button');
32 | var svg = Snap(size, size);
33 | svg.attr('viewBox', '0 0 '+size+' '+size);
34 | $svgEl.append(svg.node);
35 | //Load our external SVG
36 | var url = externalSvgUrl;
37 |
38 | Snap.load(url, function(fragment) {
39 | onSvgLoadedFn.call(null, fragment, svg, $button);
40 | });
41 | };
42 |
43 |
44 |
45 | /*************************************************************************\
46 | ** CLOUDS THUNDER RAIN ANIMATION *****************************************\
47 | **************************************************************************/
48 |
49 | var initCloudsAnimation = function() {
50 | loadSvg('.icon-clouds', 96, 'svg/clouds-thunder-rain.svg', function (fragment, svg, $button) {
51 | //Starts out with clouds displayed
52 | var clouds = fragment.select('#clouds');
53 | svg.append(clouds);
54 | var thunder = fragment.select('#thunder');
55 | var rightRain = fragment.select('#right-rain');
56 | var middleRain = fragment.select('#middle-rain');
57 | var leftRain = fragment.select('#left-rain');
58 | var leftBolt = thunder.select('#left-bolt');
59 | var rightBolt = thunder.select('#right-bolt');
60 | var middleBolt = thunder.select('#middle-bolt');
61 |
62 | $button.on(clickEvent, function(e) {
63 | animateRain(svg, leftRain, middleRain, rightRain);
64 | animateCloud(svg, clouds);
65 | animateThunder(svg, leftBolt, middleBolt, rightBolt);
66 | });
67 | });
68 | };
69 |
70 | var animateCloud = function(svg, clouds) {
71 | var cloudPath = clouds.select('path');
72 |
73 | //Oscillate the color and add some semi-random movement
74 | for(var i = 0; i < 8; i++) {
75 | //Color
76 | var isEven = i % 2 === 0;
77 | var fillColor = isEven ? '#bbbbbb' : '#ffffff';
78 |
79 | deferAnimation(cloudPath, {fill: fillColor}, 1000, mina.easeout, i * 700);
80 |
81 | //Movement
82 | if (i===4 || i===7) {
83 | var randomTransform = Math.floor(Math.random()*6);
84 | var sign = isEven ? '-' : '';
85 | deferAnimation(clouds, {'transform': 't' + sign + randomTransform + ' 0 0'}, 225, mina.easeout, i*250);
86 | }
87 | }
88 | clouds.animate({'transform': 't0 0 0'}, 200);//reset
89 | };
90 |
91 | var doRain = function (svg, rain, counter, delay, speed, totalSequences) {
92 | if (counter === totalSequences) {return;}
93 | setTimeout(function() {
94 | if (counter === 1) svg.append(rain);
95 | rain.attr({opacity: 1});
96 | rain.animate({'transform': 't0 50'}, speed, mina.easeout, function() {
97 | rain.animate({'transform': 't0 0', opacity: 0}, 25, mina.easeout, function() {
98 | doRain(svg, rain, counter, delay, speed, totalSequences);
99 | });
100 | });
101 | counter++;
102 | }, delay);
103 | }
104 |
105 | var animateRain = function(svg, leftRain, middleRain, rightRain) {
106 | var counter = 1;
107 | setTimeout(function() {
108 | doRain(svg, leftRain, counter, 50, 600, 4);
109 | doRain(svg, middleRain, counter, 0, 1000, 4);
110 | doRain(svg, rightRain, counter, 0, 1600, 3);
111 | }, 600);
112 | };
113 |
114 | var animateThunder = function(svg, leftBolt, middleBolt, rightBolt) {
115 | setTimeout(function() {
116 | svg.append(leftBolt);
117 | deferAnimation(leftBolt, {'opacity': '0'}, 250, mina.easeout, 250);
118 | deferAnimation(leftBolt, {'opacity': '1', 'transform': 's1.5,1.5'}, 250, mina.easeout, 1500);
119 | deferAnimation(leftBolt, {'opacity': '0'}, 250, mina.easeout, 2000);
120 | }, 500);
121 | setTimeout(function() {
122 | svg.append(middleBolt);
123 | deferAnimation(middleBolt, {'opacity': '0'}, 300, mina.easeout, 100);
124 | }, 1200);
125 | setTimeout(function() {
126 | svg.append(rightBolt);
127 | deferAnimation(rightBolt, {'opacity': '0'}, 150, mina.easeout, 250);
128 | deferAnimation(rightBolt, {'opacity': '1', transform: 's1.5,1.5'}, 150, mina.easeout, 2500);
129 | deferAnimation(rightBolt, {'opacity': '0'}, 150, mina.easeout, 3000);
130 | deferAnimation(rightBolt, {'opacity': '1', transform: 's2,2'}, 150, mina.easeout, 3500);
131 | deferAnimation(rightBolt, {'opacity': '0'}, 150, mina.easeout, 3800);
132 | }, 50);
133 | };
134 |
135 |
136 |
137 | /*************************************************************************\
138 | ** ROCKET SHIP ANIMATION *************************************************\
139 | **************************************************************************/
140 | var initRocketShipAnimation = function() {
141 | loadSvg('.icon-rocket-ship', 96, 'svg/rocket-ship.svg', function (fragment, svg, $button) {
142 | var ship = fragment.select('#ship');
143 | svg.append(ship);
144 |
145 | var exhaust = fragment.select('#exhaust');
146 |
147 | //On click event fires off the animation sequence
148 | $button.on(clickEvent, function(e) {
149 | exhaust.attr({transform: 't0,5 s.2,.2', opacity: '.1'})
150 | animateExhaust(svg, exhaust, ship);
151 | });
152 | });
153 | }
154 |
155 | var animateRocketShip = function(ship) {
156 | ship.animate({transform: 't75,-150'}, 2000, mina.easeout, function() {
157 | ship.attr({transform: 't-300,300', opacity: 0});
158 | ship.stop().animate({transform: 't0,0', opacity: 1}, 1000, mina.easeout);
159 | });
160 | };
161 |
162 | var animateExhaust = function(svg, exhaust, ship) {
163 | //Tilt ship
164 | ship.animate({transform: 't-4,4 r15, 48,48'}, 30, mina.bounce, function() {
165 | ship.stop().animate({transform: 't0,0 r0, 48,48'}, 200, mina.easeout);
166 |
167 | //Exhaust diffusion
168 | svg.append(exhaust);
169 | deferAnimation(exhaust, {transform: 't-10,15 s1.3,1.3', opacity: .3}, 200, mina.easeout, 50, function() {
170 | exhaust.animate({transform: 't-15,20 s5,5', opacity: 0}, 1500);
171 |
172 | //Now launch ship!
173 | animateRocketShip(ship);
174 | });
175 | });
176 | };
177 |
178 |
179 | /*************************************************************************\
180 | ** SHOPPING CART ANIMATION ***********************************************\
181 | **************************************************************************/
182 | var initShoppingCartAnimation = function() {
183 | loadSvg('.icon-cart', 96, 'svg/cart.svg', function (fragment, svg, $button) {
184 | var cart = fragment.select('#cart');
185 | var cartBody = fragment.select('#cart-body');
186 | var wheels = fragment.select('#cart-wheels');
187 | var items = fragment.select('#cart-items');
188 | svg.append(cartBody);
189 | svg.append(wheels);
190 | $button.on(clickEvent, function(e) {
191 | items.attr({transform: 't0, -100', opacity: .95});
192 | svg.append(items);
193 | items.stop().animate({transform: 't0,0', opacity: .9}, 1000, mina.bounce, function() {
194 | cartBody.stop().animate({transform: 'r2, 96,96'}, 250, mina.backout, function() {
195 | cartBody.attr({transform: 'r0 96,96'});
196 | cartBody.stop().animate({transform: 't-200, 0'}, 500, mina.easeout);
197 | wheels.stop().animate({transform: 't-200, 0'}, 500, mina.easeout);
198 | items.stop().animate({transform: 't-200, 0'}, 500, mina.easeout, function() {
199 | cartBody.attr({opacity: 0, transform: 't0, 0'});
200 | wheels.attr({opacity: 0, transform: 't0, 0'});
201 | cartBody.stop().animate({opacity: 1}, 2000, mina.easeout);
202 | wheels.stop().animate({opacity: 1}, 2000, mina.easeout);
203 | });
204 | });
205 | });
206 | });
207 | });
208 | };
209 |
210 | /*************************************************************************\
211 | ** SHARE ANIMATION *******************************************************\
212 | **************************************************************************/
213 | var initSharingAnimation = function() {
214 | loadSvg('.icon-share', 64, 'svg/share.svg', function (fragment, svg, $button) {
215 | var group = fragment.select('g');
216 | svg.append(group);
217 | var circleLeft = group.select('#circle-left');
218 | var topRight = group.select('#top-right');
219 | var bottomRight = group.select('#bottom-right');
220 |
221 | $button.on(clickEvent, function(e) {
222 | topRight.attr({opacity: 0, transform: 't-10,5'});
223 | topRight.stop().animate({transform: 't0,0', opacity: 1}, 400, mina.bounce);
224 | bottomRight.attr({opacity: 0, transform: 't-10,-5'});
225 | deferAnimation(bottomRight, {transform: 't0,0', opacity: 1}, 300, mina.easeout, 50);
226 |
227 | });
228 | });
229 | };
230 |
231 |
232 | /*************************************************************************\
233 | ** MIC STAND ANIMATION ****************************************************\
234 | **************************************************************************/
235 | var initMicrophoneAnimation = function() {
236 | loadSvg('.icon-microphone', 64, 'svg/mic-stand.svg', function (fragment, svg, $button) {
237 | var group = fragment.select('g');
238 | svg.append(group);
239 | var mic = group.select('#mic');
240 |
241 | $button.on(clickEvent, function(e) {
242 | wobble(3, mic);
243 | });
244 | });
245 | };
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | //
255 | // TODO
256 | //
257 |
258 |
259 |
260 |
261 |
262 | /*************************************************************************\
263 | ** ALARM CLOCK ANIMATION *************************************************\
264 | **************************************************************************/
265 | var initAlarmClockAnimation = function() {
266 | loadSvg('.icon-alarm-clock', 64, 'svg/alarm-clock.svg', function (fragment, svg, $button) {
267 | var clockGroup = fragment.select('#clock');
268 | var ringEffectGroup = fragment.select('#ring-effect');
269 | svg.append(clockGroup);
270 | ringEffectGroup.attr({opacity: 0});
271 | svg.append(ringEffectGroup);
272 |
273 | var leftBell = clockGroup.select('#bell-left');
274 | var rightBell = clockGroup.select('#bell-right');
275 |
276 | $button.on(clickEvent, function(e) {
277 | console.log("TODO .. animate");
278 | });
279 | });
280 | };
281 |
282 | /*************************************************************************\
283 | ** USB ANIMATION *************************************************\
284 | **************************************************************************/
285 | var initUSBAnimation = function() {
286 | loadSvg('.icon-usb', 64, 'svg/usb.svg', function (fragment, svg, $button) {
287 | var group = fragment.select('g');
288 | var path = group.select('path');
289 | svg.append(group);
290 |
291 | $button.on(clickEvent, function(e) {
292 | console.log("TODO .. animate");
293 | });
294 | });
295 | };
296 |
297 |
298 | /*************************************************************************\
299 | ** QUOTES ANIMATION ******************************************************\
300 | **************************************************************************/
301 | var initQuotesRightAnimation = function() {
302 | loadSvg('.icon-quotes-right', 64, 'svg/quotes-right.svg', function (fragment, svg, $button) {
303 | var group = fragment.select('g');
304 | svg.append(group);
305 |
306 | var quoteLeft = group.select('#quote-left');
307 | var quoteRight = group.select('#quote-right');
308 |
309 | $button.on(clickEvent, function(e) {
310 | quoteLeft.stop().animate({transform: 'r10 0,32'}, 150, mina.easeout, function() {
311 | quoteLeft.stop().animate({transform: 'r0, 0,32'}, 150, mina.bounce);
312 | });
313 | //Not sure why I have to "stagger" the delays...logged ticket:
314 | //https://github.com/adobe-webplatform/Snap.svg/issues/340
315 | quoteRight.stop().animate({transform: 'r10 32,32'}, 175, mina.easeout, function() {
316 | quoteRight.animate({transform: 'r0, 32,32'}, 175, mina.bounce);
317 | });
318 | });
319 | });
320 | };
321 |
322 |
323 | /*************************************************************************\
324 | ** FACE ANIMATION ********************************************************\
325 | **************************************************************************/
326 | var initFaceAnimation = function() {
327 | loadSvg('.icon-face', 64, 'svg/face.svg', function (fragment, svg, $button) {
328 | var group = fragment.select('g');
329 | svg.append(group);
330 |
331 | var smile = group.select('#smile');
332 | var frown = group.select('#frown');
333 | frown.attr({opacity: 0});
334 |
335 | $button.on(clickEvent, function(e) {
336 | var isFrowning = $('.icon-face').data('is-frowning');
337 | $('.icon-face').data('is-frowning', !isFrowning);
338 |
339 | if (isFrowning) {
340 | smile.stop().animate({path: 'M32.2,49c-5.1,0-10.2-1.9-14.1-5.6c-1-1-1-2.5-0.1-3.5c1-1,2.5-1,3.5-0.1c5.9,5.7,15.5,5.7,21.4,0 c1-1,2.6-0.9,3.5,0.1c1,1,0.9,2.6-0.1,3.5C42.5,47.1,37.3,49,32.2,49z'}, 600, mina.bounce);
341 | } else {
342 | smile.stop().animate({path: 'M32.2,39c-5.2,0-10.3,1.9-14.3,5.7c-1,1-1,2.5-0.1,3.5c1,1,2.5,1,3.5,0.1c5.9-5.7,15.6-5.7,21.6,0 c1,1,2.6,0.9,3.5-0.1c1-1,0.9-2.6-0.1-3.5C42.5,40.9,37.4,39,32.2,39z'}, 200, mina.easeout);
343 | }
344 | });
345 | });
346 | };
347 |
348 |
349 |
350 | /*************************************************************************\
351 | ** BAR CHART ANIMATION ***************************************************\
352 | **************************************************************************/
353 | var initBarChartAnimation = function() {
354 | loadSvg('.icon-bar-chart', 64, 'svg/bar-chart.svg', function (fragment, svg, $button) {
355 | var group = fragment.select('g');
356 | svg.append(group);
357 |
358 | $button.on(clickEvent, function(e) {
359 | console.log("TODO .. animate");
360 | });
361 | });
362 | };
363 |
364 | /*************************************************************************\
365 | ** PLUG ANIMATION ********************************************************\
366 | **************************************************************************/
367 | var initPlugAnimation = function() {
368 | loadSvg('.icon-plug', 64, 'svg/plug.svg', function (fragment, svg, $button) {
369 | var group = fragment.select('g');
370 | svg.append(group);
371 |
372 | $button.on(clickEvent, function(e) {
373 | console.log("TODO .. animate");
374 | });
375 | });
376 | };
377 |
378 | /*************************************************************************\
379 | ** BELL ANIMATION ********************************************************\
380 | **************************************************************************/
381 | var initBellAnimation = function() {
382 | loadSvg('.icon-bell', 64, 'svg/bell.svg', function (fragment, svg, $button) {
383 | var group = fragment.select('g');
384 | svg.append(group);
385 | var clapper = group.select('#clapper');
386 | var bell = group.select('#bell');
387 |
388 | $button.on(clickEvent, function(e) {
389 | clapper.stop().animate({transform: 't-6,-2 s.9,.9'}, 100, mina.backin, function() {
390 | clapper.stop().animate({transform: 't0,0'}, 800, mina.bounce);
391 | });
392 | });
393 | });
394 | };
395 |
396 | /*************************************************************************\
397 | ** FROG ANIMATION ********************************************************\
398 | **************************************************************************/
399 | var initFrogAnimation = function() {
400 | loadSvg('.icon-frog', 64, 'svg/frog.svg', function (fragment, svg, $button) {
401 | var group = fragment.select('g');
402 | svg.append(group);
403 | var eyeLeft = group.select('#eye-left');
404 | var eyeRight = group.select('#eye-right');
405 |
406 | $button.on(clickEvent, function(e) {
407 | wobble(3, eyeLeft);
408 | setTimeout(function() {
409 | wobble(2, eyeRight);
410 | }, 250);
411 | });
412 | });
413 | };
414 |
415 |
416 |
417 | /*************************************************************************\
418 | ** IMAGE ANIMATION ********************************************************\
419 | **************************************************************************/
420 | var initImageAnimation = function() {
421 | loadSvg('.icon-image', 64, 'svg/image.svg', function (fragment, svg, $button) {
422 | var group = fragment.select('g');
423 | svg.append(group);
424 | var mountains = group.select('#mountains');
425 | var sun = group.select('#sun');
426 | $button.on(clickEvent, function(e) {
427 | sun.stop().animate({transform: 't100,0'}, 500, mina.easeout, function() {
428 | sun.attr({opacity: 0});
429 | sun.stop().animate({transform: 't-60,0'}, 50, mina.easeout, function() {
430 | sun.stop().animate({transform: 't0,0', opacity: 1}, 2000, mina.bounce);
431 | });
432 | });
433 | });
434 | });
435 | };
436 |
437 |
438 | /*************************************************************************\
439 | ** PENCIL ANIMATION ********************************************************\
440 | **************************************************************************/
441 | var initPencilAnimation = function() {
442 | loadSvg('.icon-pencil', 64, 'svg/pencil.svg', function (fragment, svg, $button) {
443 | var group = fragment.select('g');
444 | svg.append(group);
445 |
446 | $button.on(clickEvent, function(e) {
447 | console.log("TODO .. animate");
448 | });
449 | });
450 | };
451 |
452 |
453 | /*************************************************************************\
454 | ** REFRESH ANIMATION *****************************************************\
455 | **************************************************************************/
456 | var initRefreshAnimation = function() {
457 | loadSvg('.icon-refresh', 64, 'svg/refresh.svg', function (fragment, svg, $button) {
458 | var group = fragment.select('g');
459 | svg.append(group);
460 |
461 | $button.on(clickEvent, function(e) {
462 | group.attr({ transform: 's1,1 r0 32,32'});//reset scale and rotation
463 | group.stop().animate({transform: 's.6,.6 r180 32,32'}, 400, mina.easeout, function() {
464 | group.stop().animate({transform: 's1,1 r360 32,32'}, 600, mina.easeout);
465 | });
466 | });
467 | });
468 | };
469 |
470 |
471 | /*************************************************************************\
472 | ** ARROW LEFT & UP ANIMATIONS ********************************************\
473 | **************************************************************************/
474 | var initBorderedArrowLeft = function() {
475 | loadSvg('.icon-bordered-arrow-left', 64, 'svg/bordered-arrow-left.svg', function (fragment, svg, $button) {
476 | var group = fragment.select('g');
477 | svg.append(group);
478 |
479 | $button.on(clickEvent, function(e) {
480 | console.log("TODO .. animate");
481 | });
482 | });
483 | };
484 |
485 | var initBorderedArrowUp = function() {
486 | loadSvg('.icon-bordered-arrow-up', 64, 'svg/bordered-arrow-up.svg', function (fragment, svg, $button) {
487 | var group = fragment.select('g');
488 | svg.append(group);
489 |
490 | $button.on(clickEvent, function(e) {
491 | console.log("TODO .. animate");
492 | });
493 | });
494 | };
495 |
496 |
497 | /*************************************************************************\
498 | ** MAP MARKER ANIMATION ********************************************************\
499 | **************************************************************************/
500 | var initMapMarkerAnimation = function() {
501 | loadSvg('.icon-map-marker', 64, 'svg/map-marker.svg', function (fragment, svg, $button) {
502 | var group = fragment.select('g');
503 |
504 | svg.append(group);
505 |
506 | $button.on(clickEvent, function(e) {
507 | group.stop().animate({transform: 't-5,-5 s.4,.4 r-90, 32,32'}, 100, mina.easeout, function() {
508 | deferAnimation(group,{transform: 't0,0 s1,1 r0 32,32'}, 100, mina.backout, 500, function() {
509 | wobble(2, group);
510 | });
511 | });
512 | });
513 | });
514 | };
515 |
516 |
517 |
518 | /*************************************************************************\
519 | ** EQ ANIMATION **********************************************************\
520 | **************************************************************************/
521 | var initEQAnimation = function() {
522 | loadSvg('.icon-eq', 64, 'svg/equalizer.svg', function (fragment, svg, $button) {
523 | var group = fragment.select('g');
524 |
525 | svg.append(group);
526 |
527 | $button.on(clickEvent, function(e) {
528 | console.log("TODO .. animate");
529 | });
530 | });
531 | };
532 |
533 |
534 | /*************************************************************************\
535 | ** MAXIMIZE ANIMATION ****************************************************\
536 | **************************************************************************/
537 | var initMaximizeAnimation = function() {
538 | loadSvg('.icon-maximize', 64, 'svg/maximize.svg', function (fragment, svg, $button) {
539 | var group = fragment.select('g');
540 | var rightUp = group.select('#arrow-right-up');
541 | var leftUp = group.select('#arrow-left-up');
542 | var rightDown = group.select('#arrow-right-down');
543 | var leftDown = group.select('#arrow-left-down');
544 |
545 | svg.append(group);
546 |
547 |
548 | $button.on(clickEvent, function(e) {
549 | var duration = 200,
550 | isMaximized = $('.icon-maximize').data('is-maximized');
551 |
552 | $('.icon-maximize').data('is-maximized', !isMaximized);
553 |
554 | if (isMaximized) {
555 | leftUp.stop().animate({transform: 't0,0'}, duration, mina.easeout);
556 | leftDown.stop().animate({transform: 't0,0'}, duration, mina.easeout);
557 | rightUp.stop().animate({transform: 't0,0'}, duration, mina.easeout);
558 | rightDown.stop().animate({transform: 't0,0'}, duration, mina.easeout);
559 | } else {
560 | leftUp.stop().animate({transform: 't-4,-4'}, duration, mina.easeout);
561 | leftDown.stop().animate({transform: 't-4,4'}, duration, mina.easeout);
562 | rightUp.stop().animate({transform: 't2,-4'}, duration, mina.easeout);
563 | rightDown.stop().animate({transform: 't4,4'}, duration, mina.easeout);
564 | }
565 | });
566 | });
567 | };
568 |
569 | /*************************************************************************\
570 | ** PINNED ANIMATION ******************************************************\
571 | **************************************************************************/
572 | var initPinnedAnimation = function() {
573 | loadSvg('.icon-pinned', 64, 'svg/pinned.svg', function (fragment, svg, $button) {
574 | var group = fragment.select('g');
575 | svg.append(group);
576 |
577 | $button.on(clickEvent, function(e) {
578 | group.stop().animate({transform: 't0, -8 s.7,.8'}, 100, mina.easeout, function() {
579 | deferAnimation(group,{transform: 't0,0 s1,1'}, 100, mina.elastic, 500);
580 | });
581 | });
582 | });
583 | };
584 |
585 |
586 | /*************************************************************************\
587 | ** STAR ANIMATION ********************************************************\
588 | **************************************************************************/
589 | var initStarAnimation = function() {
590 | loadSvg('.icon-star-outline', 64, 'svg/star-outline.svg', function (fragment, svg, $button) {
591 | var group = fragment.select('g');
592 | var path = group.select('#outline');
593 |
594 | var filledPath = "M12.6,63.2L20,40.1L0.7,26h23.9L32,2.6L39.4,26h23.9L44,40.1l7.4,23.2L32,48.8L12.6,63.2z";
595 | var outlinedPath = "M12.6,62.2L20,38.6L0.7,24h23.9L32,0.6L39.4,24h23.9L44,38.6l7.4,23.5L32,47.7L12.6,62.2z M6.6,26l15.7,11.8l-6,19L32,45.2l15.6,11.6l-6-18.9L57.4,26H38L32,7.2L26,26H6.6z";
596 |
597 | svg.append(group);
598 |
599 | $button.on(clickEvent, function(e) {
600 | var isFilled = $('.icon-star-outline').data('is-filled');
601 | $('.icon-star-outline').data('is-filled', !isFilled);
602 |
603 | if (isFilled) {
604 | path.animate({path: outlinedPath}, 100, mina.bounce);
605 | } else {
606 | path.animate({path: filledPath}, 300, mina.backout);
607 | }
608 | });
609 | });
610 | };
611 |
612 | /*************************************************************************\
613 | ** SEARCH ANIMATION ******************************************************\
614 | **************************************************************************/
615 | var initSearchAnimation = function() {
616 | loadSvg('.icon-search-add', 64, 'svg/search-add.svg', function (fragment, svg, $button) {
617 | var group = fragment.select('g');
618 | var path = group.select('#plus');
619 |
620 | var minusPath = 'M30,22H11v-3h19V22z';
621 | var plusPath = 'M30,22h-8v8h-3v-8h-8v-3h8v-8h3v8h8V22z';
622 |
623 | svg.append(group);
624 |
625 | $button.on(clickEvent, function(e) {
626 |
627 | var isRemoving = $('.icon-search-add').data('is-removing');
628 | $('.icon-search-add').data('is-removing', !isRemoving);
629 |
630 | if (isRemoving) {
631 | path.animate({path: plusPath}, 300, mina.easeout);
632 | } else {
633 | path.animate({path: minusPath}, 300, mina.easeout);
634 | }
635 | });
636 | });
637 | };
638 |
639 |
640 | /*************************************************************************\
641 | ** LINK ANIMATION ******************************************************\
642 | **************************************************************************/
643 | var initLinkAnimation = function() {
644 | loadSvg('.icon-link-connected', 64, 'svg/link-connected.svg', function (fragment, svg, $button) {
645 | var group = fragment.select('g');
646 |
647 |
648 | //TODO ------------- MORPH TO LINK-BROKEN.SVG
649 |
650 | svg.append(group);
651 |
652 | $button.on(clickEvent, function(e) {
653 | console.log("TODO .. animate");
654 | });
655 | });
656 | };
657 |
658 |
659 | /*************************************************************************\
660 | ** PERSON MALE ANIMATION **************************************************\
661 | **************************************************************************/
662 | var initPersonMaleAnimation = function() {
663 | loadSvg('.icon-person-male', 64, 'svg/person-male.svg', function (fragment, svg, $button) {
664 | var group = fragment.select('g');
665 | svg.append(group);
666 | var head = group.select("#head");
667 |
668 | $button.on(clickEvent, function(e) {
669 | wobble(2, head);
670 | });
671 | });
672 | };
673 |
674 | /*************************************************************************\
675 | ** CELL ANIMATION ********************************************************\
676 | **************************************************************************/
677 | var initCellAnimation = function() {
678 | loadSvg('.icon-cell', 64, 'svg/cell.svg', function (fragment, svg, $button) {
679 | var group = fragment.select('g');
680 |
681 |
682 | //TODO ------------- MORPH TO DESKTOP.SVG
683 |
684 | svg.append(group);
685 |
686 | $button.on(clickEvent, function(e) {
687 | console.log("TODO .. animate");
688 | });
689 | });
690 | };
691 |
692 | /*************************************************************************\
693 | ** CAMERA ANIMATION ******************************************************\
694 | **************************************************************************/
695 | var initCameraAnimation = function() {
696 | loadSvg('.icon-camera', 64, 'svg/camera.svg', function (fragment, svg, $button) {
697 | var group = fragment.select('g');
698 | svg.append(group);
699 |
700 | $button.on(clickEvent, function(e) {
701 | console.log("TODO .. animate");
702 | });
703 | });
704 | };
705 |
706 | /*************************************************************************\
707 | ** BUG ANIMATION *********************************************************\
708 | **************************************************************************/
709 | var initBugAnimation = function() {
710 | loadSvg('.icon-bug', 64, 'svg/bug.svg', function (fragment, svg, $button) {
711 | var group = fragment.select('g');
712 | svg.append(group);
713 |
714 | $button.on(clickEvent, function(e) {
715 | console.log("TODO .. animate");
716 | });
717 | });
718 | };
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 | /*************************************************************************\
741 | ** ENVELOP ANIMATION *****************************************************\
742 | **************************************************************************/
743 | var initEnvelopeAnimation = function() {
744 | loadSvg('.icon-envelope', 64, 'svg/envelope.svg', function (fragment, svg, $button) {
745 | var envelope = fragment.select('g');
746 | var envelopeBody = envelope.select('#body');
747 | var flapOpened = envelope.select('#flap-opened');
748 | var flapClosed = envelope.select('#flap-closed');
749 | var topLine = envelope.select('#top-line');
750 | svg.append(envelopeBody);
751 | svg.append(flapClosed);
752 |
753 | //Also append flapOpened shape, but hide initially
754 | flapOpened.attr({opacity: 0});
755 | svg.append(flapOpened);
756 |
757 | //Inverse for top line of envelope (show initially)
758 | topLine.attr({opacity: 1});
759 | svg.append(topLine);
760 |
761 | $button.on(clickEvent, function(e) {
762 |
763 | //Determine last toggled state then toggle as appropriate
764 | var isOpened = $('.icon-envelope').data('is-open');
765 | $('.icon-envelope').data('is-open', !isOpened);
766 |
767 | if (isOpened) {
768 | flapOpened.stop().animate({opacity: 0}, 200, mina.easeout);
769 | deferAnimation(flapClosed, {opacity: 1}, 200, mina.easeout, 250);
770 | topLine.stop().animate({opacity: 1}, 400, mina.easeout);
771 | } else {
772 | flapClosed.stop().animate({ opacity: .3}, 200, mina.easeout);
773 | deferAnimation(flapOpened, {opacity: 1}, 200, mina.easeout, 250);
774 | topLine.stop().animate({opacity: 0}, 300, mina.easeout);
775 | }
776 | });
777 | });
778 | };
779 |
780 |
781 |
782 |
783 | /*************************************************************************\
784 | ** PLUS/MINUS ANIMATION ***************************************************\
785 | **************************************************************************/
786 | var initPlusMinusAnimation = function() {
787 | loadSvg('.icon-plus', 64, 'svg/plus.svg', function (fragment, svg, $button) {
788 | var plus = fragment.select('g');
789 | svg.append(plus);
790 |
791 | //Store the vertical and horizontal lines of the '+'
792 | var vertical = plus.select('#vertical-line');
793 | var horizontal = plus.select('#horizontal-line');
794 |
795 | $button.on(clickEvent, function(e) {
796 | plus.attr({transform: 'r0 32,32'});//reset
797 |
798 | //Determine last toggled state then toggle as appropriate
799 | var isPlus = $('.icon-plus').data('is-plus');
800 | $('.icon-plus').data('is-plus', !isPlus);
801 |
802 | if (isPlus) {
803 | plus.stop().animate({transform: 'r-180 32,32'}, 500, mina.easeout);
804 | deferAnimation(vertical, {opacity: 1}, 150, mina.easeout, 400);
805 | } else {
806 | plus.stop().animate({transform: 'r360 32,32'}, 500, mina.easeout);
807 | deferAnimation(vertical, {opacity: 0}, 150, mina.easeout, 400);
808 | }
809 | });
810 | });
811 | };
812 |
813 |
814 | /*************************************************************************\
815 | ** SPEAKER ON/OFF ANIMATION **********************************************\
816 | **************************************************************************/
817 | var initSpeakerAnimation = function() {
818 | loadSvg('.icon-speaker', 64, 'svg/speaker.svg', function (fragment, svg, $button) {
819 | var group = fragment.select('g');
820 | var speaker = group.select('path:nth-child(1)');
821 | var volume = group.select('#volume');
822 | svg.append(speaker);
823 | svg.append(volume);
824 | var crossedOut = group.select('#crossed-out');
825 | crossedOut.attr({opacity: 0});
826 | svg.append(crossedOut);
827 |
828 | $button.on(clickEvent, function(e) {
829 |
830 | //Determine last toggled state then toggle as appropriate
831 | var isPlaying = $('.icon-speaker').data('is-playing');
832 | $('.icon-speaker').data('is-playing', !isPlaying);
833 |
834 | if (isPlaying) {
835 | crossedOut.stop().animate({opacity: 0}, 200, mina.easeout, function() {
836 | volume.attr({transform: 's.8,.8 t0,0'});
837 | volume.stop().animate({transform: 's1,1', opacity: 1}, 200, mina.easeout);
838 | });
839 | } else {
840 | volume.stop().animate({transform: 's1.3,1.3 t20,0', opacity: 0}, 350, mina.easeout, function() {
841 | crossedOut.stop().animate({opacity: 1}, 100, mina.easeout);
842 | });
843 | }
844 | });
845 | });
846 | };
847 |
848 |
849 | /*************************************************************************\
850 | ** PLAY/STOP ANIMATION ***************************************************\
851 | **************************************************************************/
852 | var initPlayStopAnimation = function() {
853 | loadSvg('.icon-play', 64, 'svg/play.svg', function (fragment, svg, $button) {
854 | var group = fragment.select('g');
855 | svg.append(group);
856 | var path = group.select('path');
857 |
858 | $button.on(clickEvent, function(e) {
859 |
860 | //Determine last toggled state then toggle as appropriate
861 | var isPlaying = $('.icon-play').data('is-playing');
862 | $('.icon-play').data('is-playing', !isPlaying);
863 |
864 | if (isPlaying) {
865 | path.stop().animate({path: 'M30.9,16.9L58,32L11.5,57.5L5,61V3L30.9,16.9z', opacity: 1}, 300, mina.easeout);
866 | } else {
867 | path.stop().animate({path: 'M30,7h28v48H5V7H30z', opacity: 1}, 300, mina.easeout);
868 | }
869 | });
870 | });
871 | };
872 |
873 | /*************************************************************************\
874 | ** GEAR ANIMATION ********************************************************\
875 | **************************************************************************/
876 | var initGearAnimation = function() {
877 | loadSvg('.icon-gear', 64, 'svg/gear.svg', function (fragment, svg, $button) {
878 | var group = fragment.select('g');
879 | svg.append(group);
880 | $button.on(clickEvent, function(e) {
881 | group.stop().animate({transform: 's.1,.1', opacity: .1}, 500, mina.backout, function() {
882 | group.stop().animate({transform: 's1,1 r-720 32,32', opacity: 1}, 1200, mina.easeout);
883 | });
884 | });
885 | });
886 | };
887 |
888 |
889 | /*************************************************************************\
890 | ** HAMBURGER ANIMATION ********************************************************\
891 | **************************************************************************/
892 | var initHamburgerAnimation = function() {
893 | loadSvg('.icon-hamburger', 64, 'svg/hamburger.svg', function (fragment, svg, $button) {
894 | var group = fragment.select('g');
895 |
896 | var rectTop = group.select('#rect-top');
897 | var rectMiddle = group.select('#rect-middle');
898 | var rectBottom = group.select('#rect-bottom');
899 | svg.append(rectTop);
900 | svg.append(rectMiddle);
901 | svg.append(rectBottom);
902 |
903 | $button.on(clickEvent, function(e) {
904 | var isOpened = $('.icon-hamburger').data('is-open');
905 | $('.icon-hamburger').data('is-open', !isOpened);
906 |
907 | if (isOpened) {
908 | rectTop.stop().animate({path: 'M58.7,22.7H5.3v-5.3h53.3V22.7z'}, 200, mina.bounce);
909 | rectMiddle.stop().animate({opacity: 1}, 200, mina.linear);
910 | rectBottom.stop().animate({path: 'M58.7,46.7H5.3v-5.3h53.3V46.7z'}, 200, mina.bounce);
911 | } else {
912 | rectTop.stop().animate({path: 'M58.7,8.8L8.5,59l-3.8-3.8L55,5L58.7,8.8z'}, 200, mina.easeout);
913 | rectMiddle.stop().animate({opacity: 0}, 100, mina.linear);
914 | rectBottom.stop().animate({path: 'M8.5,5l50.2,50.2L55,59L4.8,8.8L8.5,5z'}, 200, mina.easeout);
915 | }
916 | });
917 | });
918 | };
919 |
920 |
921 | /*************************************************************************\
922 | ** EYE ANIMATION ********************************************************\
923 | **************************************************************************/
924 | var initEyeAnimation = function() {
925 | loadSvg('.icon-eye', 64, 'svg/eye.svg', function (fragment, svg, $button) {
926 | var group = fragment.select('g');
927 | svg.append(group);
928 |
929 | var outerPupil = group.select('#outer-pupil');
930 | var innerPupil = group.select('#inner-pupil');
931 |
932 | $button.on(clickEvent, function(e) {
933 | //Look right
934 | outerPupil.stop().animate({transform: 't-7,0 s.95,.95'}, 150, mina.easeout);
935 | innerPupil.stop().animate({transform: 't-9,0 s.9,.9'}, 150, mina.easeout);
936 | //Look left
937 | deferAnimation(outerPupil,{transform: 't7,1 .95,.95'}, 150, mina.easeout, 400);
938 | deferAnimation(innerPupil,{transform: 't9,0 s.9,.9'}, 150, mina.easeout, 400);
939 | //Look back to center
940 | deferAnimation(outerPupil,{transform: 't0,0'}, 150, mina.easeout, 750);
941 | deferAnimation(innerPupil,{transform: 't0,0'}, 150, mina.easeout, 750);
942 | });
943 | });
944 | };
945 |
946 | /*************************************************************************\
947 | ** LIGHT BULB ANIMATION *************************************************\
948 | **************************************************************************/
949 | var initLightBulbAnimation = function() {
950 | loadSvg('.icon-light-bulb', 64, 'svg/light-bulb.svg', function (fragment, svg, $button) {
951 | var wholeBulb = fragment.select('#bulb');
952 | svg.append(wholeBulb);
953 |
954 | var beams = fragment.select('#beams');
955 | beams.attr('opacity', 0);
956 | svg.append(beams);
957 |
958 | //On click event fires off the animation sequence
959 | $button.on(clickEvent, function(e) {
960 | //Determine last toggled state then toggle as appropriate
961 | var isLightOn = $('.icon-light-bulb').data('on');
962 | $('.icon-light-bulb').data('on', !isLightOn);
963 |
964 | if (isLightOn) {
965 | animateLightOff(svg, wholeBulb, beams);
966 | } else {
967 | animateLightOn(svg, wholeBulb, beams);
968 | }
969 | });
970 | });
971 | }
972 |
973 | var animateLightOn = function(svg, bulb, beams) {
974 | var beamRects = beams.selectAll('rect');
975 | var bulbPaths = bulb.selectAll('path');
976 | bulb.animate({transform: 's.8,.8 t0,8'}, 300, mina.easeout, function() {
977 | bulbPaths.forEach(function(el, i) {
978 | el.attr({fill: '#f5d76e', opacity: 1});
979 | });
980 | });
981 |
982 | beams.animate({transform: 's1.2,1.2 t0,3', opacity: 1}, 200, mina.bounce, function() {
983 | beamRects.forEach(function(el, i) {
984 | el.stop().animate({fill: '#f5d76e', opacity: 1}, 200, mina.easeout);
985 | });
986 | });
987 | };
988 |
989 | var animateLightOff = function(svg, bulb, beams) {
990 | var beamRects = beams.selectAll('rect');
991 | var bulbPaths = bulb.selectAll('path');
992 | beamRects.forEach(function(el, i) {
993 | el.stop().animate({fill: 'white', opacity: 0}, 300, mina.easeout);
994 | });
995 | bulb.stop().animate({transform: 's1,1 t0,0', opacity:1}, 300, mina.easeout, function() {
996 | bulbPaths.forEach(function(el, i) {
997 | el.stop().animate({fill: 'white'}, 200, mina.easeout);
998 | });
999 | });
1000 | };
1001 |
1002 |
1003 | /*************************************************************************\
1004 | ** LOCK ANIMATION ********************************************************\
1005 | **************************************************************************/
1006 | var initLockAnimation = function() {
1007 | loadSvg('.icon-lock', 64, 'svg/ic_lock_open_64px.svg', function (fragment, svg, $button) {
1008 | var g = fragment.select('g');
1009 | svg.append(g);
1010 |
1011 | //On click event fires off the animation sequence
1012 | $button.on(clickEvent, function(e) {
1013 | animateLockHandle(svg);
1014 | });
1015 | });
1016 | }
1017 |
1018 | var animateLockHandle = function(svg) {
1019 |
1020 | //Determine last locked state and then toggle
1021 | var isLocked = $('.icon-lock').data('locked');
1022 | $('.icon-lock').data('locked', !isLocked);
1023 |
1024 | //Animate the lock handle
1025 | var lockHandle = svg.select('#lock-handle');
1026 | var locked = {path: 'M46.9,21.5v-6c0-8.3-6.6-14.9-14.9-14.9S17.1,7.2,17.1,15.5l-0.1,6h5.8v-6c0-5.1,4.2-9.4,9.4-9.4 s9.4,4.2,9.4,9.4v6'};
1027 | var unLocked = {path: 'M47.5,21.5v-6c0-8.3-6.6-14.9-14.9-14.9S17.8,7.3,17.8,15.5h0.1h5.5h0.1c0-5.1,4.2-9.3,9.3-9.3 s9.3,4.2,9.3,9.3v6'};
1028 | var attrs = isLocked ? unLocked : locked;
1029 | lockHandle.animate(attrs, 250, mina.easeout);
1030 | };
1031 |
1032 |
1033 | /*************************************************************************\
1034 | ** TRASH CAN ANIMATION ***************************************************\
1035 | **************************************************************************/
1036 |
1037 | var initTrashCanAnimation = function() {
1038 | loadSvg('.icon-delete-v2', 96, 'svg/ic_delete_48px.svg', function (fragment, svg, $button) {
1039 | var g = fragment.select('g');
1040 | svg.append(g);//inject group into our SVG
1041 |
1042 | //On click event fires off the animation sequence
1043 | $button.on(clickEvent, function(e) {
1044 | animateOpeningLid(svg);
1045 | animateIncomingGarbage(svg);
1046 | animateClosingLid(svg, g);
1047 | });
1048 | });
1049 | }
1050 |
1051 | /////////////////////////////////////////////////////////////////
1052 | // 1. Open Trash Can's Lid //
1053 | /////////////////////////////////////////////////////////////////
1054 | var animateOpeningLid = function(svg) {
1055 | var trashCanLid = svg.select('.lid');
1056 | //Path that represents opened trash can lid
1057 | var attrs={"path":"M69,4.7l-13.7,5l-5.4-2.5l-19.6,7.1l-2.5,5.4l-13.7,5l2.9,7.9l55-20L69,4.7z"};
1058 |
1059 | deferAnimation(trashCanLid, attrs, 150, mina.easeout, 0);
1060 | };
1061 |
1062 | /////////////////////////////////////////////////////////////////
1063 | // 2. Simulates some incoming garbage //
1064 | /////////////////////////////////////////////////////////////////
1065 | var animateIncomingGarbage = function(svg) {
1066 | console.log("WIll animate garbage...");
1067 |
1068 | //We're taking the liberty of using a circle for our trash
1069 | var circle = svg.circle(110, 0, 5);
1070 |
1071 | //t-N is moving x coordinate left
1072 | //second number is y coordinate and increasing number moves downward
1073 | for (var i = 6; i < 10; i++) {
1074 | var transformValue = 't-' + i*7 + ' ' + (34+i);
1075 | deferAnimation(circle, {'transform': transformValue}, 400, mina.easeout, i*20);
1076 | }
1077 | };
1078 |
1079 | /////////////////////////////////////////////////////////////////
1080 | // 3. Close Trash Can's Lid //
1081 | /////////////////////////////////////////////////////////////////
1082 | var animateClosingLid = function(svg, entireTrashCanGroup) {
1083 | var trashCanLid = svg.select('.lid');
1084 |
1085 | //Path represents, essentially, the original path (lid closed)
1086 | var attrs = {"path": "M75.5,24.2H60.9L56.7,20H35.8l-4.2,4.2H17v8.4h58.5V24.2z"};
1087 |
1088 | deferAnimation(trashCanLid, attrs, 150, mina.easeout, 500, function() {
1089 | animateWobblingTrashCan(entireTrashCanGroup);
1090 | });
1091 | };
1092 |
1093 | /////////////////////////////////////////////////////////////////
1094 | // 4. Close Trash Can's Lid //
1095 | /////////////////////////////////////////////////////////////////
1096 | var animateWobblingTrashCan = function(svg) {
1097 | svg.animate({'transform': 'r-5 48 48'}, 100, mina.easeout, function() {
1098 | svg.animate({'transform': 'r5 48 48'}, 50, mina.easeout, function() {
1099 | svg.animate({'transform': 'r-7 48 48'}, 50, mina.easeout, function() {
1100 | svg.animate({'transform': 'r0 48 48'}, 50, mina.easeout);
1101 | });
1102 | });
1103 | });
1104 | };
1105 |
1106 |
1107 |
1108 |
1109 | /////////////////////////////////////////////////////////////////
1110 | // ENTRY POINT //
1111 | /////////////////////////////////////////////////////////////////
1112 | initTrashCanAnimation();
1113 | initCloudsAnimation();
1114 | initLockAnimation();
1115 | initRocketShipAnimation();
1116 | initLightBulbAnimation();
1117 | initGearAnimation();
1118 | initHamburgerAnimation();
1119 | initEyeAnimation();
1120 | initShoppingCartAnimation();
1121 | initMicrophoneAnimation();
1122 | initSharingAnimation();
1123 | initPlayStopAnimation();
1124 | initPlusMinusAnimation();
1125 | initSpeakerAnimation();
1126 | initEnvelopeAnimation();
1127 | initAlarmClockAnimation();
1128 | initUSBAnimation();
1129 | initQuotesRightAnimation();
1130 | initBarChartAnimation();
1131 | initFaceAnimation();
1132 | initPlugAnimation();
1133 | initBellAnimation();
1134 | initCameraAnimation();
1135 | initCellAnimation();
1136 | initPersonMaleAnimation();
1137 | initStarAnimation ();
1138 | initSearchAnimation();
1139 | initLinkAnimation();
1140 | initBugAnimation();
1141 | initFrogAnimation();
1142 | initImageAnimation();
1143 | initPencilAnimation();
1144 | initRefreshAnimation();
1145 | initBorderedArrowLeft();
1146 | initBorderedArrowUp();
1147 | initMapMarkerAnimation();
1148 | initEQAnimation();
1149 | initMaximizeAnimation();
1150 | initPinnedAnimation();
1151 |
1152 | //Vivus
1153 | window.vivusPlayground = new Vivus('svg-playground',
1154 | {
1155 | type: 'scenario-sync',
1156 | duration: 15,
1157 | start: 'autostart',
1158 | forceRender: false,
1159 | dashGap: 20
1160 | }, function () {
1161 | });
1162 |
1163 | });
1164 |
--------------------------------------------------------------------------------
/js/modernizr.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Modernizr v2.8.2
3 | * www.modernizr.com
4 | *
5 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton
6 | * Available under the BSD and MIT licenses: www.modernizr.com/license/
7 | */
8 |
9 | /*
10 | * Modernizr tests which native CSS3 and HTML5 features are available in
11 | * the current UA and makes the results available to you in two ways:
12 | * as properties on a global Modernizr object, and as classes on the
13 | * element. This information allows you to progressively enhance
14 | * your pages with a granular level of control over the experience.
15 | *
16 | * Modernizr has an optional (not included) conditional resource loader
17 | * called Modernizr.load(), based on Yepnope.js (yepnopejs.com).
18 | * To get a build that includes Modernizr.load(), as well as choosing
19 | * which tests to include, go to www.modernizr.com/download/
20 | *
21 | * Authors Faruk Ates, Paul Irish, Alex Sexton
22 | * Contributors Ryan Seddon, Ben Alman
23 | */
24 |
25 | window.Modernizr = (function( window, document, undefined ) {
26 |
27 | var version = '2.8.2',
28 |
29 | Modernizr = {},
30 |
31 | /*>>cssclasses*/
32 | // option for enabling the HTML classes to be added
33 | enableClasses = true,
34 | /*>>cssclasses*/
35 |
36 | docElement = document.documentElement,
37 |
38 | /**
39 | * Create our "modernizr" element that we do most feature tests on.
40 | */
41 | mod = 'modernizr',
42 | modElem = document.createElement(mod),
43 | mStyle = modElem.style,
44 |
45 | /**
46 | * Create the input element for various Web Forms feature tests.
47 | */
48 | inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ ,
49 |
50 | /*>>smile*/
51 | smile = ':)',
52 | /*>>smile*/
53 |
54 | toString = {}.toString,
55 |
56 | // TODO :: make the prefixes more granular
57 | /*>>prefixes*/
58 | // List of property values to set for css tests. See ticket #21
59 | prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
60 | /*>>prefixes*/
61 |
62 | /*>>domprefixes*/
63 | // Following spec is to expose vendor-specific style properties as:
64 | // elem.style.WebkitBorderRadius
65 | // and the following would be incorrect:
66 | // elem.style.webkitBorderRadius
67 |
68 | // Webkit ghosts their properties in lowercase but Opera & Moz do not.
69 | // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
70 | // erik.eae.net/archives/2008/03/10/21.48.10/
71 |
72 | // More here: github.com/Modernizr/Modernizr/issues/issue/21
73 | omPrefixes = 'Webkit Moz O ms',
74 |
75 | cssomPrefixes = omPrefixes.split(' '),
76 |
77 | domPrefixes = omPrefixes.toLowerCase().split(' '),
78 | /*>>domprefixes*/
79 |
80 | /*>>ns*/
81 | ns = {'svg': 'http://www.w3.org/2000/svg'},
82 | /*>>ns*/
83 |
84 | tests = {},
85 | inputs = {},
86 | attrs = {},
87 |
88 | classes = [],
89 |
90 | slice = classes.slice,
91 |
92 | featureName, // used in testing loop
93 |
94 |
95 | /*>>teststyles*/
96 | // Inject element with style element and some CSS rules
97 | injectElementWithStyles = function( rule, callback, nodes, testnames ) {
98 |
99 | var style, ret, node, docOverflow,
100 | div = document.createElement('div'),
101 | // After page load injecting a fake body doesn't work so check if body exists
102 | body = document.body,
103 | // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it.
104 | fakeBody = body || document.createElement('body');
105 |
106 | if ( parseInt(nodes, 10) ) {
107 | // In order not to give false positives we create a node for each test
108 | // This also allows the method to scale for unspecified uses
109 | while ( nodes-- ) {
110 | node = document.createElement('div');
111 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
112 | div.appendChild(node);
113 | }
114 | }
115 |
116 | // '].join('');
122 | div.id = mod;
123 | // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
124 | // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
125 | (body ? div : fakeBody).innerHTML += style;
126 | fakeBody.appendChild(div);
127 | if ( !body ) {
128 | //avoid crashing IE8, if background image is used
129 | fakeBody.style.background = '';
130 | //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
131 | fakeBody.style.overflow = 'hidden';
132 | docOverflow = docElement.style.overflow;
133 | docElement.style.overflow = 'hidden';
134 | docElement.appendChild(fakeBody);
135 | }
136 |
137 | ret = callback(div, rule);
138 | // If this is done after page load we don't want to remove the body so check if body exists
139 | if ( !body ) {
140 | fakeBody.parentNode.removeChild(fakeBody);
141 | docElement.style.overflow = docOverflow;
142 | } else {
143 | div.parentNode.removeChild(div);
144 | }
145 |
146 | return !!ret;
147 |
148 | },
149 | /*>>teststyles*/
150 |
151 | /*>>mq*/
152 | // adapted from matchMedia polyfill
153 | // by Scott Jehl and Paul Irish
154 | // gist.github.com/786768
155 | testMediaQuery = function( mq ) {
156 |
157 | var matchMedia = window.matchMedia || window.msMatchMedia;
158 | if ( matchMedia ) {
159 | return matchMedia(mq) && matchMedia(mq).matches || false;
160 | }
161 |
162 | var bool;
163 |
164 | injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
165 | bool = (window.getComputedStyle ?
166 | getComputedStyle(node, null) :
167 | node.currentStyle)['position'] == 'absolute';
168 | });
169 |
170 | return bool;
171 |
172 | },
173 | /*>>mq*/
174 |
175 |
176 | /*>>hasevent*/
177 | //
178 | // isEventSupported determines if a given element supports the given event
179 | // kangax.github.com/iseventsupported/
180 | //
181 | // The following results are known incorrects:
182 | // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
183 | // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
184 | // ...
185 | isEventSupported = (function() {
186 |
187 | var TAGNAMES = {
188 | 'select': 'input', 'change': 'input',
189 | 'submit': 'form', 'reset': 'form',
190 | 'error': 'img', 'load': 'img', 'abort': 'img'
191 | };
192 |
193 | function isEventSupported( eventName, element ) {
194 |
195 | element = element || document.createElement(TAGNAMES[eventName] || 'div');
196 | eventName = 'on' + eventName;
197 |
198 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those
199 | var isSupported = eventName in element;
200 |
201 | if ( !isSupported ) {
202 | // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
203 | if ( !element.setAttribute ) {
204 | element = document.createElement('div');
205 | }
206 | if ( element.setAttribute && element.removeAttribute ) {
207 | element.setAttribute(eventName, '');
208 | isSupported = is(element[eventName], 'function');
209 |
210 | // If property was created, "remove it" (by setting value to `undefined`)
211 | if ( !is(element[eventName], 'undefined') ) {
212 | element[eventName] = undefined;
213 | }
214 | element.removeAttribute(eventName);
215 | }
216 | }
217 |
218 | element = null;
219 | return isSupported;
220 | }
221 | return isEventSupported;
222 | })(),
223 | /*>>hasevent*/
224 |
225 | // TODO :: Add flag for hasownprop ? didn't last time
226 |
227 | // hasOwnProperty shim by kangax needed for Safari 2.0 support
228 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
229 |
230 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
231 | hasOwnProp = function (object, property) {
232 | return _hasOwnProperty.call(object, property);
233 | };
234 | }
235 | else {
236 | hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */
237 | return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
238 | };
239 | }
240 |
241 | // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js
242 | // es5.github.com/#x15.3.4.5
243 |
244 | if (!Function.prototype.bind) {
245 | Function.prototype.bind = function bind(that) {
246 |
247 | var target = this;
248 |
249 | if (typeof target != "function") {
250 | throw new TypeError();
251 | }
252 |
253 | var args = slice.call(arguments, 1),
254 | bound = function () {
255 |
256 | if (this instanceof bound) {
257 |
258 | var F = function(){};
259 | F.prototype = target.prototype;
260 | var self = new F();
261 |
262 | var result = target.apply(
263 | self,
264 | args.concat(slice.call(arguments))
265 | );
266 | if (Object(result) === result) {
267 | return result;
268 | }
269 | return self;
270 |
271 | } else {
272 |
273 | return target.apply(
274 | that,
275 | args.concat(slice.call(arguments))
276 | );
277 |
278 | }
279 |
280 | };
281 |
282 | return bound;
283 | };
284 | }
285 |
286 | /**
287 | * setCss applies given styles to the Modernizr DOM node.
288 | */
289 | function setCss( str ) {
290 | mStyle.cssText = str;
291 | }
292 |
293 | /**
294 | * setCssAll extrapolates all vendor-specific css strings.
295 | */
296 | function setCssAll( str1, str2 ) {
297 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
298 | }
299 |
300 | /**
301 | * is returns a boolean for if typeof obj is exactly type.
302 | */
303 | function is( obj, type ) {
304 | return typeof obj === type;
305 | }
306 |
307 | /**
308 | * contains returns a boolean for if substr is found within str.
309 | */
310 | function contains( str, substr ) {
311 | return !!~('' + str).indexOf(substr);
312 | }
313 |
314 | /*>>testprop*/
315 |
316 | // testProps is a generic CSS / DOM property test.
317 |
318 | // In testing support for a given CSS property, it's legit to test:
319 | // `elem.style[styleName] !== undefined`
320 | // If the property is supported it will return an empty string,
321 | // if unsupported it will return undefined.
322 |
323 | // We'll take advantage of this quick test and skip setting a style
324 | // on our modernizr element, but instead just testing undefined vs
325 | // empty string.
326 |
327 | // Because the testing of the CSS property names (with "-", as
328 | // opposed to the camelCase DOM properties) is non-portable and
329 | // non-standard but works in WebKit and IE (but not Gecko or Opera),
330 | // we explicitly reject properties with dashes so that authors
331 | // developing in WebKit or IE first don't end up with
332 | // browser-specific content by accident.
333 |
334 | function testProps( props, prefixed ) {
335 | for ( var i in props ) {
336 | var prop = props[i];
337 | if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
338 | return prefixed == 'pfx' ? prop : true;
339 | }
340 | }
341 | return false;
342 | }
343 | /*>>testprop*/
344 |
345 | // TODO :: add testDOMProps
346 | /**
347 | * testDOMProps is a generic DOM property test; if a browser supports
348 | * a certain property, it won't return undefined for it.
349 | */
350 | function testDOMProps( props, obj, elem ) {
351 | for ( var i in props ) {
352 | var item = obj[props[i]];
353 | if ( item !== undefined) {
354 |
355 | // return the property name as a string
356 | if (elem === false) return props[i];
357 |
358 | // let's bind a function
359 | if (is(item, 'function')){
360 | // default to autobind unless override
361 | return item.bind(elem || obj);
362 | }
363 |
364 | // return the unbound function or obj or value
365 | return item;
366 | }
367 | }
368 | return false;
369 | }
370 |
371 | /*>>testallprops*/
372 | /**
373 | * testPropsAll tests a list of DOM properties we want to check against.
374 | * We specify literally ALL possible (known and/or likely) properties on
375 | * the element including the non-vendor prefixed one, for forward-
376 | * compatibility.
377 | */
378 | function testPropsAll( prop, prefixed, elem ) {
379 |
380 | var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
381 | props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
382 |
383 | // did they call .prefixed('boxSizing') or are we just testing a prop?
384 | if(is(prefixed, "string") || is(prefixed, "undefined")) {
385 | return testProps(props, prefixed);
386 |
387 | // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
388 | } else {
389 | props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
390 | return testDOMProps(props, prefixed, elem);
391 | }
392 | }
393 | /*>>testallprops*/
394 |
395 |
396 | /**
397 | * Tests
398 | * -----
399 | */
400 |
401 | // The *new* flexbox
402 | // dev.w3.org/csswg/css3-flexbox
403 |
404 | tests['flexbox'] = function() {
405 | return testPropsAll('flexWrap');
406 | };
407 |
408 | // The *old* flexbox
409 | // www.w3.org/TR/2009/WD-css3-flexbox-20090723/
410 |
411 | tests['flexboxlegacy'] = function() {
412 | return testPropsAll('boxDirection');
413 | };
414 |
415 | // On the S60 and BB Storm, getContext exists, but always returns undefined
416 | // so we actually have to call getContext() to verify
417 | // github.com/Modernizr/Modernizr/issues/issue/97/
418 |
419 | tests['canvas'] = function() {
420 | var elem = document.createElement('canvas');
421 | return !!(elem.getContext && elem.getContext('2d'));
422 | };
423 |
424 | tests['canvastext'] = function() {
425 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
426 | };
427 |
428 | // webk.it/70117 is tracking a legit WebGL feature detect proposal
429 |
430 | // We do a soft detect which may false positive in order to avoid
431 | // an expensive context creation: bugzil.la/732441
432 |
433 | tests['webgl'] = function() {
434 | return !!window.WebGLRenderingContext;
435 | };
436 |
437 | /*
438 | * The Modernizr.touch test only indicates if the browser supports
439 | * touch events, which does not necessarily reflect a touchscreen
440 | * device, as evidenced by tablets running Windows 7 or, alas,
441 | * the Palm Pre / WebOS (touch) phones.
442 | *
443 | * Additionally, Chrome (desktop) used to lie about its support on this,
444 | * but that has since been rectified: crbug.com/36415
445 | *
446 | * We also test for Firefox 4 Multitouch Support.
447 | *
448 | * For more info, see: modernizr.github.com/Modernizr/touch.html
449 | */
450 |
451 | tests['touch'] = function() {
452 | var bool;
453 |
454 | if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
455 | bool = true;
456 | } else {
457 | injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
458 | bool = node.offsetTop === 9;
459 | });
460 | }
461 |
462 | return bool;
463 | };
464 |
465 |
466 | // geolocation is often considered a trivial feature detect...
467 | // Turns out, it's quite tricky to get right:
468 | //
469 | // Using !!navigator.geolocation does two things we don't want. It:
470 | // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513
471 | // 2. Disables page caching in WebKit: webk.it/43956
472 | //
473 | // Meanwhile, in Firefox < 8, an about:config setting could expose
474 | // a false positive that would throw an exception: bugzil.la/688158
475 |
476 | tests['geolocation'] = function() {
477 | return 'geolocation' in navigator;
478 | };
479 |
480 |
481 | tests['postmessage'] = function() {
482 | return !!window.postMessage;
483 | };
484 |
485 |
486 | // Chrome incognito mode used to throw an exception when using openDatabase
487 | // It doesn't anymore.
488 | tests['websqldatabase'] = function() {
489 | return !!window.openDatabase;
490 | };
491 |
492 | // Vendors had inconsistent prefixing with the experimental Indexed DB:
493 | // - Webkit's implementation is accessible through webkitIndexedDB
494 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
495 | // For speed, we don't test the legacy (and beta-only) indexedDB
496 | tests['indexedDB'] = function() {
497 | return !!testPropsAll("indexedDB", window);
498 | };
499 |
500 | // documentMode logic from YUI to filter out IE8 Compat Mode
501 | // which false positives.
502 | tests['hashchange'] = function() {
503 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
504 | };
505 |
506 | // Per 1.6:
507 | // This used to be Modernizr.historymanagement but the longer
508 | // name has been deprecated in favor of a shorter and property-matching one.
509 | // The old API is still available in 1.6, but as of 2.0 will throw a warning,
510 | // and in the first release thereafter disappear entirely.
511 | tests['history'] = function() {
512 | return !!(window.history && history.pushState);
513 | };
514 |
515 | tests['draganddrop'] = function() {
516 | var div = document.createElement('div');
517 | return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
518 | };
519 |
520 | // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10
521 | // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17.
522 | // FF10 still uses prefixes, so check for it until then.
523 | // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/
524 | tests['websockets'] = function() {
525 | return 'WebSocket' in window || 'MozWebSocket' in window;
526 | };
527 |
528 |
529 | // css-tricks.com/rgba-browser-support/
530 | tests['rgba'] = function() {
531 | // Set an rgba() color and check the returned value
532 |
533 | setCss('background-color:rgba(150,255,150,.5)');
534 |
535 | return contains(mStyle.backgroundColor, 'rgba');
536 | };
537 |
538 | tests['hsla'] = function() {
539 | // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally,
540 | // except IE9 who retains it as hsla
541 |
542 | setCss('background-color:hsla(120,40%,100%,.5)');
543 |
544 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
545 | };
546 |
547 | tests['multiplebgs'] = function() {
548 | // Setting multiple images AND a color on the background shorthand property
549 | // and then querying the style.background property value for the number of
550 | // occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
551 |
552 | setCss('background:url(https://),url(https://),red url(https://)');
553 |
554 | // If the UA supports multiple backgrounds, there should be three occurrences
555 | // of the string "url(" in the return value for elemStyle.background
556 |
557 | return (/(url\s*\(.*?){3}/).test(mStyle.background);
558 | };
559 |
560 |
561 |
562 | // this will false positive in Opera Mini
563 | // github.com/Modernizr/Modernizr/issues/396
564 |
565 | tests['backgroundsize'] = function() {
566 | return testPropsAll('backgroundSize');
567 | };
568 |
569 | tests['borderimage'] = function() {
570 | return testPropsAll('borderImage');
571 | };
572 |
573 |
574 | // Super comprehensive table about all the unique implementations of
575 | // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance
576 |
577 | tests['borderradius'] = function() {
578 | return testPropsAll('borderRadius');
579 | };
580 |
581 | // WebOS unfortunately false positives on this test.
582 | tests['boxshadow'] = function() {
583 | return testPropsAll('boxShadow');
584 | };
585 |
586 | // FF3.0 will false positive on this test
587 | tests['textshadow'] = function() {
588 | return document.createElement('div').style.textShadow === '';
589 | };
590 |
591 |
592 | tests['opacity'] = function() {
593 | // Browsers that actually have CSS Opacity implemented have done so
594 | // according to spec, which means their return values are within the
595 | // range of [0.0,1.0] - including the leading zero.
596 |
597 | setCssAll('opacity:.55');
598 |
599 | // The non-literal . in this regex is intentional:
600 | // German Chrome returns this value as 0,55
601 | // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
602 | return (/^0.55$/).test(mStyle.opacity);
603 | };
604 |
605 |
606 | // Note, Android < 4 will pass this test, but can only animate
607 | // a single property at a time
608 | // goo.gl/v3V4Gp
609 | tests['cssanimations'] = function() {
610 | return testPropsAll('animationName');
611 | };
612 |
613 |
614 | tests['csscolumns'] = function() {
615 | return testPropsAll('columnCount');
616 | };
617 |
618 |
619 | tests['cssgradients'] = function() {
620 | /**
621 | * For CSS Gradients syntax, please see:
622 | * webkit.org/blog/175/introducing-css-gradients/
623 | * developer.mozilla.org/en/CSS/-moz-linear-gradient
624 | * developer.mozilla.org/en/CSS/-moz-radial-gradient
625 | * dev.w3.org/csswg/css3-images/#gradients-
626 | */
627 |
628 | var str1 = 'background-image:',
629 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
630 | str3 = 'linear-gradient(left top,#9f9, white);';
631 |
632 | setCss(
633 | // legacy webkit syntax (FIXME: remove when syntax not in use anymore)
634 | (str1 + '-webkit- '.split(' ').join(str2 + str1) +
635 | // standard syntax // trailing 'background-image:'
636 | prefixes.join(str3 + str1)).slice(0, -str1.length)
637 | );
638 |
639 | return contains(mStyle.backgroundImage, 'gradient');
640 | };
641 |
642 |
643 | tests['cssreflections'] = function() {
644 | return testPropsAll('boxReflect');
645 | };
646 |
647 |
648 | tests['csstransforms'] = function() {
649 | return !!testPropsAll('transform');
650 | };
651 |
652 |
653 | tests['csstransforms3d'] = function() {
654 |
655 | var ret = !!testPropsAll('perspective');
656 |
657 | // Webkit's 3D transforms are passed off to the browser's own graphics renderer.
658 | // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
659 | // some conditions. As a result, Webkit typically recognizes the syntax but
660 | // will sometimes throw a false positive, thus we must do a more thorough check:
661 | if ( ret && 'webkitPerspective' in docElement.style ) {
662 |
663 | // Webkit allows this media query to succeed only if the feature is enabled.
664 | // `@media (transform-3d),(-webkit-transform-3d){ ... }`
665 | injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) {
666 | ret = node.offsetLeft === 9 && node.offsetHeight === 3;
667 | });
668 | }
669 | return ret;
670 | };
671 |
672 |
673 | tests['csstransitions'] = function() {
674 | return testPropsAll('transition');
675 | };
676 |
677 |
678 | /*>>fontface*/
679 | // @font-face detection routine by Diego Perini
680 | // javascript.nwbox.com/CSSSupport/
681 |
682 | // false positives:
683 | // WebOS github.com/Modernizr/Modernizr/issues/342
684 | // WP7 github.com/Modernizr/Modernizr/issues/538
685 | tests['fontface'] = function() {
686 | var bool;
687 |
688 | injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) {
689 | var style = document.getElementById('smodernizr'),
690 | sheet = style.sheet || style.styleSheet,
691 | cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
692 |
693 | bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
694 | });
695 |
696 | return bool;
697 | };
698 | /*>>fontface*/
699 |
700 | // CSS generated content detection
701 | tests['generatedcontent'] = function() {
702 | var bool;
703 |
704 | injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) {
705 | bool = node.offsetHeight >= 3;
706 | });
707 |
708 | return bool;
709 | };
710 |
711 |
712 |
713 | // These tests evaluate support of the video/audio elements, as well as
714 | // testing what types of content they support.
715 | //
716 | // We're using the Boolean constructor here, so that we can extend the value
717 | // e.g. Modernizr.video // true
718 | // Modernizr.video.ogg // 'probably'
719 | //
720 | // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
721 | // thx to NielsLeenheer and zcorpan
722 |
723 | // Note: in some older browsers, "no" was a return value instead of empty string.
724 | // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
725 | // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
726 |
727 | tests['video'] = function() {
728 | var elem = document.createElement('video'),
729 | bool = false;
730 |
731 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
732 | try {
733 | if ( bool = !!elem.canPlayType ) {
734 | bool = new Boolean(bool);
735 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
736 |
737 | // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
738 | bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
739 |
740 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
741 | }
742 |
743 | } catch(e) { }
744 |
745 | return bool;
746 | };
747 |
748 | tests['audio'] = function() {
749 | var elem = document.createElement('audio'),
750 | bool = false;
751 |
752 | try {
753 | if ( bool = !!elem.canPlayType ) {
754 | bool = new Boolean(bool);
755 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
756 | bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
757 |
758 | // Mimetypes accepted:
759 | // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
760 | // bit.ly/iphoneoscodecs
761 | bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
762 | bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
763 | elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
764 | }
765 | } catch(e) { }
766 |
767 | return bool;
768 | };
769 |
770 |
771 | // In FF4, if disabled, window.localStorage should === null.
772 |
773 | // Normally, we could not test that directly and need to do a
774 | // `('localStorage' in window) && ` test first because otherwise Firefox will
775 | // throw bugzil.la/365772 if cookies are disabled
776 |
777 | // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem
778 | // will throw the exception:
779 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
780 | // Peculiarly, getItem and removeItem calls do not throw.
781 |
782 | // Because we are forced to try/catch this, we'll go aggressive.
783 |
784 | // Just FWIW: IE8 Compat mode supports these features completely:
785 | // www.quirksmode.org/dom/html5.html
786 | // But IE8 doesn't support either with local files
787 |
788 | tests['localstorage'] = function() {
789 | try {
790 | localStorage.setItem(mod, mod);
791 | localStorage.removeItem(mod);
792 | return true;
793 | } catch(e) {
794 | return false;
795 | }
796 | };
797 |
798 | tests['sessionstorage'] = function() {
799 | try {
800 | sessionStorage.setItem(mod, mod);
801 | sessionStorage.removeItem(mod);
802 | return true;
803 | } catch(e) {
804 | return false;
805 | }
806 | };
807 |
808 |
809 | tests['webworkers'] = function() {
810 | return !!window.Worker;
811 | };
812 |
813 |
814 | tests['applicationcache'] = function() {
815 | return !!window.applicationCache;
816 | };
817 |
818 |
819 | // Thanks to Erik Dahlstrom
820 | tests['svg'] = function() {
821 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
822 | };
823 |
824 | // specifically for SVG inline in HTML, not within XHTML
825 | // test page: paulirish.com/demo/inline-svg
826 | tests['inlinesvg'] = function() {
827 | var div = document.createElement('div');
828 | div.innerHTML = '