2014년 8월 19일 화요일

Element's Position Using JavaScript

Look, it's a kirupa!!!

Get an Element's Position Using JavaScript

by kirupa   |   18 December 2012
While this may be hard to believe, especially if you are familiar with how layout in HTML works, elements are positioned in some sort of sensible manner. Those manners are many and often confusing to discuss here, but there is some logic behind the position of everything you see. With great certainty, all elements end up at a particular location.
The difficult thing to grasp is that, often, the location of an element is something you indirectly define by fiddling with parent elements, floats, paddings, margins, positions, and more. Very rarely will you set the position of an element using a precise X and Y value. In times that you do, those values are relative to whatever container you are in - a container whose own position may be set through mysterious ways.
There will be times where you need to rationalize the position of an element by figuring out its exact X and Y value. One such time is when you are trying to position an HTML element dynamically using JavaScript. For example, let's say that I want to dynamically overlay something when a user hovers over the following element:
example of an element whose position I want to know
[ what is the position of that element? ]
In order to be able to position something accurately near that element, I first need to know what the X and Y position of that element is. This is a little complicated because there is no built-in functionality provided in HTML or the JavaScript DOM APIs for being able to do that. That's where this tutorial comes in.
In this short tutorial, you will see the code for getting the exact position of an HTML element and learn why it works the way it does. This will be a good one, so let's get started!

Overview of the Problem

Before jumping into the code, let's describe the problem with a little more detail. The easy part is identifying what we want. What we want is the exact X and Y position of an arbitrary element in our HTML document.
Now, let's go one step deeper. All measurements need a starting point, and for us, the starting point is the very top-left corner of your document:
measuring the position
The ending point is the top-left corner of the element you are interested in finding the position of. Now, here comes the challenge. Your HTML document is not a giant bitmap or a canvas. HTML and JavaScript weren't really built with a scenario like this in mind. Like I mentioned in the introduction, there is no built-in function or property that gives you the exact position of an element. You'll need to infer that based on various other inputs.
Starting with the next section , we'll look at the code first. Then, we will look at an example to make sure everything works correctly. Finally, we will wrap things up by looking into more detail on why the code works the way it does.

The Code

The code for getting X and Y position of an HTML element is provided below:
function getPosition(element) {
    var xPosition = 0;
    var yPosition = 0;
  
    while(element) {
        xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
        yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
        element = element.offsetParent;
    }
    return { x: xPosition, y: yPosition };
}
The getPosition function takes a reference to an HTML element in your document as its argument. It returns an object containing an x property and a y property.
Here is an example usage of this function:
var myElement = document.querySelector("selector path to element");
var position = getPosition(myElement);
alert("The image is located at: " + position.x + ", " + position.y);
Note that I am using the awesome querySelector function to find an HTML element, but you can usegetElementByIdgetElementsByTagNamegetElementsByClassName, or any other function that returns a reference to an HTML element that lives in your DOM.
In the next section, just to make it a little easier on you, the next section contains an example that uses this code. Let's go there next.

Working Example

To see the code from the previous section at work, here is a live example. When you run the example, you will see a dialog that looks as follows:
the dialog that you see
This dialog tells you the position of an element you are looking for, and that element is the image of Nyan Cat. with an ID of imageLocation. If you measure the position from the top-left corner of your viewport to the top-left corner of the image, you will see that the position matches what our code specifies exactly:
overlay of the spots
[ x marks the spot! ]
Like the example page's text mentions, the image is located inside a container that is absolutely positioned and has all kinds of CSS shenanigans applied to it. Despite that, our code was able to get the exact position.
In the next section, I will describe what our code does. As you will see, despite me hyping it up, it is actually pretty straightforward in how it calculates the position.

Why our Code Works

Right now, you've seen the code. You've seen the example. All that is left is for you to understand why the code works. Before looking at each line in detail, let's talk a little bit about how layout for an element works in HTML.

Layout/Position in HTML

