vegas.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /* global jQuery, Zepto */
  2. (function ($) {
  3. 'use strict';
  4. var defaults = {
  5. slide: 0,
  6. delay: 5000,
  7. preload: false,
  8. preloadImage: false,
  9. preloadVideo: false,
  10. timer: true,
  11. overlay: false,
  12. autoplay: true,
  13. shuffle: false,
  14. cover: true,
  15. color: null,
  16. align: 'center',
  17. valign: 'center',
  18. transition: 'fade',
  19. transitionDuration: 1000,
  20. animation: null,
  21. animationDuration: 'auto',
  22. init: function () {},
  23. play: function () {},
  24. pause: function () {},
  25. walk: function () {},
  26. slides: [
  27. // {
  28. // src: null,
  29. // color: null,
  30. // delay: null,
  31. // align: null,
  32. // valign: null,
  33. // transition: null,
  34. // transitionDuration: null,
  35. // animation: null,
  36. // animationDuration: null,
  37. // cover: true,
  38. // videos: []
  39. // }
  40. // ...
  41. ]
  42. };
  43. var videoCache = {};
  44. var Vegas = function (elmt, options) {
  45. this.elmt = elmt;
  46. this.settings = $.extend({}, defaults, $.vegas.defaults, options);
  47. this.slide = this.settings.slide;
  48. this.total = this.settings.slides.length;
  49. this.noshow = this.total < 2;
  50. this.paused = !this.settings.autoplay || this.noshow;
  51. this.$elmt = $(elmt);
  52. this.$timer = null;
  53. this.$overlay = null;
  54. this.$slide = null;
  55. this.timeout = null;
  56. this.transitions = [];
  57. this.animations = [];
  58. this.support = {
  59. objectFit: 'objectFit' in document.body.style,
  60. transition: 'transition' in document.body.style || 'WebkitTransition' in document.body.style,
  61. video: $.vegas.isVideoCompatible()
  62. };
  63. for (var i = 0; i < document.styleSheets.length; i++) {
  64. var sheet = document.styleSheets[i],
  65. rules;
  66. try {
  67. rules = (sheet.cssRules || sheet.rules);
  68. } catch(e) {
  69. continue;
  70. }
  71. if (/vegas(\..*?)?(\.min)?\.css$/.test(sheet.href)) {
  72. for (var j = 0; j < rules.length; j++) {
  73. var rule = rules[j],
  74. matchTransition = /vegas\-transition\-([a-z0-9]*)/gi.exec(rule.selectorText),
  75. matchAnimation = /vegas\-animation\-([a-z0-9]*)/gi.exec(rule.selectorText);
  76. if (matchTransition && matchTransition[1]) {
  77. if (this.transitions.indexOf(matchTransition[1]) === -1) {
  78. this.transitions.push(matchTransition[1]);
  79. }
  80. }
  81. if (matchAnimation && matchAnimation[1]) {
  82. if (this.animations.indexOf(matchAnimation[1]) === -1) {
  83. this.animations.push(matchAnimation[1]);
  84. }
  85. }
  86. }
  87. }
  88. }
  89. if (this.settings.shuffle === true) {
  90. this.shuffle();
  91. }
  92. this._init();
  93. };
  94. Vegas.prototype = {
  95. _init: function () {
  96. var $wrapper,
  97. $overlay,
  98. $timer,
  99. isBody = this.elmt.tagName === 'BODY',
  100. timer = this.settings.timer,
  101. overlay = this.settings.overlay;
  102. // Preloading
  103. this._preload();
  104. // Wrapper with content
  105. if (!isBody) {
  106. this.$elmt.css('height', this.$elmt.css('height'));
  107. $wrapper = $('<div class="vegas-wrapper">')
  108. .css('overflow', this.$elmt.css('overflow'))
  109. .css('padding', this.$elmt.css('padding'));
  110. // Some browsers don't compute padding shorthand
  111. if (!this.$elmt.css('padding')) {
  112. $wrapper
  113. .css('padding-top', this.$elmt.css('padding-top'))
  114. .css('padding-bottom', this.$elmt.css('padding-bottom'))
  115. .css('padding-left', this.$elmt.css('padding-left'))
  116. .css('padding-right', this.$elmt.css('padding-right'));
  117. }
  118. this.$elmt.clone(true).children().appendTo($wrapper);
  119. this.elmt.innerHTML = '';
  120. }
  121. // Timer
  122. if (timer && this.support.transition) {
  123. $timer = $('<div class="vegas-timer"><div class="vegas-timer-progress">');
  124. this.$timer = $timer;
  125. this.$elmt.prepend($timer);
  126. }
  127. // Overlay
  128. if (overlay) {
  129. $overlay = $('<div class="vegas-overlay">');
  130. if (typeof overlay === 'string') {
  131. $overlay.css('background-image', 'url(' + overlay + ')');
  132. }
  133. this.$overlay = $overlay;
  134. this.$elmt.prepend($overlay);
  135. }
  136. // Container
  137. this.$elmt.addClass('vegas-container');
  138. if (!isBody) {
  139. this.$elmt.append($wrapper);
  140. }
  141. this.trigger('init');
  142. this._goto(this.slide);
  143. },
  144. _preload: function () {
  145. var video, img, i;
  146. for (i = 0; i < this.settings.slides.length; i++) {
  147. if (this.settings.preload || this.settings.preloadImages) {
  148. if (this.settings.slides[i].src) {
  149. img = new Image();
  150. img.src = this.settings.slides[i].src;
  151. }
  152. }
  153. if (this.settings.preload || this.settings.preloadVideos) {
  154. if (this.support.video && this.settings.slides[i].video) {
  155. video = this._video(this.settings.slides[i].video);
  156. video.preload = true;
  157. video.muted = true;
  158. videoCache[this.settings.slides[i].video.toString()] = video;
  159. }
  160. }
  161. }
  162. },
  163. _random: function (array) {
  164. return array[Math.floor(Math.random() * (array.length - 1))];
  165. },
  166. _slideShow: function () {
  167. var self = this;
  168. if (this.total > 1 && !this.paused && !this.noshow) {
  169. this.timeout = setTimeout(function () {
  170. self.next();
  171. }, this._options('delay'));
  172. }
  173. },
  174. _timer: function (state) {
  175. var self = this;
  176. clearTimeout(this.timeout);
  177. if (!this.$timer) {
  178. return;
  179. }
  180. this.$timer
  181. .removeClass('vegas-timer-running')
  182. .find('div')
  183. .css('transition-duration', '0ms');
  184. if (this.paused || this.noshow) {
  185. return;
  186. }
  187. if (state) {
  188. setTimeout(function () {
  189. self.$timer
  190. .addClass('vegas-timer-running')
  191. .find('div')
  192. .css('transition-duration', self._options('delay') - 100 + 'ms');
  193. }, 100);
  194. }
  195. },
  196. _video: function (srcs) {
  197. var video,
  198. source;
  199. if (videoCache[srcs.toString()]) {
  200. return videoCache[srcs.toString()];
  201. }
  202. if (srcs instanceof Array === false) {
  203. srcs = [ srcs ];
  204. }
  205. video = document.createElement('video');
  206. srcs.forEach(function (src) {
  207. source = document.createElement('source');
  208. source.src = src;
  209. video.appendChild(source);
  210. });
  211. return video;
  212. },
  213. _options: function (key, i) {
  214. if (i === undefined) {
  215. i = this.slide;
  216. }
  217. if (this.settings.slides[i][key] !== undefined) {
  218. return this.settings.slides[i][key];
  219. }
  220. return this.settings[key];
  221. },
  222. _goto: function (nb) {
  223. if (typeof this.settings.slides[nb] === 'undefined') {
  224. nb = 0;
  225. }
  226. this.slide = nb;
  227. var $slide,
  228. $inner,
  229. self = this,
  230. $slides = this.$elmt.children('.vegas-slide'),
  231. src = this.settings.slides[nb].src,
  232. videos = this.settings.slides[nb].video,
  233. delay = this._options('delay'),
  234. align = this._options('align'),
  235. valign = this._options('valign'),
  236. color = this._options('color') || this.$elmt.css('background-color'),
  237. cover = this._options('cover') ? 'cover' : 'contain',
  238. total = $slides.length,
  239. video,
  240. img;
  241. var transition = this._options('transition'),
  242. transitionDuration = this._options('transitionDuration'),
  243. animation = this._options('animation' ),
  244. animationDuration = this._options('animationDuration');
  245. if (transition === 'random' || transition instanceof Array) {
  246. if (transition instanceof Array) {
  247. transition = this._random(transition);
  248. } else {
  249. transition = this._random(this.transitions);
  250. }
  251. }
  252. if (transition && transition !== 'none' && this.transitions.indexOf(transition) < 0) {
  253. console.error("Vegas: Transition " + transition + " doesn't exist.");
  254. }
  255. if (transitionDuration === 'auto' || transitionDuration > delay) {
  256. transitionDuration = delay;
  257. }
  258. if (animation === 'random' || animation instanceof Array) {
  259. if (animation instanceof Array) {
  260. animation = this._random(animation);
  261. } else {
  262. animation = this._random(this.animations);
  263. }
  264. }
  265. if (animation && animation !== 'none' && this.animations.indexOf(animation) < 0) {
  266. console.error("Vegas: Animation " + animation + " doesn't exist.");
  267. }
  268. if (animationDuration === 'auto') {
  269. animationDuration = delay;
  270. }
  271. // Video ?
  272. if (this.support.video && videos) {
  273. video = this._video(videos);
  274. $slide = $(video)
  275. .addClass('vegas-video')
  276. .addClass('vegas-slide')
  277. .addClass('vegas-transition-' + transition)
  278. .css('background-color', color);
  279. if (this.support.objectFit) {
  280. $slide
  281. .css('object-position', align + ' ' + valign)
  282. .css('object-fit', cover)
  283. .css('width', '100%')
  284. .css('height', '100%');
  285. } else if (cover === 'contain') {
  286. $slide
  287. .css('width', '100%')
  288. .css('height', '100%');
  289. }
  290. // Image ?
  291. } else {
  292. img = new Image();
  293. $slide = $('<div></div>')
  294. .addClass('vegas-slide')
  295. .addClass('vegas-transition-' + transition);
  296. $inner = $('<div></div>')
  297. .addClass('vegas-slide-inner')
  298. .css('background-image', 'url(' + src + ')')
  299. .css('background-color', color)
  300. .css('background-position', align + ' ' + valign)
  301. .css('background-size', cover);
  302. if (animation) {
  303. $inner
  304. .addClass('vegas-animation-' + animation)
  305. .css('animation-duration', animationDuration + 'ms');
  306. }
  307. $slide.append($inner);
  308. }
  309. if (!self.support.transition) {
  310. $slide.css('display', 'none');
  311. }
  312. if (total) {
  313. $slides.eq(total - 1).after($slide);
  314. } else {
  315. this.$elmt.prepend($slide);
  316. }
  317. $slides
  318. .css('transition', 'all 0ms')
  319. .each(function () {
  320. this.className = ' vegas-slide';
  321. this.className += ' vegas-transition-' + transition;
  322. this.className += ' vegas-transition-' + transition + '-in';
  323. if (this.tagName === 'VIDEO') {
  324. this.className += ' vegas-video';
  325. }
  326. }
  327. );
  328. self._timer(false);
  329. function go () {
  330. self._timer(true);
  331. setTimeout(function () {
  332. if (self.support.transition) {
  333. $slides
  334. .css('transition', 'all ' + transitionDuration + 'ms')
  335. .addClass('vegas-transition-' + transition + '-out');
  336. }
  337. $slide
  338. .css('transition', 'all ' + transitionDuration + 'ms')
  339. .addClass('vegas-transition-' + transition + '-in');
  340. if (!self.support.transition) {
  341. $slide.fadeIn(transitionDuration);
  342. }
  343. for (var i = 0; i < $slides.length - 1; i++) {
  344. $slides.eq(i).remove();
  345. }
  346. self.trigger('walk');
  347. self._slideShow();
  348. }, 100);
  349. }
  350. if (video) {
  351. if (video.readyState === 4) {
  352. video.currentTime = 0;
  353. video.play();
  354. go();
  355. } else {
  356. video.oncanplay = function () {
  357. video.play();
  358. if (!video._started) {
  359. video._started = true;
  360. go();
  361. }
  362. };
  363. }
  364. } else {
  365. img.src = src;
  366. img.onload = go;
  367. }
  368. },
  369. shuffle: function () {
  370. var temp,
  371. rand;
  372. for (var i = this.total - 1; i > 0; i--) {
  373. rand = Math.floor(Math.random() * (i + 1));
  374. temp = this.settings.slides[i];
  375. this.settings.slides[i] = this.settings.slides[rand];
  376. this.settings.slides[rand] = temp;
  377. }
  378. },
  379. play: function () {
  380. if (this.paused) {
  381. this.paused = false;
  382. this.next();
  383. this.trigger('play');
  384. }
  385. },
  386. pause: function () {
  387. this._timer(false);
  388. this.paused = true;
  389. this.trigger('pause');
  390. },
  391. toggle: function () {
  392. if (this.paused) {
  393. this.play();
  394. } else {
  395. this.pause();
  396. }
  397. },
  398. playing: function () {
  399. return !this.paused && !this.noshow;
  400. },
  401. current: function (advanced) {
  402. if (advanced) {
  403. return {
  404. slide: this.slide,
  405. data: this.settings.slides[this.slide]
  406. };
  407. }
  408. return this.slide;
  409. },
  410. jump: function (nb) {
  411. if (nb < 0 || nb > this.total - 1 || nb === this.slide) {
  412. return;
  413. }
  414. this.slide = nb;
  415. this._goto(this.slide);
  416. },
  417. next: function () {
  418. this.slide++;
  419. if (this.slide >= this.total) {
  420. this.slide = 0;
  421. }
  422. this._goto(this.slide);
  423. },
  424. previous: function () {
  425. this.slide--;
  426. if (this.slide < 0) {
  427. this.slide = this.total - 1;
  428. }
  429. this._goto(this.slide);
  430. },
  431. trigger: function (fn) {
  432. var params = [];
  433. if (fn !== 'init') {
  434. params = [
  435. this.slide,
  436. this.settings.slides[this.slide]
  437. ];
  438. }
  439. this.$elmt.trigger('vegas' + fn, params);
  440. if (typeof this.settings[fn] === 'function') {
  441. this.settings[fn].apply(this.$elmt, params);
  442. }
  443. },
  444. options: function (key, value) {
  445. var oldSlides = this.settings.slides;
  446. if (typeof key === 'object') {
  447. this.settings = $.extend({}, defaults, $.vegas.defaults, key);
  448. } else if (typeof key === 'string') {
  449. if (value === undefined) {
  450. return this.settings[key];
  451. }
  452. this.settings[key] = value;
  453. } else {
  454. return this.settings;
  455. }
  456. // In case slides have changed
  457. if (this.settings.slides !== oldSlides) {
  458. this.total = this.settings.slides.length;
  459. this.noshow = this.total < 2;
  460. this._preload();
  461. }
  462. }
  463. };
  464. $.fn.vegas = function(options) {
  465. var args = arguments,
  466. error = false,
  467. returns;
  468. if (options === undefined || typeof options === 'object') {
  469. return this.each(function () {
  470. if (!this._vegas) {
  471. this._vegas = new Vegas(this, options);
  472. }
  473. });
  474. } else if (typeof options === 'string') {
  475. this.each(function () {
  476. var instance = this._vegas;
  477. if (!instance) {
  478. throw new Error('No Vegas applied to this element.');
  479. }
  480. if (typeof instance[options] === 'function' && options[0] !== '_') {
  481. returns = instance[options].apply(instance, [].slice.call(args, 1));
  482. } else {
  483. error = true;
  484. }
  485. });
  486. if (error) {
  487. throw new Error('No method "' + options + '" in Vegas.');
  488. }
  489. return returns !== undefined ? returns : this;
  490. }
  491. };
  492. $.vegas = {};
  493. $.vegas.defaults = defaults;
  494. $.vegas.isVideoCompatible = function () {
  495. return !/(Android|webOS|Phone|iPad|iPod|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
  496. };
  497. })(typeof jQuery !== 'undefined' ? jQuery :
  498. typeof Zepto !== 'undefined' ? Zepto : null
  499. );