Aug
05
2009

Implementing Floating Div

Problog

This blog cover the following topics:

  • How to write a jQuery Plug-in
  • What is a floating object and where it can be used
  • How to move a DOM object using jQuery
  • Implementing a class in javascript

One of the things that I really like about jQuery is its huge community that develops plug-ins for the framework. Being a big fan of client side and especially rich UI, I cannot ignore the power that jQuery provides and it shows in the amazing plug-ins users have developed for it. I’ve decided that I need to understand how to write a UI plug-in and on the way contribute something back to the community.

I don’t remember exactly why I needed a floating object/div code, but not long ago I’ve search for such implementation. I wanted an implementation that will allow me to select a div object and make it float on the screen to follow wherever the user scrolls to (more like an annoying friend that doesn’t let you go).

I found a few implementation for it, and even some were jQuery plug-ins, but I didn’t like any of the implementations since some didn’t exactly do what I wanted, or some were just written badly in a way that if there was a code inspection police it would get a fine, and to be honest I saw some code that could get the writer arrested. There is nothing worse than reading a spaghetti code, well, maybe eating coded spaghetti (whatever that is…). But hey, i guess most of the developers that will read my code will say the same about mine, right?

Anyway I decided for the practice that I will implement it on my own, and I will show you exactly how I did it.

For those of you who just can’t wait till the end – here is a demo of the plug-in.

Before I go into details about the implementation I need to answer an important question my wife asked me when I showed her the demo for the first time – “why on earth anyone might use this thing?”. Other than my usual answer to almost everything I do – “cause its cool” I think there are a few interesting usage for this plug-in:

  • Floating Menu – I’ve came across a few websites that allowed you to scroll thru the content while the site menu chase them while the scroll down and up.
  • Floating Headers – Sometimes it make sense to make a table header float. This is true when the table is long and the user needs to scroll way up just to see the name of each column.
  • Floating Ads – for those of you who are anxious to make some bucks out of advertisements on their website – don’t let you users run away from your ads – make your ads run after your users. I know its lame, but hey, there are some desperate people out there (and i’m not far from joining that group)

I guess there are more interesting usage for such a plug-in, so if you think of using this plug-in, please drop me a line about how you used it – it will give me something to show the misses.

jQuery Plug-in Conventions

Scope

jQuery uses the $ sign, but to avoid conflict with other libraries it allows the user to change the $ sign. In order to keep your code working even in this cases, you should encapsulate the $ sign in a function like so:


(function($){
    //now its safe to use the $ sign
})(jQuery);

Adding a Method

According to the jQuery documentation they way to add a new method is by using the $.fn. The following is an example for adding a simple method to the jQuery object:


$.fn.myFunc = myFunction(a,b,c){return this;}

I’ve checked the jQuery code to see what is $.fn and I found that it’s actually the prototype, as you can see in the following code taken from the jQuery implementation:


