PDA

View Full Version : Getting jQuery slideUp() event to behave



FrickenTrevor
10-19-2012, 12:52 AM
Hey thanks for looking at my question :)

I implemented a toggle on my site that slides a div up and down from visibility. The code has been rewritten to work with my menu, but now it doesn't work. Any solutions to this?

The goal is to have the background color on the div menu slide up into view upon mouse over, and slide down away from view on mouse out

Heres the code, put into a jsFiddle (http://jsfiddle.net/jpfP3/)

jscheuer1
10-19-2012, 02:02 AM
There could also be other problems. But here:



var container = $( "#menu-hover" );

There's no element in the markup with an id of 'menu-hover', so var container is an empty object. It can't slideUp or slideDown and will probably always return false for is(':visible').

I'd be interested in seeing the code when it did work. Do you have a copy of that?

Anyways, this sort of thing is much easier to diagnose if you have an actual page though. So:

If you want more help, please include a link to the page on your site that contains the problematic code so we can check it out.

keyboard
10-19-2012, 02:06 AM
Also, correct me if I'm wrong, but you're not closing the function jQuery();

jscheuer1
10-19-2012, 02:20 AM
Also, correct me if I'm wrong, but you're not closing the function jQuery();

Yes, that appears to also be a correct observation.

And it just occurred to me that, if something isn't visible, you cannot hover it. And that the hover function in jQuery is for pairing two functions (mouseenter and mouseleave) not for a single function with an if/else branch in it as you have. I have seen it used that way though, but results can be unexpected.

FrickenTrevor
10-19-2012, 04:45 AM
4811
I attached the original source code. Also I am not trying to work it out for a menu, not two divs. So like a CSS menu with a hover, except when you hover over the menu the background color slides up into view from the bottom, instead of just appearing there. I like the animation of the .slideToggle() method, just hate having to call two divs for it.

jscheuer1
10-19-2012, 09:36 AM
Still playing with it maybe:


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title> - jsFiddle demo</title>
<script type='text/javascript' src='http://code.jquery.com/jquery-1.8.2.js'></script>
<style type='text/css'>
#menu a {
border-bottom: solid #4E9BDA 4px;
padding: 10px 25px 10px 25px;
margin: 2px;
display: block;
background: #465153;
color: #8DBFE7;
float: right;
text-decoration: none;
font-family:Georgia, "Times New Roman", Times, serif;
font-size:12px;
}
#menu a:hover {
color: #eee;
border-bottom: solid #000 4px;
}
</style>
<script type='text/javascript'>//<![CDATA[
// When the DOM is ready, initialize the scripts.
jQuery(function( $ ){
var $b = $('body');
$('#menu a').each(function(){
var $t = $(this), o = $t.offset(), $c = $('<div></div><div></div>').prependTo($b);
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight(), backgroundColor: $t.css('backgroundColor')})
.eq(0).css({backgroundColor: $t.css('borderBottomColor')});
$t.css({position: 'relative', backgroundColor: 'transparent'}).data('hover', $c.eq(1));
}).hover(function(){
$(this).data('hover').animate({height: 0}, {duration: 900, queue: false});
}, function(){
var $t = $(this);
$t.data('hover').animate({height: $t.outerHeight()}, {duration: 900, queue: false});
});
});
//]]>
</script>
</head>
<body>
<div id="menu">
<a href="index.html">Home</a>
<a href="about.html">About</a>
<a href="projects.html">Projects</a>
</div>
</body>
</html>

Questions or Comments?

OK, I did play around with it and using the jQuery UI we can animate the colors for smoother transitions for them:


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title> - jsFiddle demo</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<style type='text/css'>
#menu a {
border-bottom: solid #4E9BDA 4px;
padding: 10px 25px 10px 25px;
margin: 2px;
display: block;
background: #465153;
color: #8DBFE7;
float: right;
text-decoration: none;
font-family:Georgia, "Times New Roman", Times, serif;
font-size:12px;
}
#menu a:hover {
border-bottom: solid #000000 4px;
}
</style>
<script>
// When the DOM is ready, initialize the scripts.
jQuery(function($){
var duration = 900, $b = $('body');
$('#menu a').each(function(){
var $t = $(this), o = $t.offset(), $c = $('<div></div><div></div>').prependTo($b);
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight(), backgroundColor: $t.css('backgroundColor')})
.eq(0).css({backgroundColor: $t.css('color')});
$t.css({position: 'relative', backgroundColor: 'transparent'}).data({hover: $c.eq(1), color: $t.css('color'), bbcolor: $t.css('borderBottomColor')});
}).hover(function(){
$(this).animate({color: '#000000'}, {duration: duration, queue: false}).data('hover').animate({height: 0}, {duration: duration, queue: false});
}, function(){
var $t = $(this), color = $t.data('color'), bbcolor = $t.data('bbcolor');
$t.css({borderBottomColor: '#000000'}).animate({color: color, borderBottomColor: bbcolor}, {duration: duration, queue: false, complete: function(){
$t.css({borderBottomColor: ''});}})
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
});
});
</script>
</head>
<body>
<div id="menu">
<a href="index.html">Home</a>
<a href="about.html">About</a>
<a href="projects.html">Projects</a>
</div>
</body>
</html>

