Related
I have this Code with a Slideshow for Images. I Would like to set the time the images are fixed. (Image one rest for 10min and then fade out). All the Images should rest about 10min. I like to use this for a Infoscreen in my company. Please help :)
(function() {
function Slideshow( element ) {
this.el = document.querySelector( element );
this.init();
}
Slideshow.prototype = {
init: function() {
this.wrapper = this.el.querySelector( ".slider-wrapper" );
this.slides = this.el.querySelectorAll( ".slide" );
this.previous = this.el.querySelector( ".slider-previous" );
this.next = this.el.querySelector( ".slider-next" );
this.index = 0;
this.total = this.slides.length;
this.timer = null;
this.action();
this.stopStart();
},
_slideTo: function( slide ) {
var currentSlide = this.slides[slide];
currentSlide.style.opacity = 1;
for( var i = 0; i < this.slides.length; i++ ) {
var slide = this.slides[i];
if( slide !== currentSlide ) {
slide.style.opacity = 0;
}
}
},
action: function() {
var self = this;
self.timer = setInterval(function() {
self.index++;
if( self.index == self.slides.length ) {
self.index = 0;
}
self._slideTo( self.index );
}, 3000);
},
stopStart: function() {
var self = this;
self.el.addEventListener( "mouseover", function() {
clearInterval( self.timer );
self.timer = null;
}, false);
self.el.addEventListener( "mouseout", function() {
self.action();
}, false);
}
};
document.addEventListener( "DOMContentLoaded", function() {
var slider = new Slideshow( "#main-slider" );
});
})();
Well, there is only on thing that controls anything timing related in your code, so its a pretty safe bet that's what you want to change.
You have a setInterval() in action with its time set to 3000. Change that to 600000 (10m * 60s * 1000ms) and you should be all set.
action: function() {
var self = this;
self.timer = setInterval(function() {
self.index++;
if( self.index == self.slides.length ) {
self.index = 0;
}
self._slideTo( self.index );
}, 1000*60*10);
This function uses setInterval, which expects interval in ms as the second parameter. In this example its set to 3000 which is 3 seconds.
Change it to what I wrote above (1000ms is 1 second * 60 = 1 minute * 10 = 10 minutes).
Sorry in advance if this is confusing. I've incorporated the Automatic Image Montage jQuery plugin for a page I'm working on and seem to have broken the feature that automatically resizes images upon a window resize event. Everything else about the plugin is working as it should. What am I doing wrong?
I'm pretty inexperienced with jQuery and Javascript, but have only modified a few of the max/min image size options in the plugin's js file. The internal reference in the js file that seems related to this problem is 'smartresize.' If you don't want to download the demo from the link above I've included a de-minified version of the js file below. First, my relevant css and html are as follows:
CSS
/*=========================automontage==============================*/
.am-container {
margin-top:75px;
}
.am-wrapper{
float:left;
position:relative;
overflow:hidden;
}
.am-wrapper img{
position:absolute;
outline:none;
}
/*=========================automontage==============================*/
HTML
<div class="am-container" id="am-container">
<img src="img/265_s.jpg"></img>
<img src="img/mont1_cloud_s.jpg"></img>
<img src="img/san_diego_street.jpg"></img>
<img src="img/IMG_8576_s.jpg"></img>
<img src="img/IMG_9827_1_s.jpg"></img>
<img src="img/IMG_0999_s.jpg"></img>
<img src="img/Lake_pano_11.jpg"></img>
<img src="img/IMG_8967_s.jpg"></img>
<img src="img/IMG_1346_s.jpg"></img>
<img src="img/IMG_2450.jpg"></img>
</div>
In HTML script
<script type="text/javascript" src="js/jquery.montage.min.js"></script>
<script type="text/javascript">
/*automontage*/
$(function() {
var $container = $('#am-container'),
$imgs = $container.find('img').hide(),
totalImgs = $imgs.length,
cnt = 0;
$imgs.each(function(i) {
var $img = $(this);
$('<img/>').load(function() {
++cnt;
if( cnt === totalImgs ) {
$imgs.show();
$container.montage({
fillLastRow : true,
alternateHeight : true,
alternateHeightRange : {
min : 150,
max : 350
}
});
}
}).attr('src',$img.attr('src'));
});
});
</script>
jQuery Plugin File Code (de-minified)
/**
* jQuery Montage plugin
* http://www.codrops.com/
*
* Copyright 2011, Pedro Botelho
* Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
*
* Date: Tue Aug 30 2011
*/
(function( window, $, undefined ) {
/*
* Array.max, Array.min
* #John Resig
* http://ejohn.org/blog/fast-javascript-maxmin/
*/
// function to get the Max value in array
Array.max = function( array ){
return Math.max.apply( Math, array );
};
// function to get the Min value in array
Array.min = function( array ){
return Math.min.apply( Math, array );
};
/*
* smartresize: debounced resize event for jQuery
*
* latest version and complete README available on Github:
* https://github.com/louisremi/jquery.smartresize.js
*
* Copyright 2011 #louis_remi
* Licensed under the MIT license.
*/
var $event = $.event, resizeTimeout;
$event.special.smartresize = {
setup: function() {
$(this).bind( "resize", $event.special.smartresize.handler );
},
teardown: function() {
$(this).unbind( "resize", $event.special.smartresize.handler );
},
handler: function( event, execAsap ) {
// Save the context
var context = this,
args = arguments;
// set correct event type
event.type = "smartresize";
if ( resizeTimeout ) { clearTimeout( resizeTimeout ); }
resizeTimeout = setTimeout(function() {
jQuery.event.handle.apply( context, args );
}, execAsap === "execAsap"? 0 : 50 );
}
};
$.fn.smartresize = function( fn ) {
return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
};
// ======================= imagesLoaded Plugin ===============================
// https://github.com/desandro/imagesloaded
// $('#my-container').imagesLoaded(myFunction)
// execute a callback when all images have loaded.
// needed because .load() doesn't work on cached images
// callback function gets image collection as argument
// `this` is the container
// original: mit license. paul irish. 2010.
// contributors: Oren Solomianik, David DeSandro, Yiannis Chatzikonstantinou
$.fn.imagesLoaded = function( callback ) {
var $images = this.find('img'),
len = $images.length,
_this = this,
blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==';
function triggerCallback() {
callback.call( _this, $images );
}
function imgLoaded() {
if ( --len <= 0 && this.src !== blank ){
setTimeout( triggerCallback );
$images.unbind( 'load error', imgLoaded );
}
}
if ( !len ) {
triggerCallback();
}
$images.bind( 'load error', imgLoaded ).each( function() {
// cached images don't fire load sometimes, so we reset src.
if (this.complete || this.complete === undefined){
var src = this.src;
// webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
// data uri bypasses webkit log warning (thx doug jones)
this.src = blank;
this.src = src;
}
});
return this;
};
$.Montage = function( options, element ) {
this.element = $( element ).show();
this.cache = {};
this.heights = new Array();
this._create( options );
};
$.Montage.defaults = {
liquid : true, // if you use percentages (or no width at all) for the container's width, then set this to true
// this will set the body's overflow-y to scroll ( fix for the scrollbar's width problem )
margin : 1, // space between images.
minw : 70, // the minimum width that a picture should have.
minh : 20, // the minimum height that a picture should have.
maxh : 250, // the maximum height that a picture should have.
alternateHeight : false,// alternate the height value for every row. If true this has priority over defaults.fixedHeight.
alternateHeightRange : { // the height will be a random value between min and max.
min : 100,
max : 300
},
fixedHeight : null, // if the value is set this has priority over defaults.minsize. All images will have this height.
minsize : false,// minw,minh are irrelevant. Chosen height is the minimum one available.
fillLastRow : false // if true, there will be no gaps in the container. The last image will fill any white space available
};
$.Montage.prototype = {
_getImageWidth : function( $img, h ) {
var i_w = $img.width(), i_h = $img.height(), r_i = i_h / i_w;
return Math.ceil( h / r_i );
},
_getImageHeight : function( $img, w ) {
var i_w = $img.width(), i_h = $img.height(), r_i = i_h / i_w;
return Math.ceil( r_i * w );
},
_chooseHeight : function() {
// get the minimum height
if( this.options.minsize ) {
return Array.min( this.heights );
}
// otherwise get the most repeated heights. From those choose the minimum.
var result = {},
max = 0,
res, val, min;
for( var i = 0, total = this.heights.length; i < total; ++i ) {
var val = this.heights[i], inc = ( result[val] || 0 ) + 1;
if( val < this.options.minh || val > this.options.maxh ) continue;
result[val] = inc;
if( inc >= max ) {
max = inc; res = val;
}
}
for (var i in result) {
if (result[i] === max) {
val = i;
min = min || val;
if(min < this.options.minh)
min = null;
else if (min > val)
min = val;
if(min === null)
min = val;
}
}
if(min === undefined) min = this.heights[0];
res = min;
return res;
},
_stretchImage : function( $img ) {
var prevWrapper_w = $img.parent().width(),
new_w = prevWrapper_w + this.cache.space_w_left,
crop = {
x : new_w,
y : this.theHeight
};
var new_image_w = $img.width() + this.cache.space_w_left,
new_image_h = this._getImageHeight( $img, new_image_w );
this._cropImage( $img, new_image_w, new_image_h, crop );
this.cache.space_w_left = this.cache.container_w;
// if this.options.alternateHeight is true, change row / change height
if( this.options.alternateHeight)
this.theHeight = Math.floor( Math.random()*( this.options.alternateHeightRange.max - this.options.alternateHeightRange.min + 1 ) + this.options.alternateHeightRange.min );
},
_updatePrevImage : function( $nextimg ) {
var $prevImage = this.element.find('img.montage:last');
this._stretchImage( $prevImage );
this._insertImage( $nextimg );
},
_insertImage : function( $img ) {
// width the image should have with height = this.theHeight.
var new_w = this._getImageWidth( $img, this.theHeight );
// use the minimum height available if this.options.minsize = true.
if( this.options.minsize && !this.options.alternateHeight ) {
if( this.cache.space_w_left <= this.options.margin * 2 ) {
this._updatePrevImage( $img );
}
else {
if( new_w > this.cache.space_w_left ) {
var crop = { x : this.cache.space_w_left, y : this.theHeight };
this._cropImage( $img, new_w, this.theHeight, crop );
this.cache.space_w_left = this.cache.container_w;
$img.addClass('montage');
}
else {
var crop = { x : new_w, y : this.theHeight };
this._cropImage( $img, new_w, this.theHeight, crop );
this.cache.space_w_left -= new_w;
$img.addClass('montage');
}
}
}
else {
// the width is lower than the minimum width allowed.
if( new_w < this.options.minw ) {
// the minimum width allowed is higher than the space left to fill the row.
// need to resize the previous (last) item in that row.
if( this.options.minw > this.cache.space_w_left ) {
this._updatePrevImage( $img );
}
else {
var new_w = this.options.minw, new_h = this._getImageHeight( $img, new_w ), crop = { x : new_w, y : this.theHeight };
this._cropImage( $img, new_w, new_h, crop );
this.cache.space_w_left -= new_w;
$img.addClass('montage');
}
}
else {
// the new width is higher than the space left but the space left is lower than the minimum width allowed.
// need to resize the previous (last) item in that row.
if( new_w > this.cache.space_w_left && this.cache.space_w_left < this.options.minw ) {
this._updatePrevImage( $img );
}
else if( new_w > this.cache.space_w_left && this.cache.space_w_left >= this.options.minw ) {
var crop = {x : this.cache.space_w_left, y : this.theHeight};
this._cropImage( $img, new_w, this.theHeight, crop );
this.cache.space_w_left = this.cache.container_w;
// if this.options.alternateHeight is true, change row / change height
if( this.options.alternateHeight)
this.theHeight = Math.floor( Math.random()*( this.options.alternateHeightRange.max - this.options.alternateHeightRange.min + 1 ) + this.options.alternateHeightRange.min );
$img.addClass('montage');
}
else {
var crop = { x : new_w, y : this.theHeight};
this._cropImage( $img, new_w, this.theHeight, crop );
this.cache.space_w_left -= new_w;
$img.addClass('montage');
}
}
}
},
_cropImage : function( $img, w, h, cropParam ) {
// margin value
var dec = this.options.margin * 2;
var $wrapper = $img.parent('a');
// resize the image
this._resizeImage( $img, w, h );
// adjust the top / left values to slice the image without loosing the its ratio
$img.css({
left : - ( w - cropParam.x ) / 2 + 'px',
top : - ( h - cropParam.y ) / 2 + 'px'
});
// wrap the image in a <a> element
$wrapper.addClass('am-wrapper').css({
width : cropParam.x - dec + 'px',
height : cropParam.y + 'px',
margin : this.options.margin
});
},
_resizeImage : function( $img, w, h ) {
$img.css( { width : w + 'px', height : h + 'px' } );
},
_reload : function() {
// container's width
var new_el_w = this.element.width();
// if different, something changed...
if( new_el_w !== this.cache.container_w ) {
this.element.hide();
this.cache.container_w = new_el_w;
this.cache.space_w_left = new_el_w;
var instance = this;
instance.$imgs.removeClass('montage').each( function(i) {
instance._insertImage( $(this) );
});
if( instance.options.fillLastRow && instance.cache.space_w_left !== instance.cache.container_w ) {
instance._stretchImage( instance.$imgs.eq( instance.totalImages - 1 ) );
}
instance.element.show();
}
},
_create : function( options ) {
this.options = $.extend( true, {}, $.Montage.defaults, options );
var instance = this,
el_w = instance.element.width();
instance.$imgs = instance.element.find('img');
instance.totalImages= instance.$imgs.length;
// solve the scrollbar width problem.
if( instance.options.liquid )
$('html').css( 'overflow-y', 'scroll' );
// save the heights of all images.
if( !instance.options.fixedHeight ) {
instance.$imgs.each( function(i) {
var $img = $(this), img_w = $img.width();
// if images have width > instance.options.minw then "resize" image.
if( img_w < instance.options.minw && !instance.options.minsize ) {
var new_h = instance._getImageHeight( $img, instance.options.minw );
instance.heights.push( new_h );
}
else {
instance.heights.push( $img.height() );
}
});
}
// calculate which height to use for each image.
instance.theHeight = ( !instance.options.fixedHeight && !instance.options.alternateHeight ) ? instance._chooseHeight() : instance.options.fixedHeight;
if( instance.options.alternateHeight )
instance.theHeight = Math.floor( Math.random() * ( instance.options.alternateHeightRange.max - instance.options.alternateHeightRange.min + 1 ) + instance.options.alternateHeightRange.min );
// save some values.
instance.cache.container_w = el_w;
// space left to fill the row.
instance.cache.space_w_left = el_w;
// wrap the images with the right sizes.
instance.$imgs.each( function(i) {
instance._insertImage( $(this) );
});
if( instance.options.fillLastRow && instance.cache.space_w_left !== instance.cache.container_w ) {
instance._stretchImage( instance.$imgs.eq( instance.totalImages - 1 ) );
}
// window resize event : reload the container.
$(window).bind('smartresize.montage', function() {
instance._reload();
});
},
add : function( $images, callback ) {
// adds one or more images to the container
var $images_stripped = $images.find('img');
this.$imgs = this.$imgs.add( $images_stripped );
this.totalImages= this.$imgs.length;
this._add( $images, callback );
},
_add : function( $images, callback ) {
var instance = this;
$images.find('img').each( function(i) {
instance._insertImage( $(this) );
});
if( instance.options.fillLastRow && instance.cache.space_w_left !== instance.cache.container_w )
instance._stretchImage( instance.$imgs.eq( instance.totalImages - 1 ) );
if ( callback ) callback.call( $images );
},
destroy : function( callback ) {
this._destroy( callback );
},
_destroy : function( callback ) {
this.$imgs.removeClass('montage').css({
position : '',
width : '',
height : '',
left : '',
top : ''
}).unwrap();
if( this.options.liquid )
$('html').css( 'overflow', '' );
this.element.unbind('.montage').removeData('montage');
$(window).unbind('.montage');
if ( callback ) callback.call();
},
option : function( key, value ) {
// set options AFTER initialization:
if ( $.isPlainObject( key ) ){
this.options = $.extend( true, this.options, key );
}
}
};
// taken from jquery.masonry
// https://github.com/desandro/masonry
// helper function for logging errors
// $.error breaks jQuery chaining
var logError = function( message ) {
if ( this.console ) {
console.error( message );
}
};
// Structure taken from jquery.masonry
// https://github.com/desandro/masonry
// ======================= Plugin bridge ===============================
// leverages data method to either create or return $.Montage constructor
// A bit from jQuery UI
// https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
// A bit from jcarousel
// https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
$.fn.montage = function( options ) {
if ( typeof options === 'string' ) {
// call method
var args = Array.prototype.slice.call( arguments, 1 );
this.each(function() {
var instance = $.data( this, 'montage' );
if ( !instance ) {
logError( "cannot call methods on montage prior to initialization; " +
"attempted to call method '" + options + "'" );
return;
}
if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
logError( "no such method '" + options + "' for montage instance" );
return;
}
// apply method
instance[ options ].apply( instance, args );
});
}
else {
this.each(function() {
var instance = $.data( this, 'montage' );
if ( instance ) {
// apply options & reload
instance.option( options || {} );
instance._reload();
}
else {
// initialize new instance
$.data( this, 'montage', new $.Montage( options, this ) );
}
});
}
return this;
};
})( window, jQuery );
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
Figured it out. Turns out that my jQuery plugin version, which I'd changed from 1.6.2 to 1.12.4, broke the plugin. I played with the plugin version until I found one that worked for the other plugins I'm using, too (magnific popup lightbox, automatic montage). Now all is working! 1.7.0 was what worked for all.
I am trying to turn my code into a working library. (My first)
Currently you simply call GridNav() and it sets up and executes.
I am currently refactoring out all the necessary variables
The trouble I am having is the animations property. I want users to be able to over ride the property.
(function(window, document, $, undefined) {
'use strict';
var fixOutOfBoundCordinates = function(pos, max) {
if (pos < 1) {
pos = max;
} else if (pos > max) {
pos = 1
}
return pos;
};
var calculateDestination = function(position, direction, columns) {
var directions = {
1: [-1,-1],
2: [0,-1],
3: [1,-1],
4: [-1,0],
6: [1,0],
7: [-1,1],
8: [0,1],
9: [1,1]
};
direction = directions[direction];
var y = Math.ceil(position/columns);
var x = position - ((y-1) * columns);
x = fixOutOfBoundCordinates((x+direction[0]), columns);
y = fixOutOfBoundCordinates((y+direction[1]), columns);
return (x + ((y-1)*columns)) -1;
};
var GridNav = function(params) {
return new Library(params);
};
var Library = function(params) {
//var $main = document.querySelectorAll( '#pt-main' ),
var $main = $('#pt-main'),
$pages = $main.children( 'section.pt-page' ),
$iterate = $( '.panel' ),
pagesCount = $pages.length,
isAnimating = false,
endCurrPage = false,
endNextPage = false,
animEndEventNames = {
'WebkitAnimation' : 'webkitAnimationEnd',
'OAnimation' : 'oAnimationEnd',
'msAnimation' : 'MSAnimationEnd',
'animation' : 'animationend'
},
// animation end event name
animEndEventName = animEndEventNames[ 'animation'],
keys = {
BACKSPACE: 8,
DOWN: 40,
ENTER: 13,
LEFT: 37,
UP: 38,
RIGHT: 39,
SPACE: 32,
PAGE_DOWN: 34,
PAGE_UP: 33
};
function nextPage(outpage, direction ) {
if( isAnimating ) {
return false;
}
var cols = $main.data("col");
var inpage = calculateDestination(outpage, direction, cols);
isAnimating = true;
var $currPage = $pages.eq( outpage-1 ),
// Done early so element visible during animation
$nextPage = $pages.eq( inpage ).addClass( 'pt-page-current' ),
outClass = '', inClass = '';
$currPage.addClass( Library.animation[direction]["outClass"] ).on( animEndEventName, function() {
$currPage.off( animEndEventName );
endCurrPage = true;
if( endNextPage ) {
onEndAnimation( $currPage, $nextPage );
}
} );
$nextPage.addClass( Library.animation[direction]["inClass"] ).on( animEndEventName, function() {
$nextPage.off( animEndEventName );
endNextPage = true;
if( endCurrPage ) {
onEndAnimation( $currPage, $nextPage );
}
} );
}
function onEndAnimation( $outpage, $inpage ) {
endCurrPage = false;
endNextPage = false;
resetPage( $outpage, $inpage );
isAnimating = false;
}
function resetPage( $outpage, $inpage ) {
$outpage.attr( 'class', $outpage.data( 'originalClassList' ) );
$inpage.attr( 'class', $inpage.data( 'originalClassList' ) + ' pt-page-current' );
}
$pages.each( function() {
var $page = $( this );
$page.data( 'originalClassList', $page.attr( 'class' ) );
} );
// Use start class as begining
var start = $pages.index($pages.filter(".start"));
if (start == -1) {
start = Math.ceil(pagesCount/2)-1
} else {
}
$pages.eq(start).addClass('pt-page-current');
$( "body" ).keyup(function(event) {
var key = event.which;
var cur;
if ( key == keys.DOWN || key == keys.PAGE_DOWN ) {
cur = $('section.pt-page').filter(".pt-page-current").index() + 1;
nextPage( cur, 8);
}
if ( key == keys.UP || key == keys.PAGE_UP ) {
cur = $('section.pt-page').filter(".pt-page-current").index() + 1;
nextPage( cur, 2);
}
if ( key == keys.RIGHT || key == keys.SPACE || key == keys.ENTER ) {
cur = $('section.pt-page').filter(".pt-page-current").index() + 1;
nextPage( cur, 6);
}
if ( key == keys.LEFT || key == keys.BACKSPACE ) {
cur = $('section.pt-page').filter(".pt-page-current").index() + 1;
nextPage( cur, 4);
}
});
$iterate.on( 'click', function() {
var cur = $('section.pt-page').filter(".pt-page-current").index() + 1;
var direction = $iterate.index($(this)) + 1;
if (direction > 4) {
direction+= 1;
}
nextPage(cur, direction);
} );
return this;
};
Library.animation = {
1:
// Move UP and Left
{outClass: 'pt-page-moveToBottomRight',inClass: 'pt-page-moveFromTopLeft'},
2:
// Move UP
{outClass: 'pt-page-moveToBottom', inClass: 'pt-page-moveFromTop'},
3:
// Move UP and Right
{outClass: 'pt-page-moveToBottomLeft', inClass: 'pt-page-moveFromTopRight'},
4:
// Move Left
{outClass: 'pt-page-moveToRight', inClass: 'pt-page-moveFromLeft'},
6:
// Move Right
{outClass: 'pt-page-moveToLeft', inClass: 'pt-page-moveFromRight'},
7:
// Move Down and Left
{outClass: 'pt-page-moveToTopRight', inClass: 'pt-page-moveFromBottomLeft'},
8:
// Move Down
{outClass: 'pt-page-moveToTop', inClass: 'pt-page-moveFromBottom'},
9:
// Move Down and Right
{outClass: 'pt-page-moveToTopLeft', inClass: 'pt-page-moveFromBottomRight'}
};
//define globally if it doesn't already exist
if(!window.GridNav){
window.GridNav = GridNav;
}
else{
console.log("Library already defined.");
}
})(window, document, jQuery);
Here it is working:
codepen
Any other library recommendations/tips welcome.
Was sent here from code review
I'm using slit slider (http://tympanus.net/codrops/2012/06/05/fullscreen-slit-slider-with-jquery-and-css3/) for a project and i want the slider to stop after the last slide, while leaving the arrows still active. I'm rather new to jquery coding so if anyone could help it'd really appreciate it. http://jsfiddle.net/totimage/y40wy5uf/
This is the code i'm currently using:
;( function( $, window, undefined ) {
'use strict';
var $event = $.event,
$special,
resizeTimeout;
$special = $event.special.debouncedresize = {
setup: function() {
$( this ).on( "resize", $special.handler );
},
teardown: function() {
$( this ).off( "resize", $special.handler );
},
handler: function( event, execAsap ) {
// Save the context
var context = this,
args = arguments,
dispatch = function() {
// set correct event type
event.type = "debouncedresize";
$event.dispatch.apply( context, args );
};
if ( resizeTimeout ) {
clearTimeout( resizeTimeout );
}
execAsap ?
dispatch() :
resizeTimeout = setTimeout( dispatch, $special.threshold );
},
threshold: 20
};
// global
var $window = $( window ),
$document = $( document ),
Modernizr = window.Modernizr;
$.Slitslider = function( options, element ) {
this.$elWrapper = $( element );
this._init( options );
};
$.Slitslider.defaults = {
// transitions speed
speed : 1500,
// if true the item's slices will also animate the opacity value
optOpacity : true,
// amount (%) to translate both slices - adjust as necessary
translateFactor : 230,
// maximum possible angle
maxAngle : 25,
// maximum possible scale
maxScale : 2,
// slideshow on / off
autoplay : true,
// keyboard navigation
keyboard : false,
// time between transitions
interval : 500,
// callbacks
onBeforeChange : function( slide, idx ) { return true; },
onAfterChange : function( slide, idx ) { return false; }
};
$.Slitslider.prototype = {
_init : function( options ) {
// options
this.options = $.extend( true, {}, $.Slitslider.defaults, options );
// https://github.com/twitter/bootstrap/issues/2870
this.transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'msTransition' : 'MSTransitionEnd',
'transition' : 'transitionend'
};
this.transEndEventName = this.transEndEventNames[ Modernizr.prefixed( 'transition' ) ];
// suport for css 3d transforms and css transitions
this.support = Modernizr.csstransitions && Modernizr.csstransforms3d;
// the slider
this.$el = this.$elWrapper.children( '.sl-slider' );
// the slides
this.$slides = this.$el.children( '.sl-slide' ).hide();
// total slides
this.slidesCount = this.$slides.length;
// current slide
this.current = 0;
// control if it's animating
this.isAnimating = false;
// get container size
this._getSize();
// layout
this._layout();
// load some events
this._loadEvents();
// slideshow
if( this.options.autoplay ) {
this._startSlideshow();
}
},
// gets the current container width & height
_getSize : function() {
this.size = {
width : this.$elWrapper.outerWidth( true ),
height : this.$elWrapper.outerHeight( true )
};
},
_layout : function() {
this.$slideWrapper = $( '<div class="sl-slides-wrapper" />' );
// wrap the slides
this.$slides.wrapAll( this.$slideWrapper ).each( function( i ) {
var $slide = $( this ),
// vertical || horizontal
orientation = $slide.data( 'orientation' );
$slide.addClass( 'sl-slide-' + orientation )
.children()
.wrapAll( '<div class="sl-content-wrapper" />' )
.wrapAll( '<div class="sl-content" />' );
} );
// set the right size of the slider/slides for the current window size
this._setSize();
// show first slide
this.$slides.eq( this.current ).show();
},
_navigate : function( dir, pos ) {
if( this.isAnimating || this.slidesCount < 2 ) {
return false;
}
this.isAnimating = true;
var self = this,
$currentSlide = this.$slides.eq( this.current );
// if position is passed
if( pos !== undefined ) {
this.current = pos;
}
// if not check the boundaries
else if( dir === 'next' ) {
this.current = this.current < this.slidesCount - 1 ? ++this.current : 0;
}
else if( dir === 'prev' ) {
this.current = this.current > 0 ? --this.current : this.slidesCount - 1;
}
this.options.onBeforeChange( $currentSlide, this.current );
// next slide to be shown
var $nextSlide = this.$slides.eq( this.current ),
// the slide we want to cut and animate
$movingSlide = ( dir === 'next' ) ? $currentSlide : $nextSlide,
// the following are the data attrs set for each slide
configData = $movingSlide.data(),
config = {};
config.orientation = configData.orientation || 'horizontal',
config.slice1angle = configData.slice1Rotation || 0,
config.slice1scale = configData.slice1Scale || 1,
config.slice2angle = configData.slice2Rotation || 0,
config.slice2scale = configData.slice2Scale || 1;
this._validateValues( config );
var cssStyle = config.orientation === 'horizontal' ? {
marginTop : -this.size.height / 2
} : {
marginLeft : -this.size.width / 2
},
// default slide's slices style
resetStyle = {
'transform' : 'translate(0%,0%) rotate(0deg) scale(1)',
opacity : 1
},
// slice1 style
slice1Style = config.orientation === 'horizontal' ? {
'transform' : 'translateY(-' + this.options.translateFactor + '%) rotate(' + config.slice1angle + 'deg) scale(' + config.slice1scale + ')'
} : {
'transform' : 'translateX(-' + this.options.translateFactor + '%) rotate(' + config.slice1angle + 'deg) scale(' + config.slice1scale + ')'
},
// slice2 style
slice2Style = config.orientation === 'horizontal' ? {
'transform' : 'translateY(' + this.options.translateFactor + '%) rotate(' + config.slice2angle + 'deg) scale(' + config.slice2scale + ')'
} : {
'transform' : 'translateX(' + this.options.translateFactor + '%) rotate(' + config.slice2angle + 'deg) scale(' + config.slice2scale + ')'
};
if( this.options.optOpacity ) {
slice1Style.opacity = 0;
slice2Style.opacity = 0;
}
// we are adding the classes sl-trans-elems and sl-trans-back-elems to the slide that is either coming "next"
// or going "prev" according to the direction.
// the idea is to make it more interesting by giving some animations to the respective slide's elements
//( dir === 'next' ) ? $nextSlide.addClass( 'sl-trans-elems' ) : $currentSlide.addClass( 'sl-trans-back-elems' );
$currentSlide.removeClass( 'sl-trans-elems' );
var transitionProp = {
'transition' : 'all ' + this.options.speed + 'ms ease-in-out'
};
// add the 2 slices and animate them
$movingSlide.css( 'z-index', this.slidesCount )
.find( 'div.sl-content-wrapper' )
.wrap( $( '<div class="sl-content-slice" />' ).css( transitionProp ) )
.parent()
.cond(
dir === 'prev',
function() {
var slice = this;
this.css( slice1Style );
setTimeout( function() {
slice.css( resetStyle );
}, 50 );
},
function() {
var slice = this;
setTimeout( function() {
slice.css( slice1Style );
}, 50 );
}
)
.clone()
.appendTo( $movingSlide )
.cond(
dir === 'prev',
function() {
var slice = this;
this.css( slice2Style );
setTimeout( function() {
$currentSlide.addClass( 'sl-trans-back-elems' );
if( self.support ) {
slice.css( resetStyle ).on( self.transEndEventName, function() {
self._onEndNavigate( slice, $currentSlide, dir );
} );
}
else {
self._onEndNavigate( slice, $currentSlide, dir );
}
}, 50 );
},
function() {
var slice = this;
setTimeout( function() {
$nextSlide.addClass( 'sl-trans-elems' );
if( self.support ) {
slice.css( slice2Style ).on( self.transEndEventName, function() {
self._onEndNavigate( slice, $currentSlide, dir );
} );
}
else {
self._onEndNavigate( slice, $currentSlide, dir );
}
}, 50 );
}
)
.find( 'div.sl-content-wrapper' )
.css( cssStyle );
$nextSlide.show();
},
_validateValues : function( config ) {
// OK, so we are restricting the angles and scale values here.
// This is to avoid the slices wrong sides to be shown.
// you can adjust these values as you wish but make sure you also ajust the
// paddings of the slides and also the options.translateFactor value and scale data attrs
if( config.slice1angle > this.options.maxAngle || config.slice1angle < -this.options.maxAngle ) {
config.slice1angle = this.options.maxAngle;
}
if( config.slice2angle > this.options.maxAngle || config.slice2angle < -this.options.maxAngle ) {
config.slice2angle = this.options.maxAngle;
}
if( config.slice1scale > this.options.maxScale || config.slice1scale <= 0 ) {
config.slice1scale = this.options.maxScale;
}
if( config.slice2scale > this.options.maxScale || config.slice2scale <= 0 ) {
config.slice2scale = this.options.maxScale;
}
if( config.orientation !== 'vertical' && config.orientation !== 'horizontal' ) {
config.orientation = 'horizontal'
}
},
_onEndNavigate : function( $slice, $oldSlide, dir ) {
// reset previous slide's style after next slide is shown
var $slide = $slice.parent(),
removeClasses = 'sl-trans-elems sl-trans-back-elems';
// remove second slide's slice
$slice.remove();
// unwrap..
$slide.css( 'z-index', 1 )
.find( 'div.sl-content-wrapper' )
.unwrap();
// hide previous current slide
$oldSlide.hide().removeClass( removeClasses );
$slide.removeClass( removeClasses );
// now we can navigate again..
this.isAnimating = false;
this.options.onAfterChange( $slide, this.current );
},
_setSize : function() {
// the slider and content wrappers will have the window's width and height
var cssStyle = {
width : this.size.width,
height : this.size.height
};
this.$el.css( cssStyle ).find( 'div.sl-content-wrapper' ).css( cssStyle );
},
_loadEvents : function() {
var self = this;
$window.on( 'debouncedresize.slitslider', function( event ) {
// update size values
self._getSize();
// set the sizes again
self._setSize();
} );
if ( this.options.keyboard ) {
$document.on( 'keydown.slitslider', function(e) {
var keyCode = e.keyCode || e.which,
arrow = {
left: 37,
up: 38,
right: 39,
down: 40
};
switch (keyCode) {
case arrow.left :
self._stopSlideshow();
self._navigate( 'prev' );
break;
case arrow.right :
self._stopSlideshow();
self._navigate( 'next' );
break;
}
} );
}
},
_startSlideshow: function() {
var self = this;
this.slideshow = setTimeout( function() {
self._navigate( 'next' );
if ( self.options.autoplay ) {
self._startSlideshow();
}
}, this.options.interval );
},
_stopSlideshow: function() {
if ( this.options.autoplay ) {
clearTimeout( this.slideshow );
this.isPlaying = false;
this.options.autoplay = false;
}
},
_destroy : function( callback ) {
this.$el.off( '.slitslider' ).removeData( 'slitslider' );
$window.off( '.slitslider' );
$document.off( '.slitslider' );
this.$slides.each( function( i ) {
var $slide = $( this ),
$content = $slide.find( 'div.sl-content' ).children();
$content.appendTo( $slide );
$slide.children( 'div.sl-content-wrapper' ).remove();
} );
this.$slides.unwrap( this.$slideWrapper ).hide();
this.$slides.eq( 0 ).show();
if( callback ) {
callback.call();
}
},
// public methos: adds more slides to the slider
add : function( $slides, callback ) {
this.$slides = this.$slides.add( $slides );
var self = this;
$slides.each( function( i ) {
var $slide = $( this ),
// vertical || horizontal
orientation = $slide.data( 'orientation' );
$slide.hide().addClass( 'sl-slide-' + orientation )
.children()
.wrapAll( '<div class="sl-content-wrapper" />' )
.wrapAll( '<div class="sl-content" />' )
.end()
.appendTo( self.$el.find( 'div.sl-slides-wrapper' ) );
} );
this._setSize();
this.slidesCount = this.$slides.length;
if ( callback ) {
callback.call( $items );
}
},
// public method: shows next slide
next : function() {
this._stopSlideshow();
this._navigate( 'next' );
},
// public method: shows previous slide
previous : function() {
this._stopSlideshow();
this._navigate( 'prev' );
},
// public method: goes to a specific slide
jump : function( pos ) {
pos -= 1;
if( pos === this.current || pos >= this.slidesCount || pos < 0 ) {
return false;
}
this._stopSlideshow();
this._navigate( pos > this.current ? 'next' : 'prev', pos );
},
// public method: starts the slideshow
// any call to next(), previous() or jump() will stop the slideshow
play : function() {
if( !this.isPlaying ) {
this.isPlaying = true;
this._navigate( 'next' );
this.options.autoplay = true;
this._startSlideshow();
}
},
// public method: pauses the slideshow
pause : function() {
if( this.isPlaying ) {
this._stopSlideshow();
}
},
// public method: check if isAnimating is true
isActive : function() {
return this.isAnimating;
},
// publicc methos: destroys the slicebox instance
destroy : function( callback ) {
this._destroy( callback );
}
};
var logError = function( message ) {
if ( window.console ) {
window.console.error( message );
}
};
$.fn.slitslider = function( options ) {
var self = $.data( this, 'slitslider' );
if ( typeof options === 'string' ) {
var args = Array.prototype.slice.call( arguments, 1 );
this.each(function() {
if ( !self ) {
logError( "cannot call methods on slitslider prior to initialization; " +
"attempted to call method '" + options + "'" );
return;
}
if ( !$.isFunction( self[options] ) || options.charAt(0) === "_" ) {
logError( "no such method '" + options + "' for slitslider self" );
return;
}
self[ options ].apply( self, args );
});
}
else {
this.each(function() {
if ( self ) {
self._init();
}
else {
self = $.data( this, 'slitslider', new $.Slitslider( options, this ) );
}
});
}
return self;
};
} )( jQuery, window );
You'd need to change the code in _navigate, where it says:
// if not check the boundaries
else if( dir === 'next' ) {
this.current = this.current < this.slidesCount - 1 ? ++this.current : 0;
}
else if( dir === 'prev' ) {
this.current = this.current > 0 ? --this.current : this.slidesCount - 1;
}
That's the place that specifies which slide will be the next/previous one (checking if it's the first/last slide), creating the "circular effect". Try changing it so if it's one of the limits, the slide remains the same instead of changing to the next/previous one.
Something like this:
// if not check the boundaries
else if( dir === 'next' ) {
this.current = this.current < this.slidesCount - 1 ? ++this.current : this.slidesCount - 1;
}
else if( dir === 'prev' ) {
this.current = this.current > 0 ? --this.current : 0;
}
That will prevent the circular effect in the slides... but it will create a problem with the animation. To solve the problem with the animation:
At the beginning of _navigate add the line this.prevCurrent = this.current;
After changing the value of this.current (code above), wrap the remaining of the function like this:
if (this.prevCurrent != this.current) {
....
} else {
this.isAnimating = false;
}
The arrows will still work, but it will not jump from the last one to the first one (and vice versa). But that's a bit hacky, I would have expected the plug-in to have an option for that.
I haven't actually tested it, but you could extend the plugin to provide that option by saying something like:
$.Slitslider.prototype._startSlideshow = function() {
var self = this;
this.rotationCount = ++this.rotationCount || 1;
if((this.rotationCount !== this.slidesCount && this.options.playOnce) || !this.options.playOnce) {
this.slideshow = setTimeout( function() {
self._navigate( 'next' );
if ( self.options.autoplay ) {
self._startSlideshow();
}
}, this.options.interval );
}
};
If that code doesn't work, you could try it this way (stops the slideshow, which sets autoplay to false):
$.Slitslider.prototype._startSlideshow = function() {
var self = this;
this.rotationCount = ++this.rotationCount || 1;
if(this.rotationCount === this.slidesCount && this.options.playOnce) this._stopSlideshow();
this.slideshow = setTimeout( function() {
self._navigate( 'next' );
if ( self.options.autoplay ) {
self._startSlideshow();
}
}, this.options.interval );
};
And that way, you could add an option into your plugin call and say something like:
$('div').slitslider({
autoplay: true,
playOnce: true
});
This code will allow the autoplay to play through once, and if you have the playOnce option set to true, it will stop after it rotates to the last one.
It seems that I lost myself between cancelAnimationFrame and clearTimeout. Don't know how to stop Snake game. Here is snippet of my code that start animation:
window.addEventListener( "load", function gameLoop() {
globalTimer = setTimeout( function() {
snakeObj.update();
requestAnimationFrame( gameLoop );
}, 100 );
});
in this way I tried to stop my game:
if ( parseInt(this.snakeHead.style.top) == fieldObj.h ||
parseInt(this.snakeHead.style.left) == fieldObj.w ) {
gameObj.stopGame();// stop game message
clearTimeout(globalTimer);
}
but it doesn't work - stop game message appears, but animation continuing.
here all my code:
// field object
var globalTimer;
var fieldObj = {
field: document.getElementById( "field" ),
w: 480,
h: 580
},
gameObj = {
pastCoord: [],
getRandomCoord: function( num ) { // 20 is width of one body segment
return Math.round( Math.floor(( Math.random() * num)) / 20 ) * 20;
},
createSnakeTarget: function() {
var t = document.createElement( "div" );
t.id = "snake-target";
t.style.top = this.getRandomCoord( fieldObj.h ) + "px";
t.style.left = this.getRandomCoord( fieldObj.w ) + "px";
fieldObj.field.appendChild( t );
},
stopGame: function() {
var stopMessage = document.createElement("div");
stopMessage.className = "stop-message";
stopMessage.style.background = "white";
fieldObj.field.appendChild( stopMessage );
//TODO: write message to stopGame
}
};
gameObj.createSnakeTarget();
// snake object
snakeObj = {
snakeHead: document.getElementById("head"),
snakeBody: document.getElementsByClassName( "snake-body" ),// there must be one element
p: {
x: 0, // position x
y: 0 // position y
},
v: {
x: 20, // velocity ( one loop move one unit of snake body)
y: 20
},
keys: {
up: null,
l: null,
r: null,
down: null
},
stepInSnakeBody: 0,// go through snakeBody
stepInPastCoord: 0,// go through pastCoord
addBodySegment: function() {
var seg = document.createElement( "div" );
seg.className = "snake-body";
fieldObj.field.appendChild( seg );
seg.style.top = this.p.x + "px";// receive current position
seg.style.left = this.p.y + "px";
},
update: function() {
var snakeTarget = document.getElementById("snake-target");
if ( this.keys.down ) {
this.p.x += this.v.x;
} else if ( this.keys.up ) {
this.p.x -= this.v.x;
} else if ( this.keys.r ) {
this.p.y += this.v.y;
}else if ( this.keys.l ) {
this.p.y -= this.v.y;
}
this.snakeHead.style.top = this.p.x + "px";
this.snakeHead.style.left = this.p.y + "px";
gameObj.pastCoord.push([this.p.x, this.p.y]);// create and push coord of snake head
//every step index in snakeBody receive coords from pastCoord
this.snakeBody[this.stepInSnakeBody].style.top = gameObj.pastCoord[this.stepInPastCoord][0] + "px";
this.snakeBody[this.stepInSnakeBody].style.left = gameObj.pastCoord[this.stepInPastCoord][1] + "px";
this.stepInSnakeBody++; // increment index every step
this.stepInPastCoord++;
if ( this.stepInSnakeBody === this.snakeBody.length ) {
this.stepInSnakeBody = 0; // when stepInSnakeBody equal length of snake go to zero
//and apply coords
}
// detect collision with target
if ( this.snakeHead.style.top === snakeTarget.style.top &&
this.snakeHead.style.left === snakeTarget.style.left ) {
fieldObj.field.removeChild( snakeTarget );
gameObj.createSnakeTarget();
snakeObj.addBodySegment();
}
if ( parseInt(this.snakeHead.style.top) == fieldObj.h ||
parseInt(this.snakeHead.style.left) == fieldObj.w ) {
gameObj.stopGame();
clearTimeout(globalTimer);
}
}
};
// Crome works only with keydown and keyup
window.addEventListener('keydown', function() {
// before changing direction you have to put previous direction to false
if ( event.keyCode == 38 ) {
snakeObj.keys.up = true;
snakeObj.keys.down = false;
} else if ( event.keyCode == 40 ) {
snakeObj.keys.down = true;
snakeObj.keys.up = false;
} else if ( event.keyCode == 39 ) {
snakeObj.keys.r = true;
snakeObj.keys.up = false;
snakeObj.keys.down = false;
} else if ( event.keyCode == 37 ) {
snakeObj.keys.l = true;
snakeObj.keys.r = false;
snakeObj.keys.up = false;
snakeObj.keys.down = false;
}
}, false);
//TODO: add event hendler to click to some button
window.addEventListener( "load", function gameLoop() {
globalTimer = setTimeout( function() {
snakeObj.update();
requestAnimationFrame( gameLoop );
}, 100 );
});
here is codepen (works in CHROME only ) http://codepen.io/Kuzyo/pen/pamzC
Thanks for the help.
You need to call cancelAnimationFrame.
However you need to make sure you call requestAnimationFrame before update. Currently you're calling stopGame, but then after stopGame/update finishes, requestAnimationFrame schedules another loop so even if your stopGame calls cancelAnimationFrame it will not stop the animation.
An alternative is to use a boolean flag that you check in your gameLoop function.