jQuery.fn = jQuery.prototype = { …

The returned this refers to the selected jQuery objects. In our case, there is no reason to work on all the selected, so I trim down the selection into only the first selected object. I do this using the jQuery.eq method. It is also recommended to return the selected object at the end of the function since jQuery allows you to call methods in a chain. Here is the implementation for the main method I’ve added:


$.fn.makeFloat = function(params) {
	var obj = this.eq(0); //we only operate on the first selected object;
	$.floatMgr.initializeFO(obj,params);
	if( $.floatMgr.timer == null ) $.floatMgr.adjustFO();
	return obj;
};

Floating Div – The Logic

So how this plug-in really works? The user provides the location (x,y) of the floating object. This is saved as the original x,y. (origX,origY). From that moment on I always calculate the updated location (updatedX,updatedY) that the object needs to be according to the vertical and horizontal scrolling that was made.


this.updatedX = $(window).scrollLeft() + this.origX;
this.updatedY = $(window).scrollTop()+ this.origY;

After calculating the new location, I check to see what is the difference or delta that the updated location has from the current location (currentX,currentY). If this number is not zero, than we need to move the object one step towards the updated location.


this.dx = Math.abs(this.updatedX - this.currentX );
this.dy = Math.abs(this.updatedY - this.currentY );
return this.dx || this.dy;

As you can see the  updateLocation method return true if we need to move the object and false if the delta is zero for both the X and Y.

The method that is in charge of the moving calculates the amount of pixels that the object needs to moved according to the speed parameter that was passed by the user.  In order to make the moving smooth, I make sure that on the last step the object will advance only by one pixel at a time.

Floating Div – Animation

The movement/animation of the DOM object on the screen is being done by adjusting the object top and left properties. In this case i used the jQuery.css method:


this.jqObj.css({'left':this.currentX, 'top': this.currentY });

In order to keep on moving the object, I used the setTimeout function whenever the updateLocation methods returns true. This way I make sure that the object will keep on going until it reaches its final location.

Floating Div – Object Oriented

Being a C++ programmer for many years (I wonder if it’s still being used these days) I always try to design my code according to the object oriented methodology. This is what I did here. I created the FloatObject class which is responsible for all the calculation and the movement of a single DOM object. It has only two methods: updateLocation and move.
Here is the complete code for the FloatObject:


function FloatObject(jqObj, params)
{
	this.jqObj = jqObj;

	switch(params.speed)
	{
		case 'fast': this.steps = 5; break;
		case 'normal': this.steps = 10; break;
		case 'slow': this.steps = 20; break;
		default: this.steps = 10;
	};

	var offset = this.jqObj.offset();

	this.currentX = offset.left;
	this.currentY = offset.top;

	this.origX = typeof(params.x) == "string" ?  this.currentX : params.x;
	this.origY = typeof(params.y) == "string" ?  this.currentY : params.y;
	//if( params.y) this.origY = params.y;

	//now we make sure the object is in absolute positions.
	this.jqObj.css({'position':'absolute' , 'top':this.currentY ,'left':this.currentX});
}

FloatObject.prototype.updateLocation = function()
{
	this.updatedX = $(window).scrollLeft() + this.origX;
	this.updatedY = $(window).scrollTop()+ this.origY;

	this.dx = Math.abs(this.updatedX - this.currentX );
	this.dy = Math.abs(this.updatedY - this.currentY );

	return this.dx || this.dy;
}

FloatObject.prototype.move = function()
{
	if( this.jqObj.css("position") != "absolute" ) return;
	var cx = 0;
	var cy = 0;

	if( this.dx > 0 )
	{
		if( this.dx < this.steps / 2 )
			cx = (this.dx >= 1) ? 1 : 0;
		else
			cx = Math.round(this.dx/this.steps);

		if( this.currentX < this.updatedX )
			this.currentX += cx;
		else
			this.currentX -= cx;
	}

	if( this.dy > 0 )
	{
		if( this.dy < this.steps / 2 )
			cy = (this.dy >= 1) ? 1 : 0;
		else
			cy = Math.round(this.dy/this.steps);

		if( this.currentY < this.updatedY )
			this.currentY += cy;
		else
			this.currentY -= cy;
	}

	this.jqObj.css({'left':this.currentX, 'top': this.currentY });
}

In order to manage all the movements of all the floating objects I extended the jQuery object by adding a float manager object. Since there is only one manager, there is no point of creating a class for it, so i created an opject and attached it to the jQuery instance.

This object keeps an array of all the floating objects that the user has selected, and to invoke their methods. The manager is also responsible to register to the resize and scroll events in order to instruct the floating objects to recalculate their locations.

Here is the complete code of the manager object:


$.floatMgr = {
	FOArray: new Array() ,
	timer: null ,

	initializeFO: function(jqObj,params)
	{
		var settings =  $.extend({
			x: 0 ,
			y: 0 ,
			speed: 'normal'	},params||{});
		var newFO = new FloatObject(jqObj,settings);

		$.floatMgr.FOArray.push(newFO);

		if( !$.floatMgr.timer ) $.floatMgr.adjustFO();

		//now making sure we are registered to all required window events
		if( !$.floatMgr.registeredEvents )
		{
				$(window).bind("resize", $.floatMgr.onChange);
				$(window).bind("scroll", $.floatMgr.onChange);
				$.floatMgr.registeredEvents = true;
		}
	} , 

	adjustFO: function()
	{
		$.floatMgr.timer = null;

		var moveFO = false;

		for( var i = 0 ; i < $.floatMgr.FOArray.length ; i++ )
		{
			 FO = $.floatMgr.FOArray[i];
			 if( FO.updateLocation() )  moveFO = true;
		}

		if( moveFO )
		{
			for( var i = 0 ; i < $.floatMgr.FOArray.length ; i++ )
			{
				FO = $.floatMgr.FOArray[i];
				FO.move();
			}

			if( !$.floatMgr.timer ) $.floatMgr.timer = setTimeout($.floatMgr.adjustFO,50);
		}
	}	,

	onChange: function()
	{
		if( !$.floatMgr.timer ) $.floatMgr.adjustFO();
	}
};

Documentation

Now I will explain how to use the plug-in within a project. The plug-in has only one method:


$.makeFloat(params);

The method only works on the first matched element within the jQuery object.

Params

This is an object that direct the method where to place the floating object and how fast it should move.

x: could be either numerical for the actual location on the X axis or the string ‘current’ to indicate the floating manager to use the current location as the base location.

y: the same is x.

Speed: ’slow’, ‘normal’ ,’fast’.

All the parameters are optional, and default params are set in case any parameter is being left out.

Usage

All you need to do is to select the object you want and specify the location and speed, and the rest is being done automatically – that’s it.


$(document).ready(main);
function main()
{
	$("#video").makeFloat({x:"current",y:"current"});
}

You can see a demo of this plug-in in this demo page.
Download the recent version of the plug-in.

You can also check out the plug-in in jQuery plug-in section

Bookmark and Share
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • E-mail this story to a friend!
  • FriendFeed
  • LinkedIn
  • StumbleUpon
  • Twitter

tags: , , , , ,
posted in Blog by Amir Harel

Follow comments via the RSS Feed | Leave a comment | Trackback URL

  • Abdulrauf Cheema
    Thanks, It really worked for me as I was expecting.
  • bc
    This is great, but as with all other floating div plugins causes the div to go past the bottom of the page when the window isn't long enough to fit the content. It should probably stop floating when the bottom of the floater reaches the bottom of the page.
  • I'm aware of this problem but unfortunately too busy with new project that i don't have the time to fix some of these bugs and new requests.
    Maybe you can help ;)
  • agnes
    hi , does this work on ie6 too?
  • Great script! This is one of the easiest to use and implement. It worked great in my project. Thanks! :)
  • Adam
    Have also noticed that on Safari it doesn't display the div in the right place - it's higher than it should be
  • Todd
    Here's a patch for you to keep it from escaping the parent div:

    if( $(window).scrollTop() + this.height > this.jqObj.parent().height()) {
    this.updatedY = this.jqObj.parent().height() + this.origY - this.height;
    }

    I put it at the end of the alwaysTop else clause.

  • Adam
    Todd where did you put your patch? I don't see and alwaysTop else clause? Would love to be able to use it!
  • Christian
    First of all. let me say this is a great piece of coding.

    I want to be able to position the floating div within another div. Once I apply the script, the floated divs moves out of the parent div. Is there any reason for this, or am I just doing somethign wrong?

    Also, is it necessary to style the div this way - <div id="video" style="position: absolute; top: 95px; left: 500px;"> rather than just appyling the styles to the css class ?</div>
  • Why do you want to position the floating div within another div? the whole purpose of the floating object is to be kept visible on the screen all the time, so it only makes sense to put it absolute position. At least that what i thought when i wrote the plugin. Maybe you can explain what you need to do, and i can better understand what you need to change to make it work.

    As for the inline styling - sure you can put it in an external CSS file and change the top, left and id as you require.
  • I believe what Christian is trying to do is similar with the alteration I'm attempting to do.

    www.mediamasters.ca/clientsites/2010

    Its a single page, vertical scrolling site, broken into sectional divs. So what I am trying to do is, when you get to the "about us" section. I want the menu that is on the left to scroll with the page, BUT only in that section. So not visible until you get to the about section, then as you scroll the about section it floats with the page until it gets to the bottom of the div, and then stops.

    Any advice on how to do this?

    I've left the floating div on the site for testing purposes, currently the menu doesn't use the floating plugin.
  • I understand. This can be done by adding a parameter of the container div and then calc the div dimensions along with the scrollTop to restrict the movement of the floating div.
    Actually that sounds cool to do , but i'm pretty overload with work so I don't think i'll be able to implement in the next few weeks.
  • Yuval Lieberman
    very helpful script!
    I used it for floating the first TR in long data tables.

    Some modifications I made:
    1. where the table creates a horizontal scroll, the TR keeps its absolute position, instead of locking to the other TRs in the table. I removed all lines that modify the X axis, making it work for vertical floating only.

    2. on right-to-left pages with horizontal scrolling, modifying the TR to absolute position changes the correct position (warning: currently tested on IE8 only!!).

    changed:

    this.jqObj.css({'position':'absolute' , 'top':this.currentY ,'left':this.currentX});

    to:

    this.currentXrtl = this.windowWidth() + $(window).scrollLeft() - this.jqObj.width() - this.currentX;
    this.jqObj.css({'position':'absolute' , 'top':this.currentY ,'left':this.currentX});
    var offset1 = this.jqObj.offset();
    if (offset1.left != this.currentX) { // rtl layout
    this.jqObj.css({'position':'absolute' , 'top':this.currentY ,'right':this.currentXrtl});
    }
  • So now my plugin support Hebrew as well :)
    It would be nice if you can share a link to the page that uses the script...

    Amir
  • Marko
    This is a great plugin. It works great, but
    I'm not sure if I can use it.

    I have a side panel that I float. With the modifications posted above, I was able to always keep it at the top of the screen.

    My problem:
    I dynamically add content to that side panel. It expands in width.
    I will have to find a way to stop the panel from expanding over the parent div container. If it expands below the screen, I can scroll down indefinitely.
    I will have to find a way to expand the parent div if the container gets to big and to stop it from floating over the lower boundaries of that div container.

    If anybody has helpful suggestions, that would be great.
  • Thomas
    This is a really useful script. Thank you! I did make one tweak to it. Someone named "Andy" made a similar adjustment. Your script assumes that you always want the div to float the same y distance as it starts. My floating div starts about 150px down, but once it starts to scroll out of view I want it to float to 10px from the top of the view.

    I modified line #2 in the code in updateLocation from

    this.updatedY = $(window).scrollTop()+ this.origY;

    to

    if ($(window).scrollTop() > this.origY ) {
    this.updatedY = $(window).scrollTop() + 10;
    } else {
    this.updatedY = $(window).scrollTop()+ this.origY;
    }

    The +10 is a padding of 10 from the top of the view. You may consider adding an additional parameter(s) to handle this or adjust the interpretation of the x, y parameters to be a padding value once the div starts to scroll out of view.

    Just a thought specific to my situation, but I'm not criticizing your work at all. It saved me a ton of time to develop on my own!!
  • radimdh
    Hello guys. Would you mind if i asked you for help..? Trying to add floating div to my website but still have weird position of banner and it is not clear to me what is wrong and need to be changed.

    Thanks for your help.

    http://tips.crazy-riders.eu/index.php/car-tips/49-money-saving-car-tips (delete later please url of my website)

  • Sorry for the late response - was kind of busy.
    Did you managed to solve that?
  • Lena Ghaleb
    Hi, no worries, I still have not figured it out. I am working on a horizontal scrolling site and want to have floating div_a until a certain section, where it will stop and a new div_B is now in motion for the new section.
    does that make sense?

    any help would be appreciated.

    thanks!
  • prashant
    Very Nifty script thank you....one thing I added and maybe useful for someone is the ability to start/stop the float.

    I added a new variable to the float manager:

    stopFloatChk: false ,

    Modified the onchange function in the float manager:
    onChange: function()
    {
    if( !$.floatMgr.timer && !$.floatMgr.stopFloatChk) $.floatMgr.adjustFO();
    }

    I then added two functions that I can call to stop or start.

    $.fn.stopFloat = function(params) {
    $.floatMgr.stopFloatChk = true;
    };

    $.fn.restartFloat = function(params) {
    $.floatMgr.stopFloatChk = false;
    };
  • Hey guys,

    I tried to add the stopFloat function to my page but it seems not to be working.

    Here is my script :

    $(document).ready(main);
    function main()
    {
    var origineY=$("#blocImageProf").offset().top;
    $(document).scroll(function(event){
    if(($(window).scrollTop())>=origineY){
    $("#blocImageProf").makeFloat({x:"current",y:10,speed:"fast"});
    }
    else{
    $("#blocImageProf").stopFloat();
    }
    });
    }

    What I want to do is activate the float of the selected div only when the scroll reach this div, and stop the float if you scroll up at a higher position than the origin div position.
  • Hi, Its very nice and useful. I want to know how to increase the fast?
  • The speed is defined in the following code:
    switch(params.speed)
    {
    case 'fast': this.steps = 5; break;
    case 'normal': this.steps = 10; break;
    case 'slow': this.steps = 20; break;
    default: this.steps = 10;
    };

    so in order to make it even faster just tweak the numbers to your needs (making fast even faster would set this.steps = 3)
  • dcaliri
    I've just downloaded the plugin and I find it great thou I should modify it a bit in order to be able to send 1 more option as a parameter that would be something like "float and move but limit that movement until you reach your container's limit"

    Is someone working on that? I ask just not to make a double work. If not I can spare some time to work on that. Suggestions?

  • I've slightly adapted this to keep the floating element only just visible once you scroll beneath it's default position.

    Change the updateLocation function to:

    if ($(window).scrollTop() > this.origY ) {
    this.updatedY = $(window).scrollTop();
    } else {
    this.updatedY = this.origY+10;
    }

    Hope this helps someone!
  • Andy thanks for helping out.

    Shaaa - let me know if this fix the bug.

  • Lena Ghaleb
    All the comments are so helpful, thanks to all for the code!

    I too, want the float to end at a certain coordinate-- so that another can start floating for that section.
    How do I modify the XY of where the floating div should stop? I have a relaly basic understanding, so I need to know exactly what to put where.

    thanks in advance!
  • Shaaa
    Hi this is a great plugin. I have an issue that if the floating div content is larger than main content then
    scroll down is infinite. Is it possible to stop this?
  • tomWright
    Do you think it is possible to float the div based on the height of a gridview that is dynamically changing?
  • I don't think there should be any problem. All you need to do is to keep updating the origX and origY values of the FloatObject whenever the X,Y of the gridview is changing.
  • tomWright
    So is the x,y the end points or the points of where it should be? How difficult would it be to put ednd points? If you scroll right or left it scrolls forever.
    thanks

    Sent from my Windows Mobile® phone.
  • derkoidus
    nice code. Is there a way to change the position of the floating div? my problem is the original position is below the fold. And when users that have smaller browsers scroll. The floating div goes below the fold. please help.

    Thanks,
    derkoidus
  • That is interesting. I played with the window size and did get some strange behavior.

    I think i solved it in this new release of the plug-in: http://plugins.jquery.com/project/floatobject

    Try to use now version 1.1 of the plug-in and use the new parameter: alwaysVisible set to true, like so:
    $("#video").makeFloat({x:"current",y:"current",alwaysVisible:true});

    let me know if it solves your problem.

    Regards,
    Amir
  • derkoidus
    it worked great!! Would there be a way to set a restriction because now when the browser is too little and i am at the top of the page the floating div starts to float over my header. Forgive my ignorance I'm new to jquery. Thanks for doing this.
  • there a re a couple of ways to solve what you mention:
    1) z-index - since the floating div is on a different layer (position=absolute) you can change its z-index to go back, so it won't float over your header
    2) set a restriction - as you mentioned, we could implement in the plug-in a restriction not to come to close to the upper/left side.

    Since i don't know how do you use it in your project, i can't recommend the best approach. If you like, you can send me a link to your project so i can take a look of what will be the best solution for you.

    Amir
  • derkoidus
    Here is the HTML



    <script src="../js/jquery126.js" type="text/javascript"></script>

    <script src="../js/scripts.js" type="text/javascript"></script>
    <script type="text/javascript">
    $(document).ready(main);
    function main()
    {
    $("#right_div").makeFloat({x:"current",y:"current",alwaysVisible:true});
    }
    </script>


    <div class="wrap" style="float:left;width:800px;">
    <div class="header" style="float:left;width:800px;"></div>
    <div class="content_wrap" style="float:left;position:relative;width:800px;">
    <div class="left_div" style="float:left;"></div>
    <div id="right_div" style="float:left;"></div>
    </div>
    </div>
    <div class="footer"></div>



  • in your code you have a div with a class=content_wrap. you specified that this div is positioned relative. If you don't need this div relative please change it back to static.
    The relative position is changing the way the right_div is positioned on the screen. The floating object set the position to absolute. and remember that these values (x,y) will be relative to the next parent element with relative (or absolute) positioning. If there is no such parent, it will default all the way back up to the html element itself meaning it will be placed relatively to the page itself. but in our case it is positioned relatively to the content_wrap div.

    I should fix that in the plug-in, but for now please try to change content_wrap div from relative to static and let me know how it works.
  • derkoidus
    Hi, thanks again for your help.

    I actually added 2 conditions(the 2 later ones) to the alwaysVisible if block:
    if( this.alwaysVisible && this.currentY > 450 && $(window).height()<865 )

    however, it seems like it tries to jump up and then I think this code activates, so the div behaves like a energizer bunny jumping up and down.

    do you have any idea why? thanks again.
  • Sam
    Nice script.
    I'm curious though if you have had any success running multiple floating divs on the same page.

    I'm seeing an issue where the positioning of the original floated div is affected by a second instance.
    I though the float manager was supposed to handle this. Still trying to troubleshoot.
  • I tested the code with multiple objects and didn’t noticed anything buggy. This is exactly why i decided to write the float manager – to manage multiple floating objects, so that is weird.

    Can you send me a snippet to see how you are using it so i’ll try to have a look?
  • Sam
    Never mind. My bad. I was instantiating the 2nd floater before it was there. That seemed to be causing the issue.

    On a side note, I've modified the code to allow the floating div to be relative to a parent div to keep it within a min/max y range.

    Seems to be working good.
    Thanks again.
  • dcaliri
    I just posted about doing something with the code to manage a min/max range according to its parent container and I didnt noticed someone did it before. Would you mind sharing it? My email is diegocaliri@gmail.com

    Thanks in advance
  • Great.
    The relative float is an interesting addition, it would be nice to add it to the plug-in, if you don't mind sharing.

    Regards,
    Amir
blog comments powered by Disqus
 
Powered by Wordpress and MySQL. Theme by openark.org