Following this answer to a similar question Make position: fixed behavior like sticky (for Vue2), I have tried to implement it in my application.
The solution was a little bit buggy (in some cases it behaved oddly, especially when opening other tabs and coming back), so I decided to implement it using jQuery and it's working as expected.
Here is the working example:
<template>
<div>
<div class="recap">
<div class="inner" :style="recapStyle">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ProductRecap',
data() {
return {
scrollY: null,
top: null,
bottom: null,
marginTop: 40,
recapStyle: {},
};
},
methods: {
updatePosition(scroll) {
// using jQuery to calculate amount
const offset = $(this.$el).offset().top;
const scrollAmount = offset - scroll;
const rectHeight = $(this.$el).find('.inner').outerHeight();
if (scrollAmount < this.top) {
let updatedTop = scroll - offset + this.top;
if ((scroll + rectHeight) < this.bottom) {
this.prevScroll = updatedTop;
} else {
updatedTop = this.prevScroll;
}
this.$set(this.recapStyle, 'top', `${updatedTop}px`);
} else {
this.$delete(this.recapStyle, 'top');
}
},
},
watch: {
scrollY(scrollUpdate) {
// call `updatePosition` on scroll
this.updatePosition(scrollUpdate);
},
},
mounted() {
// calculate header size (position: fixed) and add a fixed offset
this.top = $('#main-header').outerHeight() + this.marginTop;
// calculate height of the document (without the footer)
this.bottom = document.querySelector('.global-container').offsetHeight;
// update scrollY position
window.addEventListener('scroll', _.throttle(() => {
this.scrollY = Math.round(window.scrollY);
}, 20, { leading: true }));
},
};
</script>
However I'd like to find a solution that doesn't use jQuery to calculate the offset, so I headed to You Might Not Need jQuery, but if I just replace the offset part with the one that's suggested it's still a bit buggy.
$(el).offset();
Should become:
var rect = el.getBoundingClientRect();
{
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
}
So I replaced the line:
const offset = $(this.$el).offset().top;
with:
const rect = this.$el.getBoundingClientRect();
const offset = rect.top + document.body.scrollTop;
But the distance of the sidebar from the fixed header increases with the scroll: can anyone explain how to fix it?
Here is a working fiddle (slightly simplified): Fiddle
The short answer is to use these two lines (the first one is yours):
const rect = this.$el.getBoundingClientRect();
const offset = rect.top + window.pageYOffset;
The longer answer of course includes the thought process to achieve this result. I ran
console.log($(this.$el).offset + "");
On your fiddle at the relevant place to see how the offset function is implemented and got this:
function( options ) {
// Preserve chaining for setter
if ( arguments.length ) {
return options === undefined ?
this :
this.each( function( i ) {
jQuery.offset.setOffset( this, options, i );
} );
}
var rect, win,
elem = this[ 0 ];
if ( !elem ) {
return;
}
// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
// Support: IE <=11+
// Running getBoundingClientRect on a
// disconnected node in IE throws an error
if ( !elem.getClientRects().length ) {
return { top: 0, left: 0 };
}
// Get document-relative position by adding viewport scroll to viewport-relative gBCR
rect = elem.getBoundingClientRect();
win = elem.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset
};
}
The solution is inspired from this line:
top: rect.top + win.pageYOffset,
I'm using JQuery Draggable to move items round a grid. Objects snap to a 32x32 grid area. I want to be able to cancel a grid snap if an object is in the same position.
The drag cannot be cancelled, it must just be prevented from entering the square. After it is prevented and moved back to the previous position, if the user continues to drag into a new unoccupied grid position, it must snap to that one.
I've created a demo which serves the purpose explained above however the image glitches when it tries to enter the new position but is then cancelled back to the old position.
https://jsfiddle.net/dtx7my4e/1/
Here's the code in that fiddle:
HTML:
<div class="drop-target">
<div class="drag-item" object-id="0"></div>
<div class="drag-item" style="left: 32px;" object-id="1"></div>
</div>
Javascript:
var objects = [
[0, 0],
[1, 1]
];
$(function() {
$(".drag-item").draggable({
grid: [ 32, 32 ],
containment: '.drop-target',
drag: function (event, obj){
let objectId = $(this).attr('object-id');
var objectPositionX = $(this).position().left / 32;
var objectPositionY = $(this).position().top / 32;
var previousPositionX = Math.floor(objects[objectId][0]);
var previousPositionY = Math.floor(objects[objectId][1]);
if (objectPositionX != previousPositionX || objectPositionY != previousPositionY) {
if(!isObjectInPosition(objects, [objectPositionX, objectPositionY])) {
objects[objectId] = [objectPositionX, objectPositionY];
} else {
obj.position.left = previousPositionX * 32;
obj.position.top = previousPositionY * 32;
}
}
}
});
});
function isObjectInPosition(arrayToSearch, positionToFind)
{
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] == positionToFind[0]
&& arrayToSearch[i][1] == positionToFind[1]) {
return true;
}
}
return false;
}
CSS:
.drag-item {
background-image: url("http://i.imgur.com/lBIWrWw.png");
background-size: 32px auto;
width: 32px;
height: 32px;
cursor: move;
}
.drop-target {
background: whitesmoke url("http://i.imgur.com/uUvTRLx.png") repeat scroll 0 0 / 32px 32px;
border: 1px dashed orange;
height: 736px;
left: 0;
position: absolute;
top: 0;
width: 736px;
}
Thank you, any help is greatly appreciated.
Toby.
If you're willing to modify draggable itself, I think it would make the logic easier to apply. Once the drag event is triggered you can do lots of things, but you have much more control if you modify the _generatePosition method of draggable. It may look more complicated at first but for this kind of behavior, it's sometimes easier to work.
Basically, you can run your isInPosition function after the check for grid and containment has been applied. Normally next step is to set the new position, but if your isInPosition returns true, you prevent dragging. Something like this:
'use strict'
// This is the function generating the position by calculating
// mouse position, different offsets and option.
$.ui.draggable.prototype._generatePosition = function(event, constrainPosition) {
var containment, co, top, left,
o = this.options,
scrollIsRootNode = this._isRootNode(this.scrollParent[0]),
pageX = event.pageX,
pageY = event.pageY;
// Cache the scroll
if (!scrollIsRootNode || !this.offset.scroll) {
this.offset.scroll = {
top: this.scrollParent.scrollTop(),
left: this.scrollParent.scrollLeft()
};
}
/*
* - Position constraining -
* Constrain the position to a mix of grid, containment.
*/
// If we are not dragging yet, we won't check for options
if (constrainPosition) {
if (this.containment) {
if (this.relativeContainer) {
co = this.relativeContainer.offset();
containment = [
this.containment[0] + co.left,
this.containment[1] + co.top,
this.containment[2] + co.left,
this.containment[3] + co.top
];
} else {
containment = this.containment;
}
if (event.pageX - this.offset.click.left < containment[0]) {
pageX = containment[0] + this.offset.click.left;
}
if (event.pageY - this.offset.click.top < containment[1]) {
pageY = containment[1] + this.offset.click.top;
}
if (event.pageX - this.offset.click.left > containment[2]) {
pageX = containment[2] + this.offset.click.left;
}
if (event.pageY - this.offset.click.top > containment[3]) {
pageY = containment[3] + this.offset.click.top;
}
}
if (o.grid) {
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
}
if (o.axis === "y") {
pageX = this.originalPageX;
}
if (o.axis === "x") {
pageY = this.originalPageY;
}
}
// This is the only part added to the original function.
// You have access to the updated position after it's been
// updated through containment and grid, but before the
// element is modified.
// If there's an object in position, you prevent dragging.
if (isObjectInPosition(objects, [pageX - this.offset.click.left - this.offset.parent.left, pageY - this.offset.click.top - this.offset.parent.top])) {
return false;
}
return {
top: (
pageY - // The absolute mouse position
this.offset.click.top - // Click offset (relative to the element)
this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
(this.cssPosition === "fixed" ? -this.offset.scroll.top : (scrollIsRootNode ? 0 : this.offset.scroll.top))
),
left: (
pageX - // The absolute mouse position
this.offset.click.left - // Click offset (relative to the element)
this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
(this.cssPosition === "fixed" ? -this.offset.scroll.left : (scrollIsRootNode ? 0 : this.offset.scroll.left))
)
};
}
var objects = [
[0, 0],
[1, 1]
];
$(function() {
$(".drag-item").draggable({
grid: [32, 32],
containment: '.drop-target',
// on start you remove coordinate of dragged item
// else it'll check its own coordinates
start: function(event, obj) {
var objectId = $(this).attr('object-id');
objects[objectId] = [null, null];
},
// on stop you update your array
stop: function(event, obj) {
var objectId = $(this).attr('object-id');
var objectPositionX = $(this).position().left / 32;
var objectPositionY = $(this).position().top / 32;
objects[objectId] = [objectPositionX, objectPositionY];
}
});
});
function isObjectInPosition(arrayToSearch, positionToFind) {
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] === (positionToFind[0] / 32) && arrayToSearch[i][1] === (positionToFind[1] / 32)) {
return true;
}
}
return false;
}
https://jsfiddle.net/bfc4tsrh/1/
If anyone could help me figure out how to make the draggable elements contained in a div that changes scale based on window size, i'd really appreciate any guidance.
If I do:
element.draggable({
cursor: "move",
containment: '#container'
});
What will happen is it gives me the containment for the regular size of the container. So if I have a transform: scale(1.5), there will be space in the container that the draggable element can not go.
I've also tried containment: 'parent' but that get's very glitchy.
EDIT
I've found out how to get the top and left containment but I can't figure out how to get the right and bottom.
var containmentArea = $("#container");
containment: [containmentArea.offset().left, containmentArea.offset().top, ???, ???]
I've tried width and height from containmentArea[0].getBoundingClientRect() but that doesn't seem to be the right move either.
Here is a jsfiddle of some example code.
A version with resetting the coordinates in the drag event (since they were being recalculated already for the scale transformations), without using the containment:
var percent = 1, containmentArea = $("#container");
function dragFix(event, ui) {
var contWidth = containmentArea.width(), contHeight = containmentArea.height();
ui.position.left = Math.max(0, Math.min(ui.position.left / percent , contWidth - ui.helper.width()));
ui.position.top = Math.max(0, Math.min(ui.position.top / percent, contHeight- ui.helper.height()));
}
$(".draggable").draggable({
cursor: "move",
drag: dragFix,
});
//scaling here (where the percent variable is set too)
Fiddle
In the example width and height of the container are obtained inside the dragevent, you could also store them when scaling for better performance. By having them calculated inside the event, they still work after rescaling, although the percent variable still has to be set. To be truly generic, it could be obtained inside the event as well (and instead of a fixed container, ui.helper.parent() could be used)
Since the offset inside the dragevent is (0,0) related to the container (at least it is for the current setup), took the liberty of simplifying originalleft + (position - originalposition)/percent to position / percent
Start offset didn't seem to be necessary any more, so left it out in the fiddle, but can be re-added if needed.
Take a look to this :
http://jsfiddle.net/z0gqy9w2/3/
The edited code is the following one :
// Matrix regex to take the scale value property of $('#container') element
var matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/,
matches = $('#container').css('transform').match(matrixRegex);
// Matches have this value : ["matrix(1.5, 0, 0, 1.5, 0, 0)", "1.5", "1.5"] , so we need matches[1] value :
var scaleValue = matches[1];
$(".draggable").draggable({
cursor: "move",
start: startFix,
drag: dragFix,
containment: [containmentArea.offset().left, containmentArea.offset().top,
( ( containmentArea.offset().left + ( containmentArea.width() * scaleValue ) ) - ( $(".draggable").width() * scaleValue ) ) ,
( ( containmentArea.offset().top + ( containmentArea.height() * scaleValue ) ) - ( $(".draggable").height() * scaleValue ) ) ]
});
As you see, here is the trick :
( ( containmentArea.offset().left + ( containmentArea.width() * scaleValue ) ) - ( $(".draggable").width() * scaleValue ) )
Your right max position will be : The main container left offset + the true width of the container ( with scale ) - the item true width (to let it inside the container).
(Tip: Be free to change the "percent" var value as you want too see the results)
regex ref
Here is my solution:
var _zoom = 1.2,
$element = $('.draggable-element'),
$container = $('#container');
var containmentW,
containmentH,
objW,
objH;
$element.draggable({
start: function(evt, ui) {
ui.position.left = 0;
ui.position.top = 0;
containmentW = $container.width() * _zoom;
containmentH = $container.height() * _zoom;
objW = $(this).outerWidth() * _zoom;
objH = $(this).outerHeight() * _zoom;
},
drag: function(evt, ui) {
var boundReached = false,
changeLeft = ui.position.left - ui.originalPosition.left,
newLeft = ui.originalPosition.left + changeLeft / _zoom,
changeTop = ui.position.top - ui.originalPosition.top,
newTop = ui.originalPosition.top + changeTop / _zoom;
// right bound check
if(ui.position.left > containmentW - objW) {
newLeft = (containmentW - objW) / _zoom;
boundReached = true;
}
// left bound check
if(newLeft < 0) {
newLeft = 0;
boundReached = true;
}
// bottom bound check
if(ui.position.top > containmentH - objH) {
newTop = (containmentH - objH) / _zoom;
boundReached = true;
}
// top bound check
if(newTop < 0) {
newTop = 0;
boundReached = true;
}
// fix position
ui.position.left = newLeft;
ui.position.top = newTop;
// inside bounds
if(!boundReached) {
// do stuff when element is dragged inside bounds
}
}
});
Link to fiddle
I don't know how to find the place part (one of 4 triangles) of a cursor in a rectangle.
This image is more efficient than my explication :s
Im in javascript (so the rectangle is a DIV, 0,0 placed)
I have those varaibles :
var cursor_x = e.clientX + $(document).scrollLeft()
var cursor_y = e.clientY + $(document).scrollTop()
var rect_w = $( rectangle ).width()
var rect_h = $( rectangle ).height()
I just want to know mathematically where is the cursor, in the triangle 1, 2, 3 or 4
What I think is the easiest way is to first normalize y so the computation is the same as for a square and then check for on which side of the diagonals you are...
var ynorm = y * w / h;
var s1 = x > ynorm ? 0 : 1;
var s2 = (w - x) > ynorm ? 0 : 1;
var area = s1*2 + s2;
the final area variable is a number between 0 and 3 telling in which of the four parts you are.
#6502: Thk you, its very helpful.
For more info, im working on an experimental light sortable jquery plugin, that can work with floating placement (top, left, right, bottom)
the code :
simply use $( ..selector.. ).sortable({ items: ..selector.. })
-
$.fn.sortable = function( o ) {
o.self = this;
o.helper = null;
$(document).bind('mouseup.sortable', function(e) {
if( o.sortable ) {
o.sortable.css({ opacity: ''});
if( o.target ) {
if( o.area == 's' ) {
o.sortable.css({ float: '' })
}
else if( o.area == 'n' ) {
o.sortable.css({ float: '' })
o.target.css({ float: '' })
}
else if( o.area == 'w' ) {
o.target.css({ float: 'left' })
o.sortable.css({ float: 'left' })
}
else if( o.area == 'e' ) {
o.target.css({ float: 'left' })
o.sortable.css({ float: 'left' })
}
o.target[ o.area == 's' || o.area == 'e' ? 'before':'after']( o.sortable );
o.target[0].style.setProperty( 'cursor', false , false);
o.target = null;
}
o.helper.remove();
o.sortable = null;
}
}).bind('mousemove.sortable', function(e) {
if( o.sortable ) {
o.ex = e.clientX + $(document).scrollLeft() + 10
o.ey = e.clientY + $(document).scrollTop() - o.sortable[0]._height - 10
o.helper.css({ left: o.ex, top: o.ey });
}
});
return $( this.selector ).delegate( o.items, 'mousemove.sortable', function(e) {
if( o.sortable && o.sortable[0] != this ) {
var self = $(this)
var x = e.clientX + $(document).scrollLeft() - self.offset().left
var y = e.clientY + $(document).scrollTop() - self.offset().top
var w = self.width()
var h = self.height()
var ynorm = y * w / h;
o.area = (w - x) > ynorm ? ( x > ynorm ? 's':'e' ) : ( x > ynorm ? 'w':'n' );
this.style.setProperty( 'cursor', o.area+'-resize', 'important');
o.target = self;
}
}).delegate( o.items, 'mousedown.sortable', function( e ) {
o.sortable = $(this).css({ opacity: 0.4 });
this._width = o.sortable.width();
this._height = o.sortable.height();
o.helper = o.sortable.clone().css({ position: 'absolute', left: -99999, top: 0 })
$('body').append( o.helper )
return false;
});
}
I want to know how to get the X and Y position of HTML elements such as img and div in JavaScript.
The correct approach is to use element.getBoundingClientRect():
var rect = element.getBoundingClientRect();
console.log(rect.top, rect.right, rect.bottom, rect.left);
Internet Explorer has supported this since as long as you are likely to care about and it was finally standardized in CSSOM Views. All other browsers adopted it a long time ago.
Some browsers also return height and width properties, though this is non-standard. If you're worried about older browser compatibility, check this answer's revisions for an optimised degrading implementation.
The values returned by element.getBoundingClientRect() are relative to the viewport. If you need it relative to another element, simply subtract one rectangle from the other:
var bodyRect = document.body.getBoundingClientRect(),
elemRect = element.getBoundingClientRect(),
offset = elemRect.top - bodyRect.top;
alert('Element is ' + offset + ' vertical pixels from <body>');
This function returns an element's position relative to the whole document (page):
function getOffset(el) {
const rect = el.getBoundingClientRect();
return {
left: rect.left + window.scrollX,
top: rect.top + window.scrollY
};
}
Using this we can get the X position:
getOffset(element).left
... or the Y position:
getOffset(element).top
The libraries go to some lengths to get accurate offsets for an element.
here's a simple function that does the job in every circumstances that I've tried.
function getOffset( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
var x = getOffset( document.getElementById('yourElId') ).left;
If you want it done only in javascript, here are some one liners using getBoundingClientRect()
window.scrollY + document.querySelector('#elementId').getBoundingClientRect().top // Y
window.scrollX + document.querySelector('#elementId').getBoundingClientRect().left // X
The first line will return offsetTop say Y relative to document.
The second line will return offsetLeft say X relative to document.
getBoundingClientRect() is a javascript function that returns the position of the element relative to viewport of window.
HTML elements on most browsers will have:-
offsetLeft
offsetTop
These specifiy the position of the element relative its nearest parent that has layout. This parent can often be accessed bif the offsetParent property.
IE and FF3 have
clientLeft
clientTop
These properties are less common, they specify an elements position with its parents client area (padded area is part of the client area but border and margin is not).
If page includes - at least- any "DIV", the function given by meouw throws the "Y" value beyond current page limits. In order to find the exact position, you need to handle both offsetParent's and parentNode's.
Try the code given below (it is checked for FF2):
var getAbsPosition = function(el){
var el2 = el;
var curtop = 0;
var curleft = 0;
if (document.getElementById || document.all) {
do {
curleft += el.offsetLeft-el.scrollLeft;
curtop += el.offsetTop-el.scrollTop;
el = el.offsetParent;
el2 = el2.parentNode;
while (el2 != el) {
curleft -= el2.scrollLeft;
curtop -= el2.scrollTop;
el2 = el2.parentNode;
}
} while (el.offsetParent);
} else if (document.layers) {
curtop += el.y;
curleft += el.x;
}
return [curtop, curleft];
};
You can add two properties to Element.prototype to get the top/left of any element.
Object.defineProperty( Element.prototype, 'documentOffsetTop', {
get: function () {
return this.offsetTop + ( this.offsetParent ? this.offsetParent.documentOffsetTop : 0 );
}
} );
Object.defineProperty( Element.prototype, 'documentOffsetLeft', {
get: function () {
return this.offsetLeft + ( this.offsetParent ? this.offsetParent.documentOffsetLeft : 0 );
}
} );
This is called like this:
var x = document.getElementById( 'myDiv' ).documentOffsetLeft;
Here's a demo comparing the results to jQuery's offset().top and .left: http://jsfiddle.net/ThinkingStiff/3G7EZ/
To retrieve the position relative to the page efficiently, and without using a recursive function: (includes IE also)
var element = document.getElementById('elementId'); //replace elementId with your element's Id.
var rect = element.getBoundingClientRect();
var elementLeft,elementTop; //x and y
var scrollTop = document.documentElement.scrollTop?
document.documentElement.scrollTop:document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft?
document.documentElement.scrollLeft:document.body.scrollLeft;
elementTop = rect.top+scrollTop;
elementLeft = rect.left+scrollLeft;
How about something like this, by passing ID of the element and it will return the left or top, we can also combine them:
1) find left
function findLeft(element) {
var rec = document.getElementById(element).getBoundingClientRect();
return rec.left + window.scrollX;
} //call it like findLeft('#header');
2) find top
function findTop(element) {
var rec = document.getElementById(element).getBoundingClientRect();
return rec.top + window.scrollY;
} //call it like findTop('#header');
or 3) find left and top together
function findTopLeft(element) {
var rec = document.getElementById(element).getBoundingClientRect();
return {top: rec.top + window.scrollY, left: rec.left + window.scrollX};
} //call it like findTopLeft('#header');
Here's a modern 1-liner using vanilla JS to recursively iterate over element.offsetTop and element.offsetParent:
Function:
getTop = el => el.offsetTop + (el.offsetParent && getTop(el.offsetParent))
Usage:
const el = document.querySelector('#div_id');
const elTop = getTop(el)
Advantage:
Always returns the absolute vertical offset, regardless of the current scroll position.
Traditional syntax:
function getTop(el) {
return el.offsetTop + (el.offsetParent && getTop(el.offsetParent));
}
jQuery .offset() will get the current coordinates of the first element, or set the coordinates of every element, in the set of matched elements, relative to the document.
Update:
The recursion approach (in my old answer) creates many call stacks. We can use a while loop to avoid recursion in this case:
/**
*
* #param {HTMLElement} el
* #return {{top: number, left: number}}
*/
function getDocumentOffsetPosition(el) {
let top = 0, left = 0;
while (el !== null) {
top += el.offsetTop;
left += el.offsetLeft;
el = el.offsetParent;
}
return {top, left};
}
Old answer:
/**
*
* #param {HTMLElement} el
* #return {{top: number, left: number}}
*/
function getDocumentOffsetPosition(el) {
var position = {
top: el.offsetTop,
left: el.offsetLeft
};
if (el.offsetParent) {
var parentPosition = getDocumentOffsetPosition(el.offsetParent);
position.top += parentPosition.top;
position.left += parentPosition.left;
}
return position;
}
Thank ThinkingStiff for the answer, this is only another version.
You might be better served by using a JavaScript framework, that has functions to return such information (and so much more!) in a browser-independant fashion. Here are a few:
Prototype
jQuery
MooTools
YUI (yahoo)
With these frameworks, you could do something like:
$('id-of-img').top
to get the y-pixel coordinate of the image.
I've taken #meouw's answer, added in the clientLeft that allows for the border, and then created three versions:
getAbsoluteOffsetFromBody - similar to #meouw's, this gets the absolute position relative to the body or html element of the document (depending on quirks mode)
getAbsoluteOffsetFromGivenElement - returns the absolute position relative to the given element (relativeEl). Note that the given element must contain the element el, or this will behave the same as getAbsoluteOffsetFromBody. This is useful if you have two elements contained within another (known) element (optionally several nodes up the node tree) and want to make them the same position.
getAbsoluteOffsetFromRelative - returns the absolute position relative to the first parent element with position: relative. This is similar to getAbsoluteOffsetFromGivenElement, for the same reason but will only go as far as the first matching element.
getAbsoluteOffsetFromBody = function( el )
{ // finds the offset of el from the body or html element
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) )
{
_x += el.offsetLeft - el.scrollLeft + el.clientLeft;
_y += el.offsetTop - el.scrollTop + el.clientTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
getAbsoluteOffsetFromGivenElement = function( el, relativeEl )
{ // finds the offset of el from relativeEl
var _x = 0;
var _y = 0;
while( el && el != relativeEl && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) )
{
_x += el.offsetLeft - el.scrollLeft + el.clientLeft;
_y += el.offsetTop - el.scrollTop + el.clientTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
getAbsoluteOffsetFromRelative = function( el )
{ // finds the offset of el from the first parent with position: relative
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) )
{
_x += el.offsetLeft - el.scrollLeft + el.clientLeft;
_y += el.offsetTop - el.scrollTop + el.clientTop;
el = el.offsetParent;
if (el != null)
{
if (getComputedStyle !== 'undefined')
valString = getComputedStyle(el, null).getPropertyValue('position');
else
valString = el.currentStyle['position'];
if (valString === "relative")
el = null;
}
}
return { top: _y, left: _x };
}
If you are still having problems, particularly relating to scrolling, you could try looking at http://www.greywyvern.com/?post=331 - I noticed at least one piece of questionable code in getStyle which should be fine assuming browsers behave, but haven't tested the rest at all.
Difference between small and little
function getPosition( el ) {
var x = 0;
var y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
x += el.offsetLeft - el.scrollLeft;
y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: y, left: x };
}
Look a example coordinates:
http://javascript.info/tutorial/coordinates
If you are using jQuery, this could be a simple solution:
<script>
var el = $("#element");
var position = el.position();
console.log( "left: " + position.left + ", top: " + position.top );
</script>
if using jQuery, the dimensions plugin is excellent and allows you specify exactly what you want.
e.g.
Relative position, absolute position, absolute position without padding, with padding...
It goes on, let's just say there is a lot you can do with it.
Plus the bonus of using jQuery is it's lightweight file size and easy use, you won't go back to JavaScript without it afterwards.
The cleanest approach I have found is a simplified version of the technique used by jQuery's offset. Similar to some of the other answers it starts with getBoundingClientRect; it then uses the window and the documentElement to adjust for scroll position as well as things like the margin on the body (often the default).
var rect = el.getBoundingClientRect();
var docEl = document.documentElement;
var rectTop = rect.top + window.pageYOffset - docEl.clientTop;
var rectLeft = rect.left + window.pageXOffset - docEl.clientLeft;
var els = document.getElementsByTagName("div");
var docEl = document.documentElement;
for (var i = 0; i < els.length; i++) {
var rect = els[i].getBoundingClientRect();
var rectTop = rect.top + window.pageYOffset - docEl.clientTop;
var rectLeft = rect.left + window.pageXOffset - docEl.clientLeft;
els[i].innerHTML = "<b>" + rectLeft + ", " + rectTop + "</b>";
}
div {
width: 100px;
height: 100px;
background-color: red;
border: 1px solid black;
}
#rel {
position: relative;
left: 10px;
top: 10px;
}
#abs {
position: absolute;
top: 250px;
left: 250px;
}
<div id="rel"></div>
<div id="abs"></div>
<div></div>
To get the total offset of an element, you could recursively sum up all parent offsets:
function getParentOffset(el): number {
if (el.offsetParent) {
return el.offsetParent.offsetTop + getParentOffset(el.offsetParent);
} else {
return 0;
}
}
with this utility function the total top offset of a dom element is:
el.offsetTop + getParentOffset(el);
This is the best code I've managed to create (works in iframes as well, unlike jQuery's offset()). Seems webkit has a bit of a different behavior.
Based on meouw's comment:
function getOffset( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
// chrome/safari
if ($.browser.webkit) {
el = el.parentNode;
} else {
// firefox/IE
el = el.offsetParent;
}
}
return { top: _y, left: _x };
}
While this is very likely to be lost at the bottom of so many answers, the top solutions here were not working for me.
As far as I could tell neither would any of the other answers have helped.
Situation:
In an HTML5 page I had a menu that was a nav element inside a header (not THE header but a header in another element).
I wanted the navigation to stick to the top once a user scrolled to it, but previous to this the header was absolute positioned (so I could have it overlay something else slightly).
The solutions above never triggered a change because .offsetTop was not going to change as this was an absolute positioned element. Additionally the .scrollTop property was simply the top of the top most element... that is to say 0 and always would be 0.
Any tests I performed utilizing these two (and same with getBoundingClientRect results) would not tell me if the top of the navigation bar ever scrolled to the top of the viewable page (again, as reported in console, they simply stayed the same numbers while scrolling occurred).
Solution
The solution for me was utilizing
window.visualViewport.pageTop
The value of the pageTop property reflects the viewable section of the screen, therefore allowing me to track where an element is in reference to the boundaries of the viewable area.
Probably unnecessary to say, anytime I am dealing with scrolling I expect to use this solution to programatically respond to movement of elements being scrolled.
Hope it helps someone else.
IMPORTANT NOTE: This appears to work in Chrome and Opera currently & definitely not in Firefox (6-2018)... until Firefox supports visualViewport I recommend NOT using this method, (and I hope they do soon... it makes a lot more sense than the rest).
UPDATE:
Just a note regarding this solution. While I still find what I discovered to be very valuable for situations in which "...programmatically respond to movement of elements being scrolled." is applicable. The better solution for the problem that I had was to use CSS to set position: sticky on the element. Using sticky you can have an element stay at the top without using javascript (NOTE: there are times this will not work as effectively as changing the element to fixed but for most uses the sticky approach will likely be superior)
UPDATE01:
So I realized that for a different page I had a requirement where I needed to detect the position of an element in a mildly complex scrolling setup (parallax plus elements that scroll past as part of a message).
I realized in that scenario that the following provided the value I utilized to determine when to do something:
let bodyElement = document.getElementsByTagName('body')[0];
let elementToTrack = bodyElement.querySelector('.trackme');
trackedObjPos = elementToTrack.getBoundingClientRect().top;
if(trackedObjPos > 264)
{
bodyElement.style.cssText = '';
}
Hope this answer is more widely useful now.
I did it like this so it was cross-compatible with old browsers.
// For really old browser's or incompatible ones
function getOffsetSum(elem) {
var top = 0,
left = 0,
bottom = 0,
right = 0
var width = elem.offsetWidth;
var height = elem.offsetHeight;
while (elem) {
top += elem.offsetTop;
left += elem.offsetLeft;
elem = elem.offsetParent;
}
right = left + width;
bottom = top + height;
return {
top: top,
left: left,
bottom: bottom,
right: right,
}
}
function getOffsetRect(elem) {
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop;
var clientLeft = docElem.clientLeft;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
var bottom = top + (box.bottom - box.top);
var right = left + (box.right - box.left);
return {
top: Math.round(top),
left: Math.round(left),
bottom: Math.round(bottom),
right: Math.round(right),
}
}
function getOffset(elem) {
if (elem) {
if (elem.getBoundingClientRect) {
return getOffsetRect(elem);
} else { // old browser
return getOffsetSum(elem);
}
} else
return null;
}
More about coordinates in JavaScript here: http://javascript.info/tutorial/coordinates
HTML program to show (x, y) of an
element by dragging mouse over it you just copied it and use it on your own
<!DOCTYPE html>
<html>
<head>
<title>
position of an element
</title>
<!-- scropt to get position -->
<script type = "text/javascript">
function getPositionXY(element) {
var rect = element.getBoundingClientRect();
document.getElementById('text').innerHTML
= 'X: ' + rect.x + '<br>' + 'Y: ' + rect.y;
}
</script>
</head>
<body>
<p>Move the mouse over the text</p>
<div onmouseover = "getPositionXY(this)">
Position:
<p id = 'text'></p>
</div>
</body>
</html>
i could just like element.offsetLeft or element.offsetTop. Example :
document.getElementById('profileImg').offsetLeft
I successfully used Andy E's solution to position a bootstrap 2 modal depending on what link in a table row a user clicks on. The page is a Tapestry 5 page and javascript below is imported in the java page class.
javascript:
function setLinkPosition(clientId){
var bodyRect = document.body.getBoundingClientRect(),
elemRect = clientId.getBoundingClientRect(),
offset = elemRect.top - bodyRect.top;
offset = offset + 20;
$('#serviceLineModal').css("top", offset);
}
My modal code:
<div id="serviceLineModal" class="modal hide fade add-absolute-position" data-backdrop="static"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="top:50%;">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button>
<h3 id="myModalLabel">Modal header</h3>
</div>
<div class="modal-body">
<t:zone t:id="modalZone" id="modalZone">
<p>You selected service line number: ${serviceLineNumberSelected}</p>
</t:zone>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<!-- <button class="btn btn-primary">Save changes</button> -->
</div>
The link in the loop:
<t:loop source="servicesToDisplay" value="service" encoder="encoder">
<tr style="border-right: 1px solid black;">
<td style="white-space:nowrap;" class="add-padding-left-and-right no-border">
<a t:type="eventLink" t:event="serviceLineNumberSelected" t:context="service.serviceLineNumber"
t:zone="pageZone" t:clientId="modalLink${service.serviceLineNumber}"
onmouseover="setLinkPosition(this);">
<i class="icon-chevron-down"></i> <!-- ${service.serviceLineNumber} -->
</a>
</td>
And the java code in the page class:
void onServiceLineNumberSelected(String number){
checkForNullSession();
serviceLineNumberSelected = number;
addOpenServiceLineDialogCommand();
ajaxResponseRenderer.addRender(modalZone);
}
protected void addOpenServiceLineDialogCommand() {
ajaxResponseRenderer.addCallback(new JavaScriptCallback() {
#Override
public void run(JavaScriptSupport javascriptSupport) {
javascriptSupport.addScript("$('#serviceLineModal').modal('show');");
}
});
}
Hope this helps someone, this post helped out.
After much research and testing this seems to work
function getPosition(e) {
var isNotFirefox = (navigator.userAgent.toLowerCase().indexOf('firefox') == -1);
var x = 0, y = 0;
while (e) {
x += e.offsetLeft - e.scrollLeft + (isNotFirefox ? e.clientLeft : 0);
y += e.offsetTop - e.scrollTop + (isNotFirefox ? e.clientTop : 0);
e = e.offsetParent;
}
return { x: x + window.scrollX, y: y + window.scrollY };
}
see http://jsbin.com/xuvovalifo/edit?html,js,output
Just thought I'd throw this out there as well.
I haven't been able to test it in older browsers, but it works in the latest of the top 3. :)
Element.prototype.getOffsetTop = function() {
return ( this.parentElement )? this.offsetTop + this.parentElement.getOffsetTop(): this.offsetTop;
};
Element.prototype.getOffsetLeft = function() {
return ( this.parentElement )? this.offsetLeft + this.parentElement.getOffsetLeft(): this.offsetLeft;
};
Element.prototype.getOffset = function() {
return {'left':this.getOffsetLeft(),'top':this.getOffsetTop()};
};
This is easy as two lines in JS :
var elem = document.getElementById("id");
alert(elem.getBoundingClientRect());
Since different browsers are rendering border, padding, margin and etc in different way. I wrote a little function to retrieve top and left positions of specific element in every root element that you want in precise dimension:
function getTop(root, offset) {
var rootRect = root.getBoundingClientRect();
var offsetRect = offset.getBoundingClientRect();
return offsetRect.top - rootRect.top;
}
For retrieve left position you must return:
return offsetRect.left - rootRect.left;
Get position of div in respect to left and Top
var elm = $('#div_id'); //get the div
var posY_top = elm.offset().top; //get the position from top
var posX_left = elm.offset().left; //get the position from left