Skip to content

Instantly share code, notes, and snippets.

@radist2s
Last active December 18, 2015 09:29
Show Gist options
  • Save radist2s/5762091 to your computer and use it in GitHub Desktop.
Save radist2s/5762091 to your computer and use it in GitHub Desktop.
Scenario - Build animation based on json
/* 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