!(function(root, factory) { if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else if (typeof exports === 'object') { factory(require('jquery')); } else { factory(root.jquery); } })(this, function($) { 'use strict'; /** * name of the plugin * @private * @const * @type {string} */ var plugin_name = 'vide'; /** * default settings * @private * @const * @type {object} */ var defaults = { volume: 1, playbackrate: 1, muted: true, loop: true, autoplay: true, position: '50% 50%', postertype: 'detect', resizing: true, bgcolor: 'transparent', classname: '' }; /** * not implemented error message * @private * @const * @type {string} */ var not_implemented_msg = 'not implemented'; /** * parse a string with options * @private * @param {string} str * @returns {object|string} */ function parseoptions(str) { var obj = {}; var delimiterindex; var option; var prop; var val; var arr; var len; var i; // remove spaces around delimiters and split arr = str.replace(/\s*:\s*/g, ':').replace(/\s*,\s*/g, ',').split(','); // parse a string for (i = 0, len = arr.length; i < len; i++) { option = arr[i]; // ignore urls and a string without colon delimiters if ( option.search(/^(http|https|ftp):\/\//) !== -1 || option.search(':') === -1 ) { break; } delimiterindex = option.indexof(':'); prop = option.substring(0, delimiterindex); val = option.substring(delimiterindex + 1); // if val is an empty string, make it undefined if (!val) { val = undefined; } // convert a string value if it is like a boolean if (typeof val === 'string') { val = val === 'true' || (val === 'false' ? false : val); } // convert a string value if it is like a number if (typeof val === 'string') { val = !isnan(val) ? +val : val; } obj[prop] = val; } // if nothing is parsed if (prop == null && val == null) { return str; } return obj; } /** * parse a position option * @private * @param {string} str * @returns {object} */ function parseposition(str) { str = '' + str; // default value is a center var args = str.split(/\s+/); var x = '50%'; var y = '50%'; var len; var arg; var i; for (i = 0, len = args.length; i < len; i++) { arg = args[i]; // convert values if (arg === 'left') { x = '0%'; } else if (arg === 'right') { x = '100%'; } else if (arg === 'top') { y = '0%'; } else if (arg === 'bottom') { y = '100%'; } else if (arg === 'center') { if (i === 0) { x = '50%'; } else { y = '50%'; } } else { if (i === 0) { x = arg; } else { y = arg; } } } return { x: x, y: y }; } /** * search a poster * @private * @param {string} path * @param {function} callback */ function findposter(path, callback) { var onload = function() { callback(this.src); }; $('').on('load', onload); $('').on('load', onload); $('').on('load', onload); $('').on('load', onload); } /** * vide constructor * @param {htmlelement} element * @param {object|string} path * @param {object|string} options * @constructor */ function vide(element, path, options) { this.$element = $(element); // parse path if (typeof path === 'string') { path = parseoptions(path); } // parse options if (!options) { options = {}; } else if (typeof options === 'string') { options = parseoptions(options); } // remove an extension if (typeof path === 'string') { path = path.replace(/\.\w*$/, ''); } else if (typeof path === 'object') { for (var i in path) { if (path.hasownproperty(i)) { path[i] = path[i].replace(/\.\w*$/, ''); } } } this.settings = $.extend({}, defaults, options); this.path = path; // https://github.com/vodkabears/vide/issues/110 try { this.init(); } catch (e) { if (e.message !== not_implemented_msg) { throw e; } } } /** * initialization * @public */ vide.prototype.init = function() { var vide = this; var path = vide.path; var poster = path; var sources = ''; var $element = vide.$element; var settings = vide.settings; var position = parseposition(settings.position); var postertype = settings.postertype; var $video; var $wrapper; // set styles of a video wrapper $wrapper = vide.$wrapper = $('
') .addclass(settings.classname) .css({ position: 'absolute', 'z-index': -1, top: 0, left: 0, bottom: 0, right: 0, overflow: 'hidden', '-webkit-background-size': 'cover', '-moz-background-size': 'cover', '-o-background-size': 'cover', 'background-size': 'cover', 'background-color': settings.bgcolor, 'background-repeat': 'no-repeat', 'background-position': position.x + ' ' + position.y }); // get a poster path if (typeof path === 'object') { if (path.poster) { poster = path.poster; } else { if (path.mp4) { poster = path.mp4; } else if (path.webm) { poster = path.webm; } else if (path.ogv) { poster = path.ogv; } } } // set a video poster if (postertype === 'detect') { findposter(poster, function(url) { $wrapper.css('background-image', 'url(' + url + ')'); }); } else if (postertype !== 'none') { $wrapper.css('background-image', 'url(' + poster + '.' + postertype + ')'); } // if a parent element has a static position, make it relative if ($element.css('position') === 'static') { $element.css('position', 'relative'); } $element.prepend($wrapper); if (typeof path === 'object') { if (path.mp4) { sources += ''; } if (path.webm) { sources += ''; } if (path.ogv) { sources += ''; } $video = vide.$video = $(''); } else { $video = vide.$video = $(''); } // https://github.com/vodkabears/vide/issues/110 try { $video // set video properties .prop({ autoplay: settings.autoplay, loop: settings.loop, volume: settings.volume, muted: settings.muted, defaultmuted: settings.muted, playbackrate: settings.playbackrate, defaultplaybackrate: settings.playbackrate }); } catch (e) { throw new error(not_implemented_msg); } // video alignment $video.css({ margin: 'auto', position: 'absolute', 'z-index': -1, top: position.y, left: position.x, '-webkit-transform': 'translate(-' + position.x + ', -' + position.y + ')', '-ms-transform': 'translate(-' + position.x + ', -' + position.y + ')', '-moz-transform': 'translate(-' + position.x + ', -' + position.y + ')', transform: 'translate(-' + position.x + ', -' + position.y + ')', // disable visibility, while loading visibility: 'hidden', opacity: 0 }) // resize a video, when it's loaded .one('canplaythrough.' + plugin_name, function() { vide.resize(); }) // make it visible, when it's already playing .one('playing.' + plugin_name, function() { $video.css({ visibility: 'visible', opacity: 1 }); $wrapper.css('background-image', 'none'); }); // resize event is available only for 'window' // use another code solutions to detect dom elements resizing $element.on('resize.' + plugin_name, function() { if (settings.resizing) { vide.resize(); } }); // append a video $wrapper.append($video); }; /** * get a video element * @public * @returns {htmlvideoelement} */ vide.prototype.getvideoobject = function() { return this.$video[0]; }; /** * resize a video background * @public */ vide.prototype.resize = function() { if (!this.$video) { return; } var $wrapper = this.$wrapper; var $video = this.$video; var video = $video[0]; // get a native video size var videoheight = video.videoheight; var videowidth = video.videowidth; // get a wrapper size var wrapperheight = $wrapper.height(); var wrapperwidth = $wrapper.width(); if (wrapperwidth / videowidth > wrapperheight / videoheight) { $video.css({ // +2 pixels to prevent an empty space after transformation width: wrapperwidth + 2, height: 'auto' }); } else { $video.css({ width: 'auto', // +2 pixels to prevent an empty space after transformation height: wrapperheight + 2 }); } }; /** * destroy a video background * @public */ vide.prototype.destroy = function() { delete $[plugin_name].lookup[this.index]; this.$video && this.$video.off(plugin_name); this.$element.off(plugin_name).removedata(plugin_name); this.$wrapper.remove(); }; /** * special plugin object for instances. * @public * @type {object} */ $[plugin_name] = { lookup: [] }; /** * plugin constructor * @param {object|string} path * @param {object|string} options * @returns {jquery} * @constructor */ $.fn[plugin_name] = function(path, options) { var instance; this.each(function() { instance = $.data(this, plugin_name); // destroy the plugin instance if exists instance && instance.destroy(); // create the plugin instance instance = new vide(this, path, options); instance.index = $[plugin_name].lookup.push(instance) - 1; $.data(this, plugin_name, instance); }); return this; }; $(document).ready(function() { var $window = $(window); // window resize event listener $window.on('resize.' + plugin_name, function() { for (var len = $[plugin_name].lookup.length, i = 0, instance; i < len; i++) { instance = $[plugin_name].lookup[i]; if (instance && instance.settings.resizing) { instance.resize(); } } }); // https://github.com/vodkabears/vide/issues/68 $window.on('unload.' + plugin_name, function() { return false; }); // auto initialization // add 'data-vide-bg' attribute with a path to the video without extension // also you can pass options throw the 'data-vide-options' attribute // 'data-vide-options' must be like 'muted: false, volume: 0.5' $(document).find('[data-' + plugin_name + '-bg]').each(function(i, element) { var $element = $(element); var options = $element.data(plugin_name + '-options'); var path = $element.data(plugin_name + '-bg'); $element[plugin_name](path, options); }); }); });