Note: IE 8 and less require UI version 8x. 9x is available but will break this in IE 8 and less.

FrickenTrevor
10-19-2012, 04:53 PM
Thanks for the code, but is there any way to have only jQuery (no jQuery UI) and simplify the code? It seem pretty heavy just to have a hover transition, in my opinion.

jscheuer1
10-19-2012, 05:04 PM
I don't think so, not that specific sort of transition. You could fade the text transition instead of use color animation, but that would actually be more on page code and more created elements, it would eliminate the the need for the UI but gets sloppy looking in IE 8 and less.

You can't reasonably expect to take a simple effect for a single appear disappear action and apply it to a transformation of several elements. If you wanted the menu items to simply appear and disappear when you clicked something else, like in the original demo, then yes, the code could be simple.

If there's anything you don't understand about the code, when I have more time I'd be happy to explain it.

FrickenTrevor
10-19-2012, 06:52 PM
I put the new code on my page, but about every 5 page refreshes or so random blue boxes will appear then disappear on the next page load. I am inclined to put my website here, but I can show any code samples if needed

jscheuer1
10-20-2012, 01:48 AM
How many people refresh a page 5 times?

Anyways, whether you're inclined or not (your post says you are, but I think you meant that you're not) to put a link to the page here. I would have to see the problem to fix it. So you can give me any code you like or perhaps PM me a link to the site. Whatever you do it has to show the problem. So don't send me any code unless you can get it to show the problem for you. Whether you do that or PM me a link to the page, I would still need to know the browser and OS used to see the problem, as well as step by step what I must do to duplicate the problem.

I've tried out my demo in IE 9 (in IE 9, 8, and 7 modes), a real IE 8 and 6, Chrome, Firefox, and Opera, refreshing many times in each with no problems. These are all under Win 7 except for the real IE 8 and 6, which were under XP. If it's not one of those giving you the problem, I might not be able to test it. But I'll see what I can arrange.

I did however update it a few times. Make sure you're using the latest:


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title> - jsFiddle demo</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<style type='text/css'>
#menu a {
border-bottom: solid #4E9BDA 4px;
padding: 10px 25px 10px 25px;
margin: 2px;
display: block;
background: #465153;
color: #8DBFE7;
float: right;
text-decoration: none;
font-family:Georgia, "Times New Roman", Times, serif;
font-size:12px;
}
#menu a:hover {
border-bottom: solid #000000 4px;
}
</style>
<script>
// When the DOM is ready, initialize the scripts.
jQuery(function($){
var duration = 900, $b = $('body');
$('#menu a').each(function(){
var $t = $(this), o = $t.offset(), $c = $('<div></div><div></div>').prependTo($b);
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight(), backgroundColor: $t.css('backgroundColor')})
.eq(0).css({backgroundColor: $t.css('color')});
$t.css({position: 'relative', backgroundColor: 'transparent'}).data({hover: $c.eq(1), color: $t.css('color'), bbcolor: $t.css('borderBottomColor')});
}).hover(function(){
$(this).animate({color: '#000000'}, {duration: duration, queue: false}).data('hover').animate({height: 0}, {duration: duration, queue: false});
}, function(){
var $t = $(this), color = $t.data('color'), bbcolor = $t.data('bbcolor');
$t.css({borderBottomColor: '#000000'}).animate({color: color, borderBottomColor: bbcolor}, {duration: duration, queue: false, complete: function(){
$t.css({borderBottomColor: ''});}})
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
});
});
</script>
</head>
<body>
<div id="menu">
<a href="index.html">Home</a>
<a href="about.html">About</a>
<a href="projects.html">Projects</a>
</div>
</body>
</html>

