In my plugin I handle animations by passing methods in external functions.
Firstly I have to check if the sidebars exists:
var left = cfg.left.selector,
right = cft.right.selector;
function Sidebar( e ) {
if ( e == undefined ) {
console.log( "WARNING: A (or more) sidebar(s) has been left behind" );
} else {
var align, sbw, marginA, marginB, $sbRight, $sbLeft,
$sidebar = $( e );
Once I'm sure that the sidebars (or maybe just one ) exists I define the variables I need to animate all elements:
switch ( e == cfg.right.selector ) {
case true:
align = "right";
marginA = "margin-right";
marginB = "margin-left";
$sbRight = $( cfg.right.selector );
break;
case false:
align = "left";
marginA = "margin-left";
marginB = "margin-right";
$sbLeft = $( cfg.left.selector );
break;
}
var def = cfg[align], //new path to options
$opener = $( def.opener ),
sbMaxW = def.width,
gap = def.gap,
winMaxW = sbMaxW + gap,
$elements = //a very long selector,
w = $( window ).width();
//defining sbw variable
if ( w < winMaxW ) {
sbw = w - gap;
} else {
sbw = sbMaxW;
}
//setting $sidebar initial position and style
var initialPosition = {
width: sbw,
zIndex: cfg.zIndex
};
initialPosition[marginA] = -sbw;
$sidebar.css( initialPosition );
And here are the animations. They are handled as external functions. The first animation works really good. It does its job:
//Animate $elements to open the $sidebar
var animateStart = function() {
var ssbw = $sidebar.width();
animation = {};
animation[marginA] = '+=' + ssbw;
animation[marginB] = '-=' + ssbw;
$elements.each(function() {
$( this ).animate( animation, {
duration: duration,
easing: easing,
complete: function() {
$( 'body, html' ).attr( 'data-' + dataName, 'overflow' );
maskDiv.fadeIn();
}
});
});
},
but the second one is doubled when two sidebars exist!! I need to retrieve the .wrapper offset and then move $elements according to its value. So I thought that it works as in the first animation function, and that it would be as simple as there:
animationReset = function() {
var offset = $wrapper.offset().left;
reset = {};
console.log( offset );
Console returns two time the value so the animation in doubled.
reset[marginA] = '-=' + offset;
reset[marginB] = '+=' + offset;
$elements.each(function() {
$( this ).animate( reset, {
duration: duration,
easing: easing,
complete: function() {
maskDiv.fadeOut(function() {
$( 'body, html' ).removeAttr( 'data-' + dataName );
});
}
});
});
};
So now I run the animations on click functions.
$( $opener ).click( animateStart );
maskDiv.click( animationReset );
}
}
And finally I pass the values running the main function two times.
Sidebar( left );
Sidebar( right );
Why it worked on the first animation and then it is doubled in the second animation?
DEMO: http://dcdeiv.github.io/amazing-sidebar/
FULL CODE: https://github.com/dcdeiv/amazing-sidebar/blob/master/development/jquery.amazingsidebar.js
I think the problem is that you are adding two times the click event at
maskDiv.click( animationReset );
since maskDiv is the main div of the website and there is only one, but the looping is doing it twice.
Try to take that out of the loops definitions and do it only once, but maybe it is difficult because it is defined inside the Sidebar function, so you can try to create a global variable at the top and, after the first click event is added, not add any more:
At the top:
var notAdded = true;
In the sidebar function:
$( $opener ).click( animateStart );
if(notAdded){
maskDiv.click( animationReset );
notAdded = false;
}
Hope it helps!
Here is the cause :
var maskDiv = $( 'body' ).children().filter(function(){
return $( this ).data( dataName ) === 'mask' ;
});
and
maskDiv.click( animationReset );
You have two data-named 'mask' elements on the body.
So the click action is setup on both when you call the Sidebar function.
As you call it twice, you got the reset action run twice ...
EDIT : I was wrong : you have only one mask, but as it is used by both sidebars, you still have a double setup on it.
Related
I have check some others post, and document myself but I dont know what is the problem here.
I have 2 image (would like to have like 20 at the end) where you can click on an icon and show and hide and image in the webpage. If you click on image A it should show image A, if you click on image B image A should hide and image B should be sown.
var firsttime = 1;
var $lastletter;
$(function() {
$('#A').click(function() {
if (firsttime = 0){
$lastletter.toggle();
$('#AL').toggle();
$lastletter = $( '#AL' );
}
else
{
firsttime = 0;
$('#AL').toggle();
$lastletter = $( '#AL' );
}
});
});
$(function() {
$('#B').click(function() {
if (firsttime = 0){
$lastletter.toggle();
$('#BL').toggle();
$lastletter = $( '#BL' );
}
else
{
firsttime = 0;
$('#BL').toggle();
$lastletter = $( '#BL' );
}
});
});
This is the solution im using:
$(function() {
$('.imgLetter').click(function() {
if (lastletter != this.id) {
$('#' + lastletter + 'L').toggle();
lastletter=this.id;
}
$('#' + this.id + 'L').toggle();
});
});
Assuming you're conventionally assigning the "last letter" by appending an "L" to the ID: this could get a lot simpler. Decorate all of your #<x> elements with a class name that makes it easy to select all of them at once. I'm going to choose "letter".
I don't think you even need to track the "first time". It sounds like you just want one element to toggle another. That would look like:
$(function() {
$('.letter').click(function() {
$('#' + this.id + 'L').toggle();
});
});
So I have the following code I have written to build a carousel in JavaScript using Hammer.js and jQuery:
var hCarousel = {
container: false,
panes: false,
pane_width: 0,
pane_count: 0,
current_pane: 0,
build: function( element ) {
hCarousel.container = $(element).find('.hcarousel-inner-container');
hCarousel.panes = $(hCarousel.container).find('> .section');
hCarousel.pane_width = 0;
hCarousel.pane_count = hCarousel.panes.length;
hCarousel.current_pane = 0;
hCarousel.setPaneDimensions( element );
$(window).on('load resize orientationchange', function() {
hCarousel.setPaneDimensions( element );
});
$(element).hammer({ drag_lock_to_axis: true })
.on('release dragleft dragright swipeleft swiperight', hCarousel.handleHammer);
},
setPaneDimensions: function( element ){
hCarousel.pane_width = $(element).width();
hCarousel.panes.each(function() {
$(this).width(hCarousel.pane_width);
});
hCarousel.container.width(hCarousel.pane_width*hCarousel.pane_count);
},
next: function() {
return hCarousel.showPane(hCarousel.current_pane+1, true);
},
prev: function() {
return hCarousel.showPane(hCarousel.current_pane-1, true);
},
showPane: function( index ) {
// between the bounds
index = Math.max(0, Math.min(index, hCarousel.pane_count-1));
hCarousel.current_pane = index;
var offset = -((100/hCarousel.pane_count)*hCarousel.current_pane);
hCarousel.setContainerOffset(offset, true);
},
setContainerOffset: function( percent, animate ) {
hCarousel.container.removeClass("animate");
if(animate) {
hCarousel.container.addClass("animate");
}
if(Modernizr.csstransforms3d) {
hCarousel.container.css("transform", "translate3d("+ percent +"%,0,0) scale3d(1,1,1)");
}
else if(Modernizr.csstransforms) {
hCarousel.container.css("transform", "translate("+ percent +"%,0)");
}
else {
var px = ((hCarousel.pane_width*hCarousel.pane_count) / 100) * percent;
hCarousel.container.css("left", px+"px");
}
},
handleHammer: function( ev ) {
ev.gesture.preventDefault();
switch(ev.type) {
case 'dragright':
case 'dragleft':
// stick to the finger
var pane_offset = -(100/hCarousel.pane_count)*hCarousel.current_pane;
var drag_offset = ((100/hCarousel.pane_width)*ev.gesture.deltaX) / hCarousel.pane_count;
// slow down at the first and last pane
if((hCarousel.current_pane == 0 && ev.gesture.direction == Hammer.DIRECTION_RIGHT) ||
(hCarousel.current_pane == hCarousel.pane_count-1 && ev.gesture.direction == Hammer.DIRECTION_LEFT)) {
drag_offset *= .4;
}
hCarousel.setContainerOffset(drag_offset + pane_offset);
break;
case 'swipeleft':
hCarousel.next();
ev.gesture.stopDetect();
break;
case 'swiperight':
hCarousel.prev();
ev.gesture.stopDetect();
break;
case 'release':
// more then 50% moved, navigate
if(Math.abs(ev.gesture.deltaX) > hCarousel.pane_width/2) {
if(ev.gesture.direction == 'right') {
hCarousel.prev();
} else {
hCarousel.next();
}
}
else {
hCarousel.showPane(hCarousel.current_pane, true);
}
break;
}
}
}
And I call this like:
var hSections;
$(document).ready(function(){
hSections = hCarousel.build('.hcarousel-container');
});
Which works fine. But I want to make it so that I can have multiple carousels on the page which again works... but the overall width of the container is incorrect because it's combining the width of both carousels.
How can I run multiple instances of something like this, but the code know WHICH instance it's interacting with so things don't become mixed up, etc.
The problem is your design is not really suited to multiple instances, because of the object literal which has properties of the carousel, but also the build method.
If I was starting this from scratch, I would prefer a more OOP design, with a carousel class that can you instantiate, or have it as a jQuery plugin. That said, it's not impossible to adapt your existing code.
function hCarousel(selector){
function hCarouselInstance(element){
var hCarousel = {
// insert whole hCarousel object code
container: false,
panes: false,
build : function( element ){
...
};
this.hCarousel = hCarousel;
hCarousel.build(element);
}
var instances = [];
$(selector).each(function(){
instances.push(new hCarouselInstance(this));
});
return instances;
}
Usage
For example, all elements with the hcarousel-container class will become an independant carousel.
$(document).ready(function(){
var instances = hCarousel('.hcarousel-container');
});
Explanation:
The hCarousel function is called passing the selector, which can match multiple elements. It could also be called multiple times if needed.
The inner hCarouselInstance is to be used like a class, and instantiated using the new keyword. When hCarousel is called, it iterates over the matched elements and creates a new instance of hCarouselInstance.
Now, hCarouselInstance is a self contained function that houses your original hCarousel object, and after creating the object it calls hCarousel.build().
The instances return value is an array containing each instance object. You can access the hCarousel properties and methods from there, such as:
instances[0].hCarousel.panes;
jQuery plugin
Below is a conversion to a jQuery plugin, which will work for multiple carousels.
(function ( $ ) {
$.fn.hCarousel = function( ) {
return this.each(function( ) {
var hCarousel = {
// insert whole hCarousel object code here - same as in the question
};
hCarousel.build(this);
});
};
}( jQuery ));
Plugin usage:
$('.hcarousel-container').hCarousel();
I would try turning it into a function which you can use like a class. Then you can create separate objects for your carousels.
So you would have something like the following:
function HCarousel (element) {
this.element=element;
this.container= false;
this.panes= false;
this.pane_width= 0;
this.pane_count= 0;
this.current_pane= 0;
}
And then add each method on the class like this.
HCarousel.prototype.build = function() {
this.container = $(element).find('.hcarousel-inner-container');
this.panes = $(hCarousel.container).find('> .section');
this.pane_width = 0;
this.pane_count = hCarousel.panes.length;
this.current_pane = 0;
this.setPaneDimensions( element );
$(window).on('load resize orientationchange', function() {
this.setPaneDimensions( element );
});
$(this.element).hammer({ drag_lock_to_axis: true }).on('release dragleft dragright swipeleft swiperight', hCarousel.handleHammer);
};
etc. That should give you the basic idea. Will take a little bit of re-writing, but then you can create a carousel with something like this:
var carousel1 = new HCarousel('.hcarousel-container');
Hope that puts you on the right track.
Classes don't actually exist in JS, but this is a way to simulate one using a function. Here's a good article on using classes in JS http://www.phpied.com/3-ways-to-define-a-javascript-class/
I'm using a jquery-ui accordion that opens a section on mouse hover instead of on click, however I've noticed if you mouse over multiple items quickly, the item that gets selected is the first item your mouse was over, not the last one.
You can test this out on either their demo page or this copy of the demo on jsfiddle: Simply mouse over the last item so it expands, then move your mouse quickly to the first item, passing the 3nd and 2rd item as you go. The end result is the 3nd item is open, although your mouse is over the first item. (You can also do it in the reverse, but its easiest to duplicate the problem going from bottom to top)
How can I prevent this behavior from happening so the final item that is open is the one the mouse is over, and not the first item the mouse went over?
jQuery UI has implemented the hoverIntent functionality for their accordion selections to combat animation queue issues. The snippet they used is as follows ->
//on DOM ready
$(function() {
$("#accordion").accordion({
event: "click hoverintent"
);
});
var cfg = ($.hoverintent = {
sensitivity: 7,
interval: 100
});
$.event.special.hoverintent = {
setup: function() {
$( this ).bind( "mouseover", jQuery.event.special.hoverintent.handler );
},
teardown: function() {
$( this ).unbind( "mouseover", jQuery.event.special.hoverintent.handler );
},
handler: function( event ) {
var self = this,
args = arguments,
target = $( event.target ),
cX, cY, pX, pY;
function track( event ) {
cX = event.pageX;
cY = event.pageY;
};
pX = event.pageX;
pY = event.pageY;
function clear() {
target
.unbind( "mousemove", track )
.unbind( "mouseout", arguments.callee );
clearTimeout( timeout );
}
function handler() {
if ( ( Math.abs( pX - cX ) + Math.abs( pY - cY ) ) < cfg.sensitivity ) {
clear();
event.type = "hoverintent";
// prevent accessing the original event since the new event
// is fired asynchronously and the old event is no longer
// usable (#6028)
event.originalEvent = {};
jQuery.event.handle.apply( self, args );
} else {
pX = cX;
pY = cY;
timeout = setTimeout( handler, cfg.interval );
}
}
var timeout = setTimeout( handler, cfg.interval );
target.mousemove( track ).mouseout( clear );
return true;
}
};
I'm fairly new to jQuery and I'm using the script below. Basically it uses two unordered lists to create tab functionality (one for tabs, one for content). Right now when you click through the tabs, the output is switched from "display:list-item;" to "display:none;". I'm trying to change this to "position:absolute;left:-10000px;" and "position:relative;left:0;" so that all the content gets rendered but just moves off the page rather than be hidden.
I'm having the issue you see at the bottom of the page here http://jqueryui.com/demos/tabs/ except it's not being controlled in the CSS. It's being controlled in the script below somehow that I'm unfamiliar with. Any help would be appreciated.
//INITIALIZATION
$.featureList(
$(".tabs li a"),
$(".output > li"), {
start_item : 0
}
);
//SCRIPT
(function($) {
$.fn.featureList = function(options) {
var tabs = $(this);
var output = $(options.output);
new jQuery.featureList(tabs, output, options);
return this;
};
$.featureList = function(tabs, output, options) {
function slide(nr) {
if (typeof nr == "undefined") {
nr = visible_item + 1;
nr = nr >= total_items ? 0 : nr;
}
tabs.removeClass('current').filter(":eq(" + nr + ")").addClass('current');
output.stop(true, true).filter(":visible").fadeOut();
output.filter(":eq(" + nr + ")").fadeIn(function() {
visible_item = nr;
});
}
var options = options || {};
var total_items = tabs.length;
var visible_item = options.start_item || 0;
options.pause_on_hover = options.pause_on_hover || true;
options.transition_interval = options.transition_interval || 0;
output.hide().eq( visible_item ).show();
tabs.eq( visible_item ).addClass('current');
tabs.click(function() {
if ($(this).hasClass('current')) {
return false;
}
slide( tabs.index( this) );
});
if (options.transition_interval > 0) {
var timer = setInterval(function () {
slide();
}, options.transition_interval);
if (options.pause_on_hover) {
tabs.mouseenter(function() {
clearInterval( timer );
}).mouseleave(function() {
clearInterval( timer );
timer = setInterval(function () {
slide();
}, options.transition_interval);
});
}
}
};
})(jQuery);
The action in that script is happening with .FadeIn and .Fadeout, which animate opacity. Fadeout applies display:none at the end of the opacity animation. Correspondingly, FadeIn only works on elements that are set to display:none. Fadein just won't work on visibility: hidden or opacity:0. Check out the jquery documentation, it's mostly pretty good.
So you want to substitute a css position change for those two lines of code. There are a bunch of different ways to do this, depending mostly on whether or not you want the elements to animate off the page of just leap there.
Also FYI The easiest way to share this sort of stuff for troubleshooting is to make a jsfiddle with a reduced subset of your code, just the relevant stuff, and then everybody can poke away at it until it works. :)
I have a script on http://joelglovier.com to add a class of "active" to each navigation element when it's corresponding section in the document. The script is adapted with permission from w3fools.com, so it was written without my scenario in mind.
The difference is that on w3fools.com, the nav links only refer to elements on the page, whereas in my navigation, there is one element at the end that refers to a new page.
The problem is that as I have adapted it, it works fine for the links that refer to sections on the page. However, for some reason unbeknownst to me (sorry - JS/jQuery novice) it is blocking the browser from following the link to the new page (the Blog link).
I tried reading through the code and understanding what the script is doing - however I cannot seem to understand why it is blocking that external link from being clicked, or more specifically how to fix it.
Can anybody suggest the simplest way to remedy my issue without breaking the original functionality of the script for it's purpose?
See it live here: http://joelglovier.com
Or...
Markup:
<div id="site-nav">
<div class="wrap">
<nav id="nav-links">
Top
Background
Projects
Random
Credits
Blog <span class="icon"></span>
</nav>
</div>
Javascript:
(function($) {
$(function() {
var $nav = $('#nav-links'),
$navLinks = $nav.find('a.scroll'),
cache = {};
$docEl = $( document.documentElement ),
$body = $( document.body ),
$window = $( window ),
$scrollable = $body; // default scrollable thingy, which'll be body or docEl (html)
// find out what the hell to scroll ( html or body )
// its like we can already tell - spooky
if ( $docEl.scrollTop() ) {
$scrollable = $docEl;
} else {
var bodyST = $body.scrollTop();
// if scrolling the body doesn't do anything
if ( $body.scrollTop( bodyST + 1 ).scrollTop() == bodyST) {
$scrollable = $docEl;
} else {
// we actually scrolled, so, er, undo it
$body.scrollTop( bodyST - 1 );
}
}
// build cache
$navLinks.each(function(i,v) {
var href = $( this ).attr( 'href' ),
$target = $( href );
if ( $target.length ) {
cache[ this.href ] = { link: $(v), target: $target };
}
});
// handle nav links
$nav.delegate( 'a', 'click', function(e) {
// alert( $scrollable.scrollTop() );
e.preventDefault(); // if you expected return false, *sigh*
if ( cache[ this.href ] && cache[ this.href ].target ) {
$scrollable.animate( { scrollTop: cache[ this.href ].target.position().top }, 600, 'swing' );
}
});
// auto highlight nav links depending on doc position
var deferred = false,
timeout = false, // so gonna clear this later, you have NO idea
last = false, // makes sure the previous link gets un-activated
check = function() {
var scroll = $scrollable.scrollTop(),
height = $body.height(),
tolerance = $window.height() * ( scroll / height );
$.each( cache, function( i, v ) {
// if we're past the link's section, activate it
if ( scroll + tolerance > v.target.position().top ) {
last && last.removeClass('active');
last = v.link.addClass('active');
} else {
v.link.removeClass('active');
return false; // get outta this $.each
}
});
// all done
clearTimeout( timeout );
deferred = false;
};
// work on scroll, but debounced
var $document = $(document).scroll( function() {
// timeout hasn't been created yet
if ( !deferred ) {
timeout = setTimeout( check , 250 ); // defer this stuff
deferred = true;
}
});
// fix any possible failed scroll events and fix the nav automatically
(function() {
$document.scroll();
setTimeout( arguments.callee, 1500 );
})();
});
})(jQuery);
You're trying to tell it to scroll to "http://..." which doesn't exist on the current page, so it fails and does nothing.
It should work if you change your code to this
// handle nav links
$nav.delegate( 'a', 'click', function(e) {
// alert( $scrollable.scrollTop() );
e.preventDefault(); // if you expected return false, *sigh*
if ( cache[ this.href ] && cache[ this.href ].target ) {
// preserve http:// links
if (this.href.substr(0, "http://".length) == 'http://')
location.href = this.href;
else
$scrollable.animate( { scrollTop: cache[ this.href ].target.position().top }, 600, 'swing' );
}
});