;(function ($, Formstone, undefined) {
"use strict";
/**
* @method private
* @name resize
* @description Handles window resize
*/
function resize(windowWidth) {
Functions.iterate.call($Instances, resizeInstance);
}
/**
* @method private
* @name cacheInstances
* @description Caches active instances
*/
function cacheInstances() {
$Instances = $(Classes.base);
}
/**
* @method private
* @name construct
* @description Builds instance.
* @param data [object] "Instance data"
*/
function construct(data) {
// guid
data.guid = "__" + (GUID++);
data.youTubeGuid = 0;
data.eventGuid = Events.namespace + data.guid;
data.rawGuid = RawClasses.base + data.guid;
data.classGuid = "." + data.rawGuid;
data.$container = $('
').appendTo(this);
this.addClass( [RawClasses.base, data.customClass].join(" ") );
var source = data.source;
data.source = null;
loadMedia(data, source, true);
cacheInstances();
}
/**
* @method private
* @name destruct
* @description Tears down instance.
* @param data [object] "Instance data"
*/
function destruct(data) {
data.$container.remove();
this.removeClass( [RawClasses.base, data.customClass].join(" ") )
.off(Events.namespace);
cacheInstances();
}
/**
* @method
* @name load
* @description Loads source media
* @param source [string OR object] "Source image (string) or video (object) or YouTube (object);"
* @example $(".target").background("load", "path/to/image.jpg");
*/
/**
* @method private
* @name loadMedia
* @description Determines how to handle source media
* @param data [object] "Instance data"
* @param source [string OR object] "Source image (string) or video (object)"
* @param firstLoad [boolean] "Flag for first load"
*/
function loadMedia(data, source, firstLoad) {
// Check if the source is new
if (source !== data.source) {
data.source = source;
data.responsive = false;
data.isYouTube = false;
// Check YouTube
if ($.type(source) === "object" && $.type(source.video) === "string") {
// var parts = source.match( /^.*(?:youtu.be\/|v\/|e\/|u\/\w+\/|embed\/|v=)([^#\&\?]*).*/ );
var parts = source.video.match( /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i );
if (parts && parts.length >= 1) {
data.isYouTube = true;
data.videoId = parts[1];
}
}
if (data.isYouTube) {
// youtube video
data.playing = false;
data.playerReady = false;
data.posterLoaded = false;
loadYouTube(data, source, firstLoad);
} else if ($.type(source) === "object" && source.hasOwnProperty("poster")) {
// html5 video
loadVideo(data, source, firstLoad);
} else {
var newSource = source;
// Responsive image handling
if ($.type(source) === "object") {
var sources = {},
keys = [],
i;
for (i in source) {
if (source.hasOwnProperty(i)) {
keys.push(i);
}
}
keys.sort(Functions.sortAsc);
for (i in keys) {
if (keys.hasOwnProperty(i)) {
sources[ keys[i] ] = {
width : parseInt( keys[i] ),
url : source[ keys[i] ]
};
}
}
data.responsive = true;
data.sources = sources;
newSource = calculateSource(data);
}
loadImage(data, newSource, false, firstLoad);
}
} else {
data.$el.trigger(Events.loaded);
}
}
/**
* @method private
* @name calculateSource
* @description Determines responsive source
* @param data [object] "Instance data"
* @return [string] "New source url"
*/
function calculateSource(data) {
if (data.responsive) {
for (var i in data.sources) {
if (data.sources.hasOwnProperty(i) && Formstone.windowWidth >= data.sources[i].width) {
return data.sources[i].url;
}
}
}
return data.source;
}
/**
* @method private
* @name loadImage
* @description Loads source image
* @param data [object] "Instance data",
* @param source [string] "Source image"
* @param poster [boolean] "Flag for video poster"
* @param firstLoad [boolean] "Flag for first load"
*/
function loadImage(data, source, poster, firstLoad) {
var imageClasses = [RawClasses.media, RawClasses.image, (firstLoad !== true ? RawClasses.animated : '')].join(" "),
$media = $(''),
$img = $media.find("img"),
newSource = source;
// Load image
$img.one(Events.load, function() {
if (BGSupport) {
$media.addClass(RawClasses.native)
.css({ backgroundImage: "url('" + newSource + "')" });
}
// YTransition in
$media.transition({
property: "opacity"
},
function() {
if (!poster) {
cleanMedia(data);
}
}).css({ opacity: 1 });
doResizeInstance(data);
if (!poster || firstLoad) {
data.$el.trigger(Events.loaded);
}
}).attr("src", newSource);
if (data.responsive) {
$media.addClass(RawClasses.responsive);
}
data.$container.append($media);
// Check if image is cached
if ($img[0].complete || $img[0].readyState === 4) {
$img.trigger(Events.load);
}
data.currentSource = newSource;
}
/**
* @method private
* @name loadVideo
* @description Loads source video
* @param data [object] "Instance data"
* @param source [object] "Source video"
* @param firstLoad [boolean] "Flag for first load"
*/
function loadVideo(data, source, firstLoad) {
if (data.source && data.source.poster) {
loadImage(data, data.source.poster, true, true);
firstLoad = false;
}
if (!Formstone.isMobile) {
var videoClasses = [RawClasses.media, RawClasses.video, (firstLoad !== true ? RawClasses.animated : '')].join(" "),
html = '';
html += '';
html += '
';
var $media = $(html),
$video = $media.find("video");
$video.one(Events.loadedMetaData, function(e) {
$media.transition({
property: "opacity"
},
function() {
cleanMedia(data);
}).css({ opacity: 1 });
doResizeInstance(data);
data.$el.trigger(Events.loaded);
// Events
if (data.autoPlay) {
this.play();
}
});
data.$container.append($media);
}
}
/**
* @method private
* @name loadYouTube
* @description Loads YouTube video
* @param data [object] "Instance data"
* @param source [string] "YouTube URL"
*/
function loadYouTube(data, source, firstLoad) {
if (!data.videoId) {
var parts = source.match( /^.*(?:youtu.be\/|v\/|e\/|u\/\w+\/|embed\/|v=)([^#\&\?]*).*/ );
data.videoId = parts[1];
}
if (!data.posterLoaded) {
if (!data.source.poster) {
// data.source.poster = "http://img.youtube.com/vi/" + data.videoId + "/maxresdefault.jpg";
data.source.poster = "http://img.youtube.com/vi/" + data.videoId + "/0.jpg";
}
data.posterLoaded = true;
loadImage(data, data.source.poster, true, firstLoad);
firstLoad = false;
}
if (!Formstone.isMobile) {
if (!$("script[src*='youtube.com/iframe_api']").length) {
// $("head").append('');
$("head").append('');
}
if (!YouTubeReady) {
YouTubeQueue.push({
data: data,
source: source
});
} else {
var guid = data.guid + "_" + (data.youTubeGuid++),
youTubeClasses = [RawClasses.media, RawClasses.embed, (firstLoad !== true ? RawClasses.animated : '')].join(" "),
html = '';
var $media = $(html);
data.$container.append($media);
if (data.player) {
data.oldPlayer = data.player;
data.player = null;
}
data.player = new Window.YT.Player(guid, {
videoId: data.videoId,
playerVars: {
controls: 0,
rel: 0,
showinfo: 0,
wmode: "transparent",
enablejsapi: 1,
version: 3,
playerapiid: guid,
loop: (data.loop) ? 1 : 0,
autoplay: 1,
origin: Window.location.protocol + "//" + Window.location.host
},
events: {
onReady: function (e) {
/* console.log("onReady", e); */
data.playerReady = true;
/* data.player.setPlaybackQuality("highres"); */
if (data.mute) {
data.player.mute();
}
if (data.autoPlay) {
// make sure the video plays
data.player.playVideo();
}
},
onStateChange: function (e) {
/* console.log("onStateChange", e); */
if (!data.playing && e.data === Window.YT.PlayerState.PLAYING) {
data.playing = true;
if (!data.autoPlay) {
data.player.pauseVideo();
}
$media.transition({
property: "opacity"
},
function() {
cleanMedia(data);
}).css({ opacity: 1 });
doResizeInstance(data);
data.$el.trigger(Events.loaded);
} else if (data.loop && data.playing && e.data === Window.YT.PlayerState.ENDED) {
// fix looping option
data.player.playVideo();
}
/* if (!isSafari) { */
// Fix for Safari's overly secure security settings...
data.$el.find(Classes.embed)
.addClass(RawClasses.ready);
/* } */
},
onPlaybackQualityChange: function(e) {
/* console.log("onPlaybackQualityChange", e); */
},
onPlaybackRateChange: function(e) {
/* console.log("onPlaybackRateChange", e); */
},
onError: function(e) {
/* console.log("onError", e); */
},
onApiChange: function(e) {
/* console.log("onApiChange", e); */
}
}
});
// Resize
doResizeInstance(data);
}
}
}
/**
* @method private
* @name cleanMedia
* @description Cleans up old media
* @param data [object] "Instance data"
*/
function cleanMedia(data) {
var $media = data.$container.find(Classes.media);
if ($media.length >= 1) {
$media.not(":last").remove();
data.oldPlayer = null;
}
}
/**
* @method
* @name unload
* @description Unloads current media
* @example $(".target").background("unload");
*/
/**
* @method private
* @name uploadMedia
* @description Unloads current media
* @param data [object] "Instance data"
*/
function unloadMedia(data) {
var $media = data.$container.find(Classes.media);
if ($media.length >= 1) {
$media.transition({
property: "opacity"
},
function() {
$media.remove();
delete data.source;
}).css({ opacity: 0 });
}
}
/**
* @method
* @name pause
* @description Pauses target video
* @example $(".target").background("pause");
*/
function pause(data) {
if (data.isYouTube && data.playerReady) {
data.player.pauseVideo();
} else {
var $video = data.$container.find("video");
if ($video.length) {
$video[0].pause();
}
}
}
/**
* @method
* @name play
* @description Plays target video
* @example $(".target").background("play");
*/
function play(data) {
if (data.isYouTube && data.playerReady) {
data.player.playVideo();
} else {
var $video = data.$container.find("video");
if ($video.length) {
$video[0].play();
}
}
}
/**
* @method private
* @name resizeInstance
* @description Handle window resize event
* @param data [object] "Instance data"
*/
function resizeInstance(data) {
if (data.responsive) {
var newSource = calculateSource(data);
if (newSource !== data.currentSource) {
loadImage(data, newSource, false, true);
} else {
doResizeInstance(data);
}
} else {
doResizeInstance(data);
}
}
/**
* @method private
* @name resize
* @description Resize target instance
* @example $(".target").background("resize");
*/
/**
* @method private
* @name doResizeInstance
* @description Resize target instance
* @param data [object] "Instance data"
*/
function doResizeInstance(data) {
// Target all media
var $all = data.$container.find(Classes.media);
for (var i = 0, count = $all.length; i < count; i++) {
var $m = $all.eq(i),
type = (data.isYouTube) ? "iframe" : ($m.find("video").length ? "video" : "img"),
$media = $m.find(type);
// If media found and scaling is not natively support
if ($media.length && !(type === "img" && BGSupport)) {
var frameWidth = data.$el.outerWidth(),
frameHeight = data.$el.outerHeight(),
frameRatio = frameWidth / frameHeight,
nSize = naturalSize(data, $media);
data.width = nSize.width;
data.height = nSize.height;
data.left = 0;
data.top = 0;
var mediaRatio = (data.isYouTube) ? data.embedRatio : (data.width / data.height);
// First check the height
data.height = frameHeight;
data.width = data.height * mediaRatio;
// Next check the width
if (data.width < frameWidth) {
data.width = frameWidth;
data.height = data.width / mediaRatio;
}
// Position the media
data.left = -(data.width - frameWidth) / 2;
data.top = -(data.height - frameHeight) / 2;
$m.css({
height : data.height,
width : data.width,
left : data.left,
top : data.top
});
}
}
}
/**
* @method private
* @name naturalSize
* @description Determines natural size of target media
* @param data [object] "Instance data"
* @param $media [jQuery object] "Source media object"
* @return [object OR boolean] "Object containing natural height and width values or false"
*/
function naturalSize(data, $media) {
if (data.isYouTube) {
return {
height: 500,
width: 500 / data.embedRatio
};
} else if ($media.is("img")) {
var node = $media[0];
if ($.type(node.naturalHeight) !== "undefined") {
return {
height: node.naturalHeight,
width: node.naturalWidth
};
} else {
var img = new Image();
img.src = node.src;
return {
height: img.height,
width: img.width
};
}
} else {
return {
height: $media[0].videoHeight,
width: $media[0].videoWidth
};
}
return false;
}
/**
* @plugin
* @name Background
* @description A jQuery plugin for full-frame image and video backgrounds.
* @type widget
* @dependency core.js
* @dependency transition.js
*/
var Plugin = Formstone.Plugin("background", {
widget: true,
/**
* @options
* @param autoPlay [boolean] "Autoplay video"
* @param customClass [string] <''> "Class applied to instance"
* @param embedRatio [number] <1.777777> "Video / embed ratio (16/9)"
* @param loop [boolean] "Loop video"
* @param mute [boolean] "Mute video"
* @param source [string OR object] "Source image (string or object) or video (object) or YouTube (object)"
*/
defaults: {
autoPlay : true,
customClass : "",
embedRatio : 1.777777,
loop : true,
mute : true,
source : null
},
classes: [
"container",
"media",
"animated",
"responsive",
"native",
"fixed",
"ready"
],
/**
* @events
* @event loaded.background "Background media loaded"
* @event ready.background "Background media ready"
*/
events: {
loaded : "loaded",
ready : "ready",
loadedMetaData : "loadedmetadata"
},
methods: {
_construct : construct,
_destruct : destruct,
_resize : resize,
play : play,
pause : pause,
resize : doResizeInstance,
load : loadMedia,
unload : unloadMedia
}
}),
// Localize References
Classes = Plugin.classes,
RawClasses = Classes.raw,
Events = Plugin.events,
Functions = Plugin.functions,
Window = Formstone.window,
$Instances = [],
GUID = 0,
BGSupport = ("backgroundSize" in Formstone.document.documentElement.style),
YouTubeReady = false,
YouTubeQueue = [];
/**
* @method private global
* @name window.onYouTubeIframeAPIReady
* @description Attaches YouTube players to active instances
*/
Window.onYouTubeIframeAPIReady = function() {
YouTubeReady = true;
for (var i in YouTubeQueue) {
if (YouTubeQueue.hasOwnProperty(i)) {
loadYouTube(YouTubeQueue[i].data, YouTubeQueue[i].source);
}
}
YouTubeQueue = [];
};
})(jQuery, Formstone);