For the most part, an element's position is partly determined by its own CSS properties, but it is largelydetermined by its parent's CSS properties. The properties that I am referring to are primarily the padding,margin, and border.
A great visualization of how those properties affect layout is by looking at the box-layout view for the element named container from our example:
look at the element's box
The relevant CSS looks like:
#container {
 padding: 24px;
 margin: 24px;
 border: 50px #ccc solid;
 left: 10px;
 top: 200px;
 position: absolute;
}
Notice how the values for paddingmargin, and border are represented in the diagram. At the far-left and top, you can see the left and top CSS properties represented because this element is absolutely positioned.
To contrast the flamboyance of our container, let's look at the image element. Our image lives inside this container, and it has no custom values defined for padding, margin, or border. If you look at the box-layout for it, it looks pretty plain:
box container that is boring
Despite nothing exciting happening with our image, because its parent is the container element you saw earlier, your image will inherit the layout and position settings of the parent. This drives home the point I made earlier where the layout of an element is determined both by its own settings as well as the settings of its parent.
What we have here is a simple case. In something more complex, your element may have many MANY parents:
deeply nested
[ that is a lot of parents' birthdays to remember! ]
Each parent element might have its own special blend of paddings, margins, and borders that play a role in positioning a child element. For us to get an element's position, we need to keep track of all that data as we move from one parent to the next.

Looking at the Code (For Realz this Time!)

Ok, we are almost done. You probably have a fuzzy big-picture overview of our solution. In this section, let's wash away the ambiguity and see how it translates everything you've seen so far into something your browser can understand.
Here is our code again:
function getPosition(element) {
    var xPosition = 0;
    var yPosition = 0;
  
    while(element) {
        xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
        yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
        element = element.offsetParent;
    }
    return { x: xPosition, y: yPosition };
}
Let's start at the very top:
function getPosition(element)
Our function is called getPosition, and it takes an argument called element. As you saw from the example usage, the element in question is a reference to an HTML element that lives in your DOM. Preferably, it is the HTML element whose position you are curious about as well.

Next up, we declare two variables called xPosition and yPosition whose values are initialized to 0:
var xPosition = 0;
var yPosition = 0;
These two variables will be used to keep a count of our element's current x and y position as we go through each of the element's parents. That will become more clear when we look at the next block of code:
while(element) {
    xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
    yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
    element = element.offsetParent;
}
This is where the magic happens. We have a while loop that starts with the element we passed in, measures the current element's position/layout properties, keeps a running tally of the current position/layout-related values by updating the xPosition and yPosition variables, and makes its way up to the root of your document via the offsetParent property. The loop ends when there are no more parents for us to run into.
Let's dive into this a little bit further. Earlier, I mentioned that the layout/position of an element is affected by the padding, margin, and border properties. If the element is positioned absolutely or relatively, that will stir things up a bit further with some top/left properties coming into the mix.
If you look at our code, there is no mention of padding, margin, border, top, or left. The three properties we measure instead are the offset*scroll*, and client* properties. That may seem a bit bizarre, but what we are looking for is indeed contained in these three properties. It just requires some digging and research.

The Offset Properties

The offsetLeft and offsetTop properties return the left and top positions relative to their nearestoffsetParent. That probably makes no sense. What I am trying to say is that these properties measure the distance from the current element's top-left corner to its nearest offset parent.
For example, here is the offsetTop and offsetLeft value for our image element:
offset top
The value is 24 because the parent (aka the container) pushes the image away by 24 pixels. The pushing away could be determined by many factors. In this case, the factor is the parent container's padding value. The offsetTop and offsetLeft values put a numerical value to how much a parent has pushed you away:
xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
Each parent element has a similar offset value, so our loop just sums all of them up until there is no parent left. The end result is a sum of all the offset positions to help give you an accurate position that takes into account margins, paddings, and top/left values. There is one thing missing though...

Don't Forget the Border

The one value the offset properties don't take into account is an element's border. The reason is that the border is considered a part of an inner element's top-left corner, but its size does have an effect on the position of something. To measure the border size, we use the clientLeft and clientTop properties:
xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
Right now, with our offset* and client* properties, we have accounted for paddings, margins, borders, and top/left properties. In 99% of all cases, this should be all you need to take into account...unless your case is part of that remaining 1%.

Scrolling

The element you are looking for may be inside a container that scrolls. If your container is scrolling, then the position of your element needs to take that scroll into account. To make sure that is taken care of, we measure the scrollLeft and scrollTop properties:
xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
Notice that the values of the scroll* properties is subtracted as opposed to being added like the offset* and client* properties.

The last thing we will look at is...well, the last line:
return { x: xPositiony: yPosition };
After our loop has completed, all that is left is return the xPosition and yPosition variables to the code that called our getPosition function in the first place. I return them in the form of a new object that contains an x and y property store the values found in the xPosition and yPosition variables respectively. That's all there is to it!

Conclusion

Finding an element's exact position isn't difficult from the point of writing the code. Figuring out how the various layout and position properties collude to have an element appear can be a bit tricky, but hopefully this tutorial helped you to understand it all better.

Getting Help
If you have questions, need some assistance on this topic, or just want to chat - post in the commentsbelow or drop by our friendly forums (where you have a lot more formatting options) and post your question. There are a lot of knowledgeable and witty people who would be happy to help you out. Plus, we have alarge collection of smileys you can use 

댓글 없음:

댓글 쓰기