Oh and, things might be able to be simplified a bit. But the code really is pretty simple now. All it does is create two new divs for each menu item positioning them absolutely behind the item. The one in the mid ground is the background color that the menu item had, while the item itself has its background changed to transparent. The one in the full background is the color you want transitioned to. It is revealed when the mid ground one is retracted on hover of the item. At the same time the foreground color is animated to keep pace. The border bottom color is coordinated in this as well. Everything is taken from the existing markup and styles except for the new foreground color (#000000), which is also used for the border bottom color on hover. It looks a little more complicated than just that because to get it to react smoothly and to behave well if someone only partially hovers and then moves the mouse elsewhere, the order and queuing of the animations is a little tricky.

FrickenTrevor
10-20-2012, 03:55 AM
Well I PM'd you the link to my site, also its not a matter of refreshing the page 5 times. Sometimes the blue boxes show up, other times they don't. I have been testing my site out in Chrome 20 in Windows XP and 7

jscheuer1
10-20-2012, 04:58 AM
OK, I saw the problem right away and it's probably worse for me because I'm farther away from the server. Happens almost all the time once the hosted scripts are cached. What happens is that the menu items are initialized to the script before the browser places them in their actual locations. As a result the two generated divs for each menu item that I mentioned in my last post are put in the wrong place.

I found two solutions. I favor this one - Move the linked stylesheet and favicon to their proper location in the head of the page, which is before scripts, favicon first. In other words, move the highlighted from here:


. . .e: false, complete: function(){
$t.css({borderBottomColor: ''});}})
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
});
});
</script>
<link rel="stylesheet" href="style.css" media="screen" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
</head>
<body>
<div class="Page-BgGra

to here and switch their order as shown:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Trevor Reece</title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="style.css" media="screen" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/ui/1.8.24/jquery-ui.min.js"></script>
<script type="text/javascript">
//<![CDATA[
function navigate_tabs(cont . . .

This allows the menu items to assume their actual locations on the page before the script acts upon them.

Make sure to clear the browser cache and refresh the page to see results. After that, you should be able to do just about anything you want with the page and there should be no problem with this script.

The other solution is to initialize on load instead of on document ready (changes highlighted):


<script>
// When the window is loaded, initialize the scripts.
jQuery(window).load(function($){
$ = jQuery;
var duration = 900, $b = $('body');
$('#menu a').each(function(){
var $t = $(this), o = $t.offset(), $c = $('<div></div><div></div>').prependTo($b);
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight(), backgroundColor: $t.css('backgroundColor')})
.eq(0).css({backgroundColor: $t.css('color')});
$t.css({position: 'relative', backgroundColor: 'transparent'}).data({hover: $c.eq(1), color: $t.css('color'), bbcolor: $t.css('borderBottomColor')});
}).hover(function(){
$(this).animate({color: '#000000'}, {duration: duration, queue: false}).data('hover').animate({height: 0}, {duration: duration, queue: false});
}, function(){
var $t = $(this), color = $t.data('color'), bbcolor = $t.data('bbcolor');
$t.css({borderBottomColor: '#000000'}).animate({color: color, borderBottomColor: bbcolor}, {duration: duration, queue: false, complete: function(){
$t.css({borderBottomColor: ''});}})
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
});
});
</script>

I do not recommend this approach because it unnecessarily delays the initialization. But I'm offering it here anyway because if the other solution doesn't seem to work out for you, this one almost certainly will. And because there might be some reason why you have those link tags (stylesheet and favicon) in the wrong place. If so, it's probably not a good reason and could probably be dealt with in another way, if in fact they're in the wrong place to deal with some other issue. I'm not here to tell you what to do with those link tags, only to recommend that they be placed in their proper locations.

BTW, since you're using an XHTML DOCTYPE, you can use the type attribute for the script tag and the not character data delimiters if you like for validation purposes (additions highlighted):


<script type="text/javascript">
/* <![CDATA[ */
// When the DOM is ready, initialize the scripts.
jQuery(function($){
var duration = 500, $b = $('body');
$('#menu a').each(function(){
var $t = $(this), o = $t.offset(), $c = $('<div></div><div></div>').prependTo($b);
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight(), backgroundColor: $t.css('backgroundColor')})
.eq(0).css({backgroundColor: $t.css('color')});
$t.css({position: 'relative', backgroundColor: 'transparent'}).data({hover: $c.eq(1), color: $t.css('color'), bbcolor: $t.css('borderBottomColor')});
}).hover(function(){
$(this).animate({color: '#000000'}, {duration: duration, queue: false}).data('hover').animate({height: 0}, {duration: duration, queue: false});
}, function(){
var $t = $(this), color = $t.data('color'), bbcolor = $t.data('bbcolor');
$t.css({borderBottomColor: '#000000'}).animate({color: color, borderBottomColor: bbcolor}, {duration: duration, queue: false, complete: function(){
$t.css({borderBottomColor: ''});}})
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
});
});
/* ]]> */
</script>

