Last active
December 18, 2015 09:29
-
-
Save radist2s/5762091 to your computer and use it in GitHub Desktop.
Scenario - Build animation based on json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jQuery throttle https://github.com/cowboy/jquery-throttle-debounce */ | |
(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); | |
;(function($){ | |
/** | |
* Scenario - jQuery keyframe animation | |
* Author: Alex Batalov, radist2s@gmail.com | |
* | |
* Usage: | |
* | |
* var animationQueue = { | |
* '.slide .bg': { | |
* left: 0, | |
* top: 10, | |
* speed: 500, //ms | |
* delay: 140, //ms | |
* after: { //execute after prev animation ends | |
* '.slide .obj' : { | |
* left: 120 | |
* } | |
* }, | |
* function($el){ //run immediately | |
* //$el - is jQuery object, like $context.find('.slide .bg') | |
* $el.css({left: -100, top: 0}).closest('.slide').find('.obj').css({left: 0}) | |
* } | |
* } | |
* } | |
* | |
* $('#slider').scenario(animationQueue, callback, no_Animate_use_Css ? 1 : 0) | |
* //animationQueue will be use jQuery animation queue | |
* | |
* var animationNonQueue = { | |
* '.selector' : [ | |
* { left: 100 }, | |
* { top: 200 } | |
* ] | |
* } | |
* //animationNonQueue run all '.selector' animations at the same time | |
* | |
**/ | |
/** | |
* @var {Object} obj | |
* @return {Array} | |
**/ | |
function objectKeys (obj) { | |
if (Object.keys) { | |
return Object.keys(obj) | |
} | |
else { | |
var keys = []; | |
for (var i in obj) { | |
if (obj.hasOwnProperty(i)) { | |
keys.push(i); | |
} | |
} | |
return keys; | |
} | |
} | |
$.fn.scenario = function(scenarioOrign, callback, noAnimate) { | |
if (!scenarioOrign || !(scenarioOrign instanceof Object || scenarioOrign instanceof Function)){ | |
if (callback instanceof Function){ | |
callback() | |
} | |
return $(this) | |
} | |
var $this = $(this), | |
parent = this | |
if (scenarioOrign instanceof Function) { | |
scenarioOrign = scenarioOrign.call(parent, $this) | |
} | |
var scenario = jQuery.extend(true, {}, scenarioOrign), | |
defaultSpeed = 'normal' | |
var animationsCount = (function(animation_set){ | |
var count = 0 | |
for (var el in animation_set){ | |
count++ | |
if (animation_set[el].after){ | |
count += arguments.callee(animation_set[el].after) | |
} | |
} | |
return count | |
})(scenario) | |
function animationsCountDec(){ | |
if (!--animationsCount && callback instanceof Function){ | |
callback(scenario) | |
} | |
} | |
this.prepareAnimation = function(animation_set){ | |
//Сначала кешируем элементы, а затем заново проходимся по объекту сценария | |
var elements = {}, | |
elSelector, | |
animation | |
for (elSelector in animation_set) { | |
if (!animation_set.hasOwnProperty(elSelector)) { | |
continue | |
} | |
animation = animation_set[elSelector] | |
if (animation.$el instanceof jQuery) { | |
continue; | |
} | |
if (!elements.hasOwnProperty(elSelector)) { | |
elements[elSelector] = $this.find(elSelector) | |
} | |
} | |
var stopScenarioEvent = 'stopScenario' | |
function appendAnimation(animation, queueNamespace){ | |
if (!(animation instanceof Object)){ | |
animationsCountDec() | |
return | |
} | |
var animationQueue = queueNamespace | |
? queueNamespace + '.' + 'queue-' + new Date().getTime() + Math.random() | |
: 'scenario' | |
var scenarioQueues = $el.data('scenarioQueues') | |
if (scenarioQueues && scenarioQueues instanceof Array) { | |
var queue, | |
qi | |
for (qi in scenarioQueues) { | |
if (!scenarioQueues.hasOwnProperty(qi)) { | |
continue | |
} | |
queue = scenarioQueues[qi] | |
if (typeof queue !== 'string') { | |
continue | |
} | |
if (queue.indexOf(queueNamespace + '.queue-') < 0) { | |
$el.trigger(stopScenarioEvent, [queue]) | |
} | |
} | |
} | |
var speed = animation.speed === undefined ? defaultSpeed : animation.speed | |
delete animation.speed | |
var delay = animation.delay || 0 | |
delete animation.delay | |
var after = animation.after || false | |
delete animation.after | |
var easing = animation.easing || 'swing' | |
delete animation.easing | |
var callFunction = animation['function'] || function(){} | |
callFunction.call(parent, $el, noAnimate) | |
delete animation['function'] | |
if (objectKeys(animation).length) { | |
if (noAnimate){ | |
speed = 0 | |
} | |
var animationCallback = function(){ | |
$el.off(stopScenarioEvent + '.' + queueNamespace) | |
var scenarioQueues = $el.data('scenarioQueues') | |
if (scenarioQueues instanceof Array) { | |
var queueIndex = scenarioQueues.indexOf(scenarioQueues) | |
if (queueIndex >= 0) { | |
scenarioQueues.splice(queueIndex, 1) | |
} | |
$el.data('scenarioQueues', scenarioQueues.length ? scenarioQueues : null) | |
} | |
animationsCountDec() | |
if (after instanceof Object){ | |
parent.prepareAnimation(after) | |
} | |
} | |
$el | |
.off(stopScenarioEvent) | |
.one(stopScenarioEvent + '.' + queueNamespace, function (e, queue) { | |
$(this).stop(true, queue) | |
animationCallback() | |
}) | |
if (noAnimate) { | |
$el.css(animation) | |
animationCallback() | |
} | |
else { | |
if (delay){ | |
$el.delay(delay, animationQueue) | |
} | |
var options = { | |
duration: speed, | |
easing: easing, | |
complete: animationCallback, | |
queue: animationQueue | |
} | |
var elQueues = $el.data('scenarioQueues') || [] | |
elQueues.push(animationQueue) | |
$el | |
.data('scenarioQueues', elQueues) | |
.animate(animation, options) | |
.dequeue(animationQueue) | |
} | |
} | |
else { | |
animationsCountDec() | |
if (after instanceof Object){ | |
parent.prepareAnimation(after) | |
} | |
} | |
} | |
for (var el in animation_set) { | |
if (!animation_set.hasOwnProperty(el)) { | |
continue | |
} | |
var $el, | |
el_animations = animation_set[el] | |
if (el_animations instanceof Array && el_animations[0].$el instanceof jQuery) { | |
$el = el_animations[0].$el | |
delete el_animations[0].$el | |
} | |
else if (el_animations.$el && el_animations.$el instanceof jQuery){ | |
$el = el_animations.$el | |
delete el_animations.$el | |
} | |
else { | |
$el = elements[el] | |
} | |
if (!$el || !$el.length) { | |
animationsCountDec() | |
continue | |
} | |
if (el_animations instanceof Array) { | |
if (!el_animations.length) { | |
animationsCountDec() | |
continue | |
} | |
var queueNamespace = 'scenario' + new Date().getTime() | |
for (var i in el_animations) { | |
if (!el_animations.hasOwnProperty(i)) { | |
continue | |
} | |
appendAnimation(el_animations[i], queueNamespace) | |
} | |
} | |
else if (el_animations instanceof Object) { | |
appendAnimation(el_animations) | |
} | |
else { | |
animationsCountDec() | |
} | |
} | |
} | |
parent.prepareAnimation(scenario) | |
return $this | |
} | |
/** Scenario Slides | |
* | |
* Usage: | |
* | |
* $slider = $('#slide') | |
* $('#slide').scenarioSlides(scenario, { | |
speed: 5000, | |
slideshow: true, | |
triggers: $slider.data('triggers'), | |
useTransforms: false, | |
slideTransitionSpeed: 'normal', | |
slidesQueue: false | |
}) | |
* | |
* Methods: | |
* | |
* $slider.trigger('slider.Start') | |
* $slider.trigger('slider.Stop', [forever = false]) | |
* $slider.trigger('slider.Next') | |
* $slider.trigger('slider.Prev') | |
* $slider.trigger('slider.Show', [$single_slide, {noAnimate: false, callback: function(){}, reverse: false}]) | |
**/ | |
$.fn.scenarioSlides = function(slidesScenario, settingsOrign){ | |
if (!slidesScenario || !slidesScenario instanceof Object){ | |
return; | |
} | |
var $slider = $(this), | |
parent = this, | |
settings = settingsOrign || {} | |
if ($slider.data('inited')){ | |
return; | |
} | |
$slider.data('inited', true) | |
settings = jQuery.extend({ | |
speed: 5000, | |
slideshow: true, | |
triggers: $slider.data('triggers'), | |
useTransforms: false, | |
slideTransitionSpeed: 'normal', | |
slidesQueue: false, | |
beforeSlideShow: function(){}, | |
onTriggerClick: function(){} | |
}, settings) | |
var $sliderTriggers = settings.triggers ? $(settings.triggers) : null, | |
autoSlideTimeout = settings.speed || 5000 | |
function showSlide(noAnimate, callback, reverse){ | |
var $slide = $(this), //входящий слайд | |
animation = $slide.data('animation'), //идентификатор сценариев для входящего слайда | |
scenario = slidesScenario[animation] || {}, //сценарии входящего слайда | |
$slide_prev = $slider.find('li.slideItem.current:first') //уходящий слайд | |
if (settings.beforeSlideShow instanceof Function){ | |
settings.beforeSlideShow.call(parent, $slide, noAnimate, reverse) | |
} | |
if ($slide_prev[0] == $slide[0]){ | |
return; | |
} | |
//Механика перехода может быть left или transform ( для transform нужен плагин: https://github.com/louisremi/jquery.transform.js ) | |
var setSlideLeft = function(left){ | |
if (settings.useTransforms && $.cssProps.transform){ | |
return {transform: 'translateX(' + left + 'px)'} | |
} | |
else{ | |
return {left: left} | |
} | |
} | |
//Анимация входящего слайда | |
var slideIn = function(){ | |
var animationInit = (reverse ? scenario['initReverse'] : scenario['init']) || null | |
var animationIn = (reverse ? scenario['inReverse'] : scenario['in']) || null | |
if (!animationInit){ | |
//В анимационном сценарии в качестве параметра указывается селектор потомка родительского объекта контеста | |
//Можно использовать любой параметр, и параметр $el со значением в виде jQuery-объекта, тогда селектор будет игнорироваться | |
animationInit = { | |
0: { | |
$el : $slide, | |
'function' : function(){ | |
$slide.show().css('left', 'auto') | |
} | |
} | |
} | |
var slideLeft = reverse ? $slider.width() : -$slider.width(); | |
jQuery.extend(animationInit[0], setSlideLeft(slideLeft)) | |
} | |
else { | |
//Анимация может генерироваться каждый раз заново, например, это нужно при изменении размеров окна | |
animationInit = animationInit instanceof Function ? animationInit.call(parent, $slide) : animationInit | |
} | |
if (!animationIn){ | |
animationIn = { | |
0 : { | |
$el: $slide, | |
speed: settings.slideTransitionSpeed | |
} | |
} | |
jQuery.extend(animationIn[0], setSlideLeft(0)) | |
} | |
else { | |
animationIn = animationIn instanceof Function ? animationIn.call(parent, $slide) : animationIn | |
} | |
//Применяем сценарий инициализации без анимации, затем применяем сценарий анимации как обычно | |
$slider.scenario(animationInit, undefined, true) | |
$slider.scenario(animationIn, callback, noAnimate) | |
} | |
//Уходящий слайд | |
if ($slide_prev.length){ | |
var animationPrev = $slide_prev.data('animation'), //идентификатор сценариев | |
scenario_prev = slidesScenario[animationPrev] || {} //сценарии | |
var animationOut = (reverse ? scenario_prev['outReverse'] : scenario_prev['out']) || null | |
if (!animationOut){ | |
animationOut = { | |
0 : { | |
$el: $slide_prev, | |
after: { | |
0 : { | |
$el: $slide_prev, | |
'function' : function(){ | |
$slide_prev.hide() | |
} | |
} | |
}, | |
speed: settings.slideTransitionSpeed | |
} | |
} | |
var slideLeft = reverse ? -$slider.width() : $slider.width() | |
jQuery.extend(animationOut[0], setSlideLeft(slideLeft)) | |
} | |
else{ | |
animationOut = animationOut instanceof Function ? animationOut.call(parent, $slide_prev) : animationOut | |
} | |
//Прогружаем изображения входящего слайда | |
$slide.imagesLoaded(function(){ | |
$slider.scenario(animationOut, function(){ | |
//Если используем очереди, то запускаем входящий слайд, по завершении | |
if (settings.slidesQueue){ | |
slideIn() | |
} | |
}, noAnimate) | |
}) | |
//Если не используем очереди, то запускаем входящий слайд сразу | |
if (!settings.slidesQueue){ | |
$slide.imagesLoaded(function(){ | |
slideIn() | |
}) | |
} | |
$slide_prev.removeClass('current') | |
} | |
else { //Предыдущего слайда нет | |
$slide.imagesLoaded(function(){ | |
slideIn() | |
}) | |
} | |
$slide.addClass('current') | |
} | |
var autoSlideTimeoutId = 0, | |
slideshowCallbacks = {} | |
function stopAutoSlide(){ | |
clearTimeout(autoSlideTimeoutId) | |
slideshowCallbacks = {} | |
} | |
function startAutoSlide(immediately){ | |
if (!settings.slideshow){ | |
return; | |
} | |
stopAutoSlide() | |
autoSlideTimeoutId = setTimeout(function(){ | |
var $li_current = $slider.find('li.slideItem.current') | |
if (!$li_current.length){ | |
$li_current = $slider.find('li.slideItem:last') | |
} | |
var $li_next = $li_current.next('li.slideItem') | |
if (!$li_next.length){ | |
$li_next = $li_current.siblings('li.slideItem:first') | |
} | |
var callbackId = new Date().getTime() | |
slideshowCallbacks = {} | |
slideshowCallbacks[callbackId] = function(){ | |
startAutoSlide() | |
delete slideshowCallbacks[callbackId] | |
} | |
showSlide.call($li_next[0], false, function(){ | |
if (slideshowCallbacks && slideshowCallbacks[callbackId] instanceof Function){ | |
slideshowCallbacks[callbackId]() | |
} | |
}) | |
}, immediately ? 0 : autoSlideTimeout) | |
} | |
$slider.on('mouseenter.slider mouseleave.slider', function(e){ | |
if (e.type == 'mouseenter'){ | |
stopAutoSlide() | |
} | |
else { | |
startAutoSlide() | |
} | |
}) | |
if ($sliderTriggers){ | |
$sliderTriggers.on('mouseenter.slider mouseleave.slider', function(e){ | |
if (e.type == 'mouseenter'){ | |
stopAutoSlide() | |
} | |
else { | |
startAutoSlide() | |
} | |
}) | |
var triggerClick_Throttle = $.throttle(250, function(e, noAnimate){ | |
e.preventDefault() | |
if (settings.onTriggerClick instanceof Function){ | |
settings.onTriggerClick.call(this, e, noAnimate) | |
} | |
stopAutoSlide() | |
var $this = $(this), | |
slide = $this.data('slide'), | |
action = $this.data('action') | |
if (action){ | |
if (action == 'prevSlide'){ | |
$slider.trigger('slider.Prev') | |
} | |
else if (action == 'nextSlide'){ | |
$slider.trigger('slider.Next') | |
} | |
return; | |
} | |
if (!slide){ | |
return; | |
} | |
var $slide = $('li.slideItem[data-id="' + slide + '"]', $slider) | |
if (!$slide.length){ | |
return; | |
} | |
showSlide.call($slide[0], noAnimate) | |
}) | |
$sliderTriggers.on('click', function(e, noAnimate){ | |
triggerClick_Throttle.call(this, e, noAnimate) | |
}) | |
} | |
if (settings.slideshow){ | |
startAutoSlide(true) | |
} | |
$slider.on('slider.Start', function(){ | |
settings.slideshow = true | |
startAutoSlide() | |
}) | |
$slider.on('slider.Stop', function(e, forever){ | |
if (forever){ | |
settings.slideshow = false | |
} | |
stopAutoSlide() | |
}) | |
$slider.on('slider.Next', function(){ | |
stopAutoSlide() | |
var $li_current = $slider.find('li.slideItem.current') | |
if (!$li_current.length){ | |
$li_current = $slider.find('li.slideItem:last') | |
} | |
var $li_next = $li_current.next('li.slideItem') | |
if (!$li_next.length){ | |
$li_next = $li_current.siblings('li.slideItem:first') | |
} | |
showSlide.call($li_next[0]) | |
}) | |
$slider.on('slider.Prev slider.Previous', function(){ | |
stopAutoSlide() | |
var $li_current = $slider.find('li.slideItem.current') | |
if (!$li_current.length){ | |
$li_current = $slider.find('li.slideItem:first') | |
} | |
var $li_prev = $li_current.prev('li.slideItem') | |
if (!$li_prev.length){ | |
$li_prev = $li_current.siblings('li.slideItem:last') | |
} | |
showSlide.call($li_prev[0], false, undefined, true) | |
}) | |
$slider.on('slider.Show', function(e, $slide, settings){ | |
$slide = $slide instanceof jQuery ? $slide : $slider.find($slide); | |
settings = $.extend({ | |
noAnimate: false, | |
callback: undefined, | |
reverse: false | |
}, settings || {}); | |
if ($slide.length){ | |
showSlide.call($slide[0], settings.noAnimate, settings.callback, settings.reverse) | |
} | |
}) | |
return $slider | |
} | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment