jquery image hover keeps growing as many times as i hover - javascript

i got this strange behaviour
when i do a slow hover on image everything is working, the image grows and on hover out the image shrinks.
But when i repeat the hover fast the image keeps growing and growing and the position is changing according to the hover speed
Please see fiddle
Jquery
$(document).ready(function () {
var cont_left = $("#container").position().left;
$("a img").hover(function () {
// hover in
$(this).parent().parent().css("z-index", 1);
current_h = $(this, 'img')[0].height;
current_w = $(this, 'img')[0].width;
$(this).stop(true, false).animate({
width: (current_w * 1.3),
height: (current_h * 1.3),
left: "-=50",
top: "-=50"
}, 300);
}, function () {
// hover out
$(this).parent().parent().css("z-index", 0);
$(this).stop(true, false).animate({
width: current_w + 'px',
height: current_h + 'px',
left: "+=50",
top: "+=50"
}, 300);
});
$(".img").each(function (index) {
var left = (index * 160) + cont_left;
$(this).css("left", left + "px");
});
});
Please advise how to i fix the image grow and position.
P.S: every image has a different dimentions

These lines are the key to the problem:
current_h = $(this, 'img')[0].height;
current_w = $(this, 'img')[0].width;
When you .stop the image-growing animation, it doesn't shrink back to its original size (unless you set its second param to true - but you assign false to it explicitly, and I assume you know what you're doing here). So both dimensions are set to the increased value actually.
Solution is simple: always use the original size of the images:
$(document).ready(function () {
var current_h, current_w;
// ...
current_h = current_h || $(this, 'img')[0].height;
current_w = current_w || $(this, 'img')[0].width;
JS Fiddle.
Two sidenotes here. First, there's a similar problem with positioning of these elements: move too fast, and your images will shift to the left-upper or right-lower corners (depending on the phase); that's because, again, animation is done against the current state of things, which is not the same as original when the previous animation is stopped with .stop(true, false).
Second, using $(this, 'img')[0] in this case is essentially the same as just this. Remember, in event handlers this corresponds to the DOM element having this event handler assigned.
So this is how it can be done (demo):
$("a img").hover(function() {
var $this = $(this);
$this.closest('.img').css('z-index', 1);
var orig = $this.data('orig');
if (!orig) { // caching the original sizes via `jQuery.data`
orig = {
width: this.width,
height: this.height
};
$this.data('orig', orig);
}
$this.stop(true, false).animate({
width: orig.width * 1.3,
height: orig.height * 1.3,
left: -(orig.width * 0.3 / 2),
top: -(orig.height * 0.3 / 2)
}, 300);
}, function () {
var $this = $(this),
orig = $this.data('orig');
if (!orig) {
return false;
// actually, it should never be here,
// as calling 'mouseleave' without data precached
// means 'mouseenter' has been never called
}
$this.closest('.img').css('z-index', 0);
$this.stop(true, false).animate({
width: orig.width,
height: orig.height,
left: 0,
top: 0
}, 300);
});

The problem is that when you hover quickly, your values current_h and current_w don't measure the original height and width, but the current height and width. Thus, every time, you're increasing the value.
Solution
I've used a simple .each() function here to set the original height and width of each image as data attributes which can then be accessed when you're setting current_h and current_w.
$('img').each(function(i, el) {
$(el).attr({
"data-original-width": $(this).width(),
"data-original-height": $(this).height()
});
});
current_h = $(this).attr("data-original-height");
current_w = $(this).attr("data-original-width");
WORKING FIDDLE
You don't have to use the each function though. If you know the height and width of the images before rendering, then you can set these as data attributes in your HTML

Related

Centering Element using jQuery Position + Variable

I have the following code below, it was modified from (CSS-Tricks Link). It works fine, however the magic line (floating element under navigation) in my site is 40 pixels wide.
I want to permanently center align the bar (whether it falls under hover state or not), at present it is aligned left of the element. As it is using the jQuery .position() to calculate from the left, all my efforts add the 'forceRight' but negate jQuery .position().
The variable 'forceRight', finds the difference either side of the 40px bar. However I need this side gap to be enforced as the menu items are different widths.
var forceRight,
off_hover_left,
$el,
$calcuateForceDistance,
$magicLine_width = 40,
$mainNav = $("#main-navigation"),
$currentPosition = $(".current-menu-item"),
$currentPosition_width = ($currentPosition.outerWidth() - $magicLine_width);
$mainNav.append("<span id='magic-line'></span>");
var $magicLine = $("#magic-line");
$magicLine
.css("left", $(".current-menu-item").position().left)
.data("origLeft", $magicLine.position().left);
$("#main-navigation a").hover(function(){
// current element
$el = $(this),
// Calcuate Distance
$calcuateForceDistance = Math.floor( ( $el.outerWidth() - $magicLine_width ) / 2 );
forceRight = ( $el.position().left + $calcuateForceDistance );
off_hover_left = Math.floor( $currentPosition.position().left + ($currentPosition_width / 2) );
$magicLine.stop().animate({
left: forceRight
});
}, function() {
// On Hover Out - Return to DOM LOAD (.current-menu-item)
$magicLine.stop().animate({
// not selected outerwidth !!! sort out variables above!
left: off_hover_left
});
});
Many thanks!
I have fixed the bug in my code, I also added a doc ready to get the code to run on browser rendering. When the code loads, I also created a delay then added a class to remove the glitch (the left animation between 0 -> needed axis value.
In this case I used opacity 0, as default. and 1, on .addClass('found').
var forceRight,
$el,
$calcuateForceDistance,
$magicLine_width = 40,
$mainNav = $("#main-navigation"),
$currentPosition = $(".current-menu-item"),
$currentPosition_width = ($currentPosition.outerWidth() - $magicLine_width),
$off_hover_left = Math.floor( $currentPosition.position().left + ($currentPosition_width / 2) );
// Create HTML ELEMENT
$mainNav.append("<span id='magic-line'></span>");
// Target -> Variable
var $magicLine = $("#magic-line");
// Apply attr's
$magicLine.css("left", $(".current-menu-item").position().left);
$(document).ready(function(){
// Enforce Code On Load
$magicLine.stop().animate({
// not selected outerwidth !!! sort out variables above!
left: $off_hover_left
}).promise().done(function(){
// Once Positioned - Then add class (changes opacity) !
$(this).addClass('found');
});
$("#main-navigation a").hover(function(){
// current element
$el = $(this),
// Calcuate Distance
$calcuateForceDistance = Math.floor( ( $el.outerWidth() - $magicLine_width ) / 2 );
forceRight = ( $el.position().left + $calcuateForceDistance );
$magicLine.stop().animate({
left: forceRight
});
}, function() {
// On Hover Out - Return to DOM LOAD (.current-menu-item)
$magicLine.stop().animate({
// not selected outerwidth !!! sort out variables above!
left: $off_hover_left
});
});
});
I hope this helps, someone not just me!
In your javascript you only set the left position of your #magic-line. You need to set the width of the line also. Example in the code underneath (Just the rows to modify):
// On the initialization
$magicLine
.css({
"left": $(".current-menu-item").position().left,
"width" : $(".current-menu-item").outerWidth()
});
// On modification
$magicLine.stop().animate({
left: leftPos,
width: $el.outerWidth()
});
This should solve the problem.

Smoother JQuery width change

I'm trying to animate a menu where the hovered block is getting bigger, while the siblings are offering their space for this size increase. All blocks together are taking up the whole window width.
I successfully accomplished that using some basic Jquery, but the result is a bit clunky.
The rightmost div suffers from all the calculations and rounding :
var width = $(window).width() - 44;
var blockwidth = width/12;
var blockwidthLarge = blockwidth+154;
var blockwidthSmall = blockwidth-14;
$('.headerblock').css('width',blockwidth+'px').hover(function()
{
$(this).siblings().stop(false,false).animate({width: blockwidthSmall},300);
$(this).stop(false,false).animate({width: blockwidthLarge},300);
},function()
{
$(this).siblings().stop(false,false).animate({width: blockwidth},300);
$(this).stop(false,false).animate({width: blockwidth},300);
});
This is a working jsfiddle (I would recommend to resize the preview frame to something larger to increase the effect's visibility):
jsfiddle
How can I improve this to have the boxes appear stable? Maybe this has already been developed?
The website should be IE8+ compatible, so I cannot use fancy css rules.
There's a jQuery plugin called hoverIntent by Brian Cherne that might solve your issue.
Basically, what it does is only firing the hover event when it thinks that the user actually meant to hover. It's a little less fluid than the original, but it works big time and looks fancy.
Here's a fiddle with the plugin added under "External Resources" and the
$('.headerblock').hover(function() {
changed to ".hoverIntent"
http://jsfiddle.net/Matze/c4uyR/1/
var width = $(window).width() - 44;
var blockwidth = Math.round(width / 12);
var blockwidthLarge = blockwidth + 154;
var blockwidthSmall = blockwidth - 14;
$('.headerblock').css('width', blockwidth + 'px').hoverIntent(function ()
{
$(this).siblings().stop(false, false).animate({
width: blockwidthSmall
}, 300);
$(this).stop(false, false).animate({
width: blockwidthLarge
}, 300);
}, function () {
$(this).siblings().stop(false, false).animate({
width: blockwidth
}, 300);
$(this).stop(false, false).animate({
width: blockwidth
}, 300);
});

Jquery animating width accordion style banner

I built this simple accordion style banner. Here's what it's supposed to do:
Grab <li> containing images from selected <ul>.
Divide them equally within the container (div.banner)
On 'mouseenter', add class .active to the hovered <li>
Shrink the other <li>s widths (half their original width).
Enlarge active <li> to new width (remainder after halving the others)
On 'mouseleave', all return to original widths.
Works fine until you swipe over multiple panes quickly. If you do, the last of the floated <li>'s break to the next line. It appears the total width of the panes is exceeding their container.
Rounding error while animating? Does it have something to do with animate's default 'swing' easing?
Fiddle: http://jsfiddle.net/UNFc4/
var banner = $('.banner');
var list_items = banner.find('li');
var banner_width = $(banner).width();
var num_of_images = $(banner).find('li').length;
var original_width = banner_width / num_of_images;
var half_width = (banner_width / num_of_images) / 2;
var init = function () {
$(list_items).css('width', original_width);
$(list_items).on('mouseenter', function () {
$(this).addClass('active');
doAnimation();
});
$(list_items).on('mouseleave', function () {
resetAnimation();
$(this).removeClass('active');
});
}
var doAnimation = function () {
$(list_items).not(".active").stop().animate({
width: half_width + "px"
}, 500);
$(".active").stop().animate({
width: (original_width + (half_width * (num_of_images - 1))) + "px"
}, 500);
}
var resetAnimation = function () {
$(list_items).stop().animate({
width: original_width + "px"
}, 500);
}
init();
I could fix it by changing this line, slowing the animation of the others, giving things time to equal out. But, I'd rather solve what's going on here, hopefully learning a bit more about how jQuery's animate() works.
$(list_items).not(".active").stop().animate({
width: half_width + "px"
}, 480); // changed 500 to 480
For those interested, I realized I only needed the reset on the banner area. Now it works, as described, without all the jitteriness and the subsequent layout mis-alignments.
New Fiddle: http://jsfiddle.net/UNFc4/1/
$(list_items).on('mouseenter', function () {
$(this).addClass('active');
doAnimation();
});
$(list_items).on('mouseleave', function () {
$(this).removeClass('active');
doAnimation();
});
$(banner).on('mouseleave', function () {
resetAnimation();
});

jQuery: animate to height of div

I have this function:
function fixedFeeSize(i){
var num1 = $('#num' + i);
if (num1.hasClass("extended")) {
num1.stop(true, true).animate({height: '59px'},500);
num1.removeClass("extended");
}else{
var height = 0;
num1.animate({height: '360px'},500);
num1.addClass("extended");
}
return null;
}
Which expands / contracts a div, however I am struggling to get it to expand to the height of the div as each div (there will be 10+) is going to be different heights.
I tried num1.animate({height: 'auto'},500); which has not worked.
Is this the effect you're after?
jQuery(".menu a").mouseenter(function(){
jQuery(this).stop().animate({
height: 200, opacity: 0.5
}, 1000, "linear");
});
jQuery(".menu a").mouseleave(function(){
jQuery(this).stop().animate({
height: 18, opacity: 1
}, 1000, "linear");
});
Here's a jsfiddle:
http://jsfiddle.net/kKAZx/1/
For further reading, take a look at the full documentation of the .animate function: http://api.jquery.com/animate/
You can combine effects to make all kinds of fantastic, standard-compliant effects.

How to reset to original values?

It looks like it keeps adding a new newHeight and a newDistance each time i click, I am trying to save original height with a global var at the top and using data to do that but i get weird results, basically i should be able to reset newDistance and newHeight to first original values as per before to run the lot with a click but it doesn't and i get new added values each time i click breaking my layout as a result:
talents = $(".talenti");
filter = $(".filtra");
genHeight = $("#container").data($("#container").height());
filter.click(function(e) {
e.preventDefault();
if (talents.hasClass("opened")) {
$(".nasco").slideToggle();
$("#wrapNav").slideToggle("10", "linear");
talents.removeClass('opened');
filter.addClass('opened');
$("#container").css("height", genHeight);
} else {
filter.addClass('opened');
};
if (filter.hasClass("opened")) {
$("#wrapNav").slideToggle("10", "linear", function(){
$("#sliding-navigation").slideToggle();
var newHeight = $("#container").height() + $("#wrapNav").outerHeight(true);
var newDistance = newHeight - $("#container").height() + 22;
$("#container").animate({height: newHeight}, 50,function(){
$(".box").animate({top: newDistance});
});
});
}
});
talents.click(function(e) {
e.preventDefault();
if (filter.hasClass("opened")) {
$("#sliding-navigation").slideToggle();
$("#wrapNav").slideToggle("10", "linear");
filter.removeClass('opened');
talents.addClass('opened');
$("#container").css("height", genHeight);
} else {
talens.addClass('opened');
};
if (talents.hasClass("opened")) {
$("#wrapNav").slideToggle("10", "linear", function(){
$(".nasco").slideToggle();
var newHeight = $("#container").height() + $("#wrapNav").outerHeight(true);
var newDistance = newHeight - $("#container").height() + 156;
$("#container").animate({height: newHeight}, 50,function(){
$(".box").animate({top: newDistance});
});
});
}
});
Anyone?
So, based on the code I could download about 20min ago from your test site, I managed to get it working with the following code:
$(document).ready(function(){
// placeholder to contain the original height...
var original_height = 0;
talents = $(".talenti");
filter = $(".filtra");
filter.click(function(e){
e.preventDefault();
if (filter.hasClass('opened')){
filter.removeClass('opened');
// toggle the wrapping, just with a zero top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
$("#sliding-navigation").hide();
$(".box").animate({top: '0px'});
});
// reset to the original height...
$("#container").height(original_height);
}
else {
// get the original height if it's not already set...
if (original_height == 0)
original_height = $("#container").height();
filter.addClass('opened');
if (talents.hasClass("opened"))
{
$(".nasco").hide();
$("#wrapNav").slideToggle();
talents.removeClass('opened');
}
// toggle the wrapping with a height of the nav as top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
$("#sliding-navigation").slideToggle(true, function(){
// need the height of the nav before we know how far to move the boxes...
var newHeight = $("#wrapNav").outerHeight(true);
$(".box").animate({top: newHeight});
// set the container's new height, much like you had...
$("#container").height(original_height + newHeight);
});
});
}
});
talents.click(function(e) {
e.preventDefault();
if (talents.hasClass('opened')) {
talents.removeClass('opened');
// toggle the wrapping, just with a zero top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
$(".nasco").hide();
$(".box").animate({top: '0px'});
});
// reset to the original height...
$("#container").height(original_height);
}
else {
// get the original height if it's not already set...
if (original_height == 0)
original_height = $("#container").height();
talents.addClass('opened');
if (filter.hasClass("opened"))
{
$("#sliding-navigation").hide();
$("#wrapNav").slideToggle();
filter.removeClass('opened');
}
// toggle the wrapping with a height of the nav as top coordinate...
$("#wrapNav").slideToggle("10", "linear", function(){
// need the height of the nav before we know how far to move the boxes...
$(".nasco").slideToggle(true, function(){
var newHeight = $("#wrapNav").outerHeight(true);
$(".box").animate({top: newHeight});
// set the container's new height, much like you had...
$("#container").height(original_height + newHeight);
});
});
}
});
});
A few points adding food for thought:
I simplified the multiple if statements to make it easier to understand and process
I used hide() to avoid messy animation problems if you clicked on FILTER multiple times in a row
I only adjusted the top coordinates of the boxes to achieve this
I would have preferred to contain the boxes in a more general container, allowing for easier animation and management, but I understand that wordpress doesn't always give you the most room to work, so this should get you on your way!
It might not be completely what you're looking for in your animation, but it's a working example of the code you had and should get you 90% of the way...hope this helps! :)
What about using the data collection of the container element rather than a global variable i.e. at the top record the height
$("#container").data('height', $("#container").height());
then to use
$("#container").data('height');
i.e. to reset the height
$("#container").css({height: $("#container").data('height') });
I feel a bit suspicious about how the global variable is working. Worth a try maybe

Categories

Resources