Or you could make the script external.

Also notice that I changed the duration to 500 (red in the above). It was 900. I think that's too slow for this effect. 500 looks good to me.

FrickenTrevor
10-20-2012, 06:35 PM
Wow thanks alot for your help! I also noticed you put quite a bit of the CSS inside the JS, and that if you wanted to change the CSS around you would also have to fiddle around with the JS. Is there anyway to make the JS read the properties of the CSS so you wouldn't have to mess with the JS at all?



Or you could make the script external.

Yes I ended up making it external as well

jscheuer1
10-20-2012, 09:05 PM
The only css in the script, other than that required by the created divs is the color that the text and bottom border use on hover. For the rest, it takes the dimensions and colors of the existing menu items. These are required for the the created divs though.

Here's an annotated version of the code:


// When the DOM is ready, initialize the menu items.
jQuery(function($){
var duration = 500, $b = $('body'); // set duration of effect, get a reference to the body
$('#menu a').each(function(){ // setup for each menu item (runs once:
var $t = $(this), o = $t.offset(), $c = $('<div></div><div></div>').prependTo($b); //get dimenssions make two divs for the effect
// position these two divs absolutely so they're behind the menu item and the same size and background color:
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight(), backgroundColor: $t.css('backgroundColor')})
.eq(0).css({backgroundColor: $t.css('color')}); // set the one on the bottom to be the hover bg color taken from the fg color of the menu item
// make the menu item position relative so it will stack on top of the two created divs:
$t.css({position: 'relative', backgroundColor: 'transparent'})
// and save as data of the menu item the 2nd of the two divs, the item's original fg color and original border bottom color
.data({hover: $c.eq(1), color: $t.css('color'), bbcolor: $t.css('borderBottomColor')});
// end setup for each menu item
}).hover(function(){ // for any menu item when hovered (runs each time one is hovered):
// mouseover - animate its color to black and its 2nd created div to height 0, revealing the hover color of its the 1st created div in a sliding motion
$(this).animate({color: '#000000'}, {duration: duration, queue: false}).data('hover').animate({height: 0}, {duration: duration, queue: false});
// note the css #menu a:hover selector in the stylesheet is overridden except it's still relied upon to turn the bottom border black
}, function(){ // mouseout -
var $t = $(this), color = $t.data('color'), bbcolor = $t.data('bbcolor'); // create a reference to the item and retrieve it's stored originl colors
// set its border bottom to black so it can be animated back to the stored color for it, do so and animate it's fg color back as well
$t.css({borderBottomColor: '#000000'}).animate({color: color, borderBottomColor: bbcolor}, {duration: duration, queue: false, complete: function(){
$t.css({borderBottomColor: ''});}}) // once the bottom border color is restored, unset it so that the stylesheet can take over next hover
// while doing the colors, animate the 2nd created div to the menu item's full height, restoring the original background in a sliding motion:
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
}); // end for any menu item when hovered
});

Only the two highlighted references are set by the script as they're not readily available from the existing menu items' markup or styles. The black color is in the :hover class for the menu items' bottom border color, so could perhaps be gotten there when the item is hovered.

In any case, as you see almost everything is set in the markup and styles, the script just reads it and uses it.

FrickenTrevor
10-21-2012, 04:29 AM
Only the two highlighted references are set by the script as they're not readily available from the existing menu items' markup or styles.

So if I added those to the CSS and removed them from the JS it should work?
Also if you know any good sites to teach you JavaScript or jQuery, I would love to check them out so I can figure this stuff out by my self

jscheuer1
10-21-2012, 03:59 PM
Yes, in a way. But I don't think there's any good hooks for them at the moment. I assumed that the hover bg color was the rest fg color, but upon closer examination it's the rest border bottom color. There's no way to get those colors from the :hover selector, at least not with the scripts and styles as they currently are. But that would be the best approach, it's more configurable. There actually is a way, but it's complicated, requires browser sniffing, and in IE 8 and less makes the initial transitions jumpy. So for simplicity's sake I chose to add a class (menu-hover) to one of the created divs and to change the code a little:


// When the DOM is ready, initialize the menu items.
jQuery(function($){
var duration = 500, $b = $('body');
$('#menu a').each(function(){
var $t = $(this), o = $t.offset(), $c = $('<div class="menu-hover"></div><div></div>').prependTo($b);
$c.css({position: 'absolute', top: o.top, left: o.left, width: $t.outerWidth(), height: $t.outerHeight() - 1, borderBottomWidth: 0})
.eq(1).css({backgroundColor: $t.css('backgroundColor')});
$t.css({position: 'relative', backgroundColor: 'transparent'}).data({hover: $c.eq(1), color: $t.css('color'), bbcolor: $t.css('borderBottomColor')});
}).hover(function(){
$(this).animate({color: $('.menu-hover').css('color')}, {duration: duration, queue: false}).data('hover').animate({height: 0}, {duration: duration, queue: false});
}, function(){
var $t = $(this), color = $t.data('color'), bbcolor = $t.data('bbcolor');
$t.css({borderBottomColor: $('.menu-hover').css('borderBottomColor')}).animate({color: color, borderBottomColor: bbcolor}, {duration: duration, queue: false, complete: function(){
$t.css({borderBottomColor: ''});}})
.data('hover').animate({height: $t.outerHeight()}, {duration: duration, queue: false});
});
});

Now, in the stylesheet where you have:


#menu a:hover {
background: #4E9BDA;
color: #000;
border-bottom: solid #000 4px;
}

Make that:


#menu a:hover, .menu-hover {
background: #4E9BDA;
color: #000;
border-bottom: solid #000 4px;
}

That way the script will be able to read those styles and use them.

One of the things I changed in the script is I'm now subtracting 1 from each menu's outerHeight when setting the height of the created divs. This was not required in my bare bones demo, but is for some reason in all IE in your layout. I'm not inclined to investigate why, as doing it (making the subtraction) is unnoticeable in other browsers.

I also noticed that in IE 7 the page lays out a little oddly. It's caused at least in part by this script. I wouldn't be too worried about that unless you expect a lot of IE 7 traffic. That browser is rapidly on the decline though with little actual current share among the browsing public. If you like, we can disable the script for that browser. And in the meantime I may figure out the problem.

I discovered the problem in IE 7. By having a margin (all browsers have a default margin for the body) for the body and a negative margin for the Page-BgGradient div it confuses IE 7. You can have the same effect without confusing the browser by adding to the body style in the stylesheet:


body{
margin: 0;
font-size:12px;
background: black url("Page-BgTexture.jpg") repeat;
font-family:Arial, Helvetica, sans-serif;
}

and removing the highlighted from the Page-BgGradient style:


.Page-BgGradient {
background: url("Page-BgGradient.jpg") repeat-x;
margin: -8px;
height: 80px;
}

That's a better approach anyway because the default margin in browsers varies from browser to browser. Doing it like the above provides more consistency across browsers. In fact it's the general approach taken in most cases in situations like this.

I also discovered the root of the 1px problem in IE that I mentioned earlier. Turns out it's only in IE 9, and that it's an artifact of that browser rounding up the offsetHeight while rounding down the actual layout height of the menu items. So the action I took (subtracting 1 as mentioned in a previous post and included in the above script code) is appropriate, especially since it doesn't adversely affect other browsers (there's 4px of wiggle room provided by the bottom border)

As for learning javascript, there's w3schools and other tutorial sites and videos around the web. For jQuery all I'm aware of is the jQuery site itself. Their:

http://api.jquery.com/

is a handy reference and has elementary examples as well as sometimes useful user comments.

The way I learned javascript was by doing. Helping out here in the forums mostly and looking things up with Google when I didn't understand them. I was also harassed/educated a lot in my early years by folks that insisted on standards who were participating back then. I know that helped me. I would recommend getting an understanding of the standards. I was pretty advanced when I started with jQuery, so after an initial resistance to it, since I knew in theory what it was doing, it's become pretty easy to work with.

There is one book that I would recommend on javascript, it's not perfect but it will take you through a lot of things that are hard to learn just poking around:

http://www.amazon.com/Object-Oriented-JavaScript-high-quality-applications-libraries/dp/1847194141

That's it on Amazon. If you look around the web you can probably find a better deal on the physical book. And I believe there's an inexpensive e-book and/or PDF version available someplace or some places around the web. Right there on Amazon there's a Kindle version for less than half the physical book price.