Related
THE PROBLEM
I'm having a minor problem dragging elements onto a scalable div container.
Once the element is actually in the container, the elements drag fine and work the way they are supposed to.
Larger elements that are dragged onto the scalable container don't have too much of an issue.
But when smaller elements are dragged, you can see that the mouse is no longer attached to said element and when it is dropped, it drops a little off where it is supposed to drop.
I'm trying to find a solution that my mouse stays on the element and it drops where it is supposed to drop.
I've solved problems bit by bit and you can see below but this is the last piece of the puzzle that's driving me mad. If anyone has the time to lend a hand, it would be greatly appreciated.
Here is a codepen - click and drag the two blue elements onto the white container to try it out
Codepen
Full Screen View
Short GIF in Action
This wil help making sure that the droppable area works with a scaled container.
$.ui.ddmanager.prepareOffsets = function(t, event) {
var i, j, m = $.ui.ddmanager.droppables[t.options.scope] || [],
type = event ? event.type : null,
list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
droppablesLoop: for (i = 0; i < m.length; i++) {
if (m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0], (t.currentItem || t.element)))) {
continue;
}
for (j = 0; j < list.length; j++) {
if (list[j] === m[i].element[0]) {
m[i].proportions().height = 0;
continue droppablesLoop;
}
}
m[i].visible = m[i].element.css("display") !== "none";
if (!m[i].visible) {
continue;
}
if (type === "mousedown") {
m[i]._activate.call(m[i], event);
}
m[i].offset = m[i].element.offset();
m[i].proportions({
width: m[i].element[0].offsetWidth * percent,
height: m[i].element[0].offsetHeight * percent
});
}
};
Enable the element to be resizable on a scaled container
function resizeFix(event, ui) {
var changeWidth = ui.size.width - ui.originalSize.width,
newWidth = ui.originalSize.width + changeWidth / percent,
changeHeight = ui.size.height - ui.originalSize.height,
newHeight = ui.originalSize.height + changeHeight / percent;
ui.size.width = newWidth;
ui.size.height = newHeight;
}
Makes it so drag works on a scaled container
function dragFix(event, ui) {
var containmentArea = $("#documentPage_"+ui.helper.parent().parent().attr('id').replace(/^(\w+)_/, "")),
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()));
}
Creating a draggable element that I can drag onto the box.
.directive('draggableTypes', function() {
return {
restrict:'A',
link: function(scope, element, attrs) {
element.draggable({
zIndex:3000,
appendTo: 'body',
helper: function(e, ui){
var formBox = angular.element($("#formBox"));
percent = formBox.width() / scope.templateData.pdf_width;
if(element.attr('id') == 'textbox_item')
return $('<div class="text" style="text-align:left;font-size:14px;width:200px;height:20px;line-height:20px;">New Text Box.</div>').css({ 'transform': 'scale(' + percent + ')', '-moz-transform': 'scale(' + percent + ')', '-webkit-transform': 'scale(' + percent + ')', '-ms-transform': 'scale(' + percent + ')'});
if(element.attr('id') == 'sm_textbox_item')
return $('<div class="text" style="text-align:left;font-size:14px;width:5px;height:5px;line-height:20px;"></div>').css({ 'transform': 'scale(' + percent + ')', '-moz-transform': 'scale(' + percent + ')', '-webkit-transform': 'scale(' + percent + ')', '-ms-transform': 'scale(' + percent + ')'});
}
});
}
};
})
Create draggable/resizable elements that may already be in the box and applying the drag/resize fix to these
.directive('textboxDraggable', function() {
return {
restrict:'A',
link: function(scope, element, attrs) {
element.draggable({
cursor: "move",
drag: dragFix,
start: function(event, ui) {
var activeId = element.attr('id');
scope.activeElement.id = activeId;
scope.activeElement.name = scope.templateItems[activeId].info.name;
scope.$apply();
}
});
element.resizable({
minWidth: 25,
minHeight: 25,
resize: resizeFix,
stop: function( event, ui ) {
var activeId = element.attr('id');
scope.activeElement.duplicateName = false;
scope.activeElement.id = activeId;
scope.activeElement.name = scope.templateItems[activeId].info.name;
scope.templateItems[activeId]['style']['width'] = element.css('width');
scope.templateItems[activeId]['style']['height'] = element.css('height');
scope.$apply();
}
})
}
};
})
What happens when an item is dropped
.directive('droppable', function($compile) {
return {
restrict: 'A',
link: function(scope,element,attrs){
element.droppable({
drop:function(event,ui) {
var draggable = angular.element(ui.draggable),
draggable_parent = draggable.parent().parent(),
drag_type = draggable.attr('id'),
documentBg = element,
x = ui.offset.left,
y = ui.offset.top,
element_top = (y - documentBg.offset().top - draggable.height() * (percent - 1) / 2) / percent,
element_left = (x - documentBg.offset().left - draggable.width() * (percent - 1) / 2) / percent,
timestamp = new Date().getTime();
//just get the document page of where the mouse is if its a new element
if(draggable_parent.attr('id') == 'template_builder_box_container' || draggable_parent.attr('id') == 'template_builder_container')
var documentPage = documentBg.parent().parent().attr('id').replace(/^(\w+)_/, "");
//if you are dragging an element that was already on the page, get parent of draggable and not parent of where mouse is
else var documentPage = draggable_parent.parent().parent().attr('id').replace(/^(\w+)_/, "");
if(drag_type == "textbox_item")
{
scope.activeElement.id = scope.templateItems.push({
info: {'page': documentPage,'name': 'textbox_'+timestamp, 'type': 'text'},
style: {'text-align':'left','font-size':'14px','top':element_top+'px','left':element_left+'px', 'width':'200px', 'height':'20px'}
}) - 1;
scope.activeElement.name = 'textbox_'+timestamp;
}
else if(drag_type == "sm_textbox_item")
{
scope.activeElement.id = scope.templateItems.push({
info: {'page': documentPage,'name': '', 'type': 'text'},
style: {'text-align':'left','font-size':'14px','top':element_top+'px','left':element_left+'px', 'width':'5px', 'height':'5px'}
}) - 1;
scope.activeElement.name = 'textbox_'+timestamp;
}
else {
scope.templateItems[scope.activeElement.id]['style']['top'] = draggable.css('top');
scope.templateItems[scope.activeElement.id]['style']['left'] = draggable.css('left');
}
scope.$apply();
}
});
}
};
})
last but not least, my controller
.controller('testing', function($scope, $rootScope, $state, $stateParams) {
$scope.templateItems = [];
$scope.activeElement = { id: undefined, name: undefined };
$scope.templateData = {"id":"12345", "max_pages":1,"pdf_width":385,"pdf_height":800};
$scope.clickElement = function(index) { $scope.activeElement = { id: index, name: $scope.templateItems[index].info.name } }
});
Here is the basis of my html
<div id="formBox" ng-style="formbox(templateData.pdf_width)" zoom>
<div class="trimSpace" ng-style="trimSpace(templateData.pdf_width)" zoom>
<div id="formScale" ng-style="formScale(templateData.pdf_width)" zoom>
<form action="#" id="{{ templateData.id }}_form">
<div ng-repeat="key in [] | range:templateData.max_pages">
<div class="formContainer" id="{{ templateData.id + '_' + (key+1) }}" ng-style="{width: templateData.pdf_width+'px', height: templateData.pdf_height+'px'}">
<div class="formContent">
<div class="formBackground" id="documentPage_{{ (key+1) }}" droppable>
<div ng-hide="preview" ng-repeat="item in templateItems">
<div ng-if="item.info.page == (key+1) && item.info.type == 'text'" id="{{ $index }}" data-type="{{ item.info.type }}" ng-click="clickElement($index)" class="text" ng-style="item.style" textbox-draggable>{{ item.info.name }}</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
For the cursor position while dragging, see this answer : Make Cursor position in center for ui.helper in jquery-ui draggable method
Basically, you can control the cursor position of the instance, allowing to have something more dynamic that cursorAt. Like this:
start: function(event, ui){
$(this).draggable('instance').offset.click = {
left: Math.floor(ui.helper.width() / 2),
top: Math.floor(ui.helper.height() / 2)
}
},
Then on the drop, you need to take into account the transform, but you can simplify by using the helper coordinates instead of the draggable. Like this:
element_top = (ui.helper.offset().top / percent) - (documentBg.offset().top / percent);
element_left = (ui.helper.offset().left / percent) - (documentBg.offset().left / percent);
Result: https://codepen.io/anon/pen/jamLBq
It looks like what is causing this to look strange is the following:
First, the small div is styled as display: block. This means that even though it looks like the div is small, that element actually stretches out to it's whole container.
Second, once you show the dragged square on the left screen, the relation between the mouse cursor and the element whole is technically centered, but you are cutting the size of the original element to a smaller one, and when the width and height get diminished, the result is rendered with the new width and height starting from the upper left corner of the original div. (If you style the small button to be display: inline, you can see what I mean. Try grabbing it from the upper left corner and the try the lower right one. You will see that the former looks fine but the latter is off).
So my suggestions are:
Make the draggabble elements display: inline
Make the dragged element on the left screen the exact height and width of the original element on the right screen.
Hope that helps!
I've forked your codepen and played around with it.
Take a look at it HERE, and see if it helps you find the "bug".
For your draggable script, I changed the code to this, adding margin-left and margin-right:
if(element.attr('id') == 'sm_textbox_item') { /* the small draggable box */
var el = {
pos: element.offset(), // position of the small box
height: element.outerHeight() + 20,
left: 0
}
var deduct = $('#formBox').innerWidth() - 20; // width of the element that's left of small box's container
el.left = el.pos.left - deduct;
return $('<div class="text" style="text-align:left; font-size:14px; width:5px; height:5px; line-height:20px;"></div>')
.css({
'margin-left': el.left + 'px',
'margin-top': el.pos.top - el.height + 'px',
'transform': 'scale(' + percent + ')',
'-moz-transform': 'scale(' + percent + ')',
'-webkit-transform': 'scale(' + percent + ')',
'-ms-transform': 'scale(' + percent + ')'
});
}
Then, for your droppable script, I changed the formula for element_top and element_left:
// old formula
element_top = (y - documentBg.offset().top - draggable.height() * (percent - 1) / 2) / percent
element_left = (x - documentBg.offset().left - draggable.width() * (percent - 1) / 2) / percent
// new formula
element_top = (y - documentBg.offset().top) / (percent * 0.915)
element_left = (x - documentBg.offset().left) / (percent * 0.915)
It gives an "almost" accurate result, but you may be able to tweak it further to polish it. Hope this helps.
For attaching elements with cursor during dragging you just need to use
cursorAt: { top: 6, left: -100 }
And a little change in top and left parameters of "sm_textbox_item".
top: (y - documentBg.offset().top) / (percent) + "px",
left: (x - documentBg.offset().left) / (percent) + "px",
For the large box again some tweak in top and left element is required (pen updated).
top: element_top-3,
left: element_left+6.49,
I forked your pen and did some changes. I know that this is not a perfect solution, i am also trying to solve this bit by bit. You can check it here
#ITWitch is right, there have to be some bug in draggable().
Style margin: 0 auto; in #sm_textbox_item is the source of problem.
Try to add this to draggable options in your draggableType directive to correct the position:
cursorAt: {left: -parseInt(window.getComputedStyle(element[0],null,null)['margin-left'])},
This problem occurs when you add a transform to a element's style, then make it draggable. You'll have to make do without transform to have a perfect result. I spent 2 days debugging till I found it out, and I didn't want someone else to go through that pain.
I'm trying to position the center of a div element to the center of the mouse cursor, that will follow along its movements.
Already I came up with the code below, but the problem with this one is, that the following div is not positioned at the center of my cursor, but with some offset off the cursor.
WORKFLOW
The basic idea behind my code is, when the mouse enters the .post-entry div element, the .pointer within the current item should be displayed and follow the cursor of the mouse. When the mouse leaves the div it should be hidden.
CODE
HTML post item:
<article class="col-md-4 col-sm-6 post-entry">
<a href="#" title="">
<figure class="post-thumb">
<img src="http://placehold.it/300x300" alt="">
<div class="pointer" style="background: red;"></div>
</figure><!-- End figure.post-thumb -->
</a>
</article><!-- End article.col-md-4 post-entry -->
JS:
$('.entry .post-entry').each(function() {
$(this).on("mouseenter", mouseEnter);
$(this).on("mousemove", mouseMove);
$(this).on("mouseleave", mouseLeave);
});
function mouseEnter(event) {
console.log('enter');
var target = $(this);
var dot = target.find('.pointer');
var mX = (event.clientX);
var mY = (event.clientY);
set(
dot, {
x: mX,
y: mY,
force3D: !0
}
);
};
function mouseMove(event) {
console.log('move');
var target = $(this);
var dot = target.find('.pointer');
// var offset = target.offset();
// var width = target.width();
// var height = target.height();
// var top = offset.top;
// var left = offset.left;
var mX = (event.clientX);
var mY = (event.clientY);
$(dot).css('-webkit-transform', 'translate3d(' + mX + 'px, ' + mY + 'px, 0)');
};
function mouseLeave(event) {
console.log('leave');
var target = $(this);
var dot = target.find('.pointer');
$(dot).css('-webkit-transform', 'translate3d(0, 0, 0) scale(0, 0)');
};
function onClick(event) {
event.preventDefault();
console.log('click');
};
function set(el, obj) {
var dot = $(el).css('-webkit-transform', 'translate3d(' + obj.x + 'px, ' + obj.y + 'px, 0px)');
return dot;
};
PROBLEM / DEMO
As mentioned before, the span is following the mouse cursor, only the span is not positioned to the center of the cursor. It will be offset the mouse. See live demo here
I tried already something like this for the mX and mY variables, but with no succes:
var mX = (event.clientX - $(this).offset().left) / $(this).width() * $(this).width() - .125 * $(this).width();
var mY = (event.clientY - $(this).offsetTop) / $(this).height() * $(this).height() - .125 * $(this).width();
Also the answer from #hiEven doesn't work and will let me with the same issue:
transform: calc(mX - 50%, mY - 50%)
I know I should do something with dividing the .pointer by half, but how I should implement that in the code is a big question mark for me.
UPDATE
I created two new Codepen projects:
Use without images: http://codepen.io/anon/pen/GqGOLv. When you hover over the first item you will see that the brown pointer is correctly following your mouse cursor - what I am looking for. But when hovering over the second one, you will see the red pointer, only when you are at the very left side of the item.
When I use images: http://codepen.io/anon/pen/QExOkx. The problem by this example is that when you at the very top of the first column, you will see the brown pointer. When hover at the top left corner of the second item you will see a little piece of the red pointer, the same as the example without images.
Both pointer should follow the mouse cursor correctly. And I am searching for a solution that works with the use of an image.
Beside these two examples, when I add to the first one, an extra margin-left to the first item, the brown pointer will not be in the center of the mouse cursor, only when it's set to margin-left zero.
So I don't know what's missing and why it only works with the first example (without images) and only for the first item?
Try the code below
<html>
<head>
<style>
#mouse_div{
position: absolute;
background-color: black;
}
</style>
<script>
var div_width = 100;
var div_height = 100;
var div_x, div_y;
function mouse_position(event){
var mouse_x = event.clientX;
var mouse_y = event.clientY;
document.getElementById("mouse_div").style.width = div_width + "px";
document.getElementById("mouse_div").style.height = div_height + "px";
div_x = mouse_x - (div_width / 2);
div_y = mouse_y - (div_height / 2);
document.getElementById("mouse_div").style.left = div_x + "px";
document.getElementById("mouse_div").style.top = div_y + "px";
}
</script>
</head>
<body onmousemove="mouse_position(event)" onload="mouse_position(event)">
<div id="mouse_div"></div>
</body>
</html>
This program gets the position of your mouse, the width, and the height of the div. Then, it takes the x and subtracts the div's width divided by two from it (this centres the div's x position on your mouse). The program then does the same thing for the mouse y. Once all of the variables are defined, I use JavaScript to access the CSS of the div to place the div where it needs to be.
Note: you must make sure that the position of the div is set to absolute or the program will not work.
I assume you want the circle being center of your mouse, right?
try do this
transform: `translate(calc(${mx}px - 50%), calc(${my}px - 50%))
here is the demo
Based on my latest update, I did not conform to the correct formula that is needed to center the element .pointer to the mouse.
In order to use the following calculation within mouseMove:
var mX = (event.clientX);
var mY = (event.clientY);
Should be changed to this:
var height = dot.height();
var width = dot.width();
var offset = target.offset();
var w = target.width();
var h = target.height();
var top = offset.top;
var left = offset.left;
var mX = (event.clientX - left) - width / 2 - 15; // 15 = padding
var mY = (event.clientY - top) - height / 2;
So this formule is considering that the following DOM element .pointer will follow the mouse movements of the user. I don't know exactly why this working, but the offset from the previous item will be decreased from the current clientX coordinates, so the position of the second item is reset to zero, so the pointer will start at the left side of each item.
Here is a working demo of above code: http://codepen.io/anon/pen/AXdxZO?editors=0110
I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
I have been given a theme of bubbles so they want me to make the "characters" start at the bottom and animate upwards. Any ideas how I would achieve this?
Here is the code that currently maps the divs to there positions in the canvas...
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(1000, function() {
setTimeout(function() {
$('#' + id).fadeOut(1000);
window.cont++;
}, 7000);
});
Here is my most recent fiddle: http://jsfiddle.net/pUwKb/15/
The part below actually set the CSS (and thus the position of your element).
$('#' + id).css({
top: newY,
left: newX }).fadeIn(1000, function() {
setTimeout(function() {
$('#' + id).fadeOut(1000);
window.cont++;
}, 7000); });
You should add a function move who uses a movement variable. Small example:
function move(movement, id) {
$('#' + id).css({
top: this.css('top') + movement.y,
left: this.css('left') + movement.x
}).fadeIn(1000, function() {
setTimeout(function() {
$('#' + id).fadeOut(1000);
window.cont++;
}, 7000);
});
}
Where in movement should be an object something the like of {x: 30, y: 0} which would result in a 30 pixels movement to the right. Hope it helps!
I want to get an element's position relative to the window (fixed position).
Here's what I've got so far:
(function ($) {
$.fn.fixedPosition = function () {
var offset = this.offset();
var $doc = $(document);
return {
'x': offset.left - $doc.scrollLeft(),
'y': offset.top - $doc.scrollTop()
};
};
})(jQuery);
$('#thumbnails img').click(function () {
var pos = $(this).fixedPosition();
console.log(pos);
});
But when I click a thumbnail, it appears to be off by about 10 pixels or so. i.e., it will give me negative values for y even when the top edge of the photo is about 5 pixels away from the top of my browser window.
Use:
element.getBoundingClientRect();
In a JQuery Plugin:
$.fn.fixedPosition = function () {
var rect = this.getBoundingClientRect();
return {
x: rect.left,
y: rect.top
};
};
See:
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect
Update:
Solution now depends on JSizes and a couple helper methods:
function Point(x, y) {
return {
'x': x,
'y': y,
'left': x,
'top': y
};
}
$.fn.outerOffset = function () {
/// <summary>Returns an element's offset relative to its outer size; i.e., the sum of its left and top margin, padding, and border.</summary>
/// <returns type="Object">Outer offset</returns>
var margin = this.margin();
var padding = this.padding();
var border = this.border();
return Point(
margin.left + padding.left + border.left,
margin.top + padding.top + border.top
);
};
$.fn.fixedPosition = function () {
/// <summary>Returns the "fixed" position of the element; i.e., the position relative to the browser window.</summary>
/// <returns type="Object">Object with 'x' and 'y' properties.</returns>
var offset = this.offset();
var $doc = $(document);
var bodyOffset = $(document.body).outerOffset();
return Point(offset.left - $doc.scrollLeft() + bodyOffset.left, offset.top - $doc.scrollTop() + bodyOffset.top);
};
Your code looks fine and it should work as you're expecting it to.
That said, .offset() has a "gotcha" involved in which it won't account for any padding, margin, or border applied to the DOM body. It finds the offset of the element in relation to the document, not the window.
http://api.jquery.com/offset/
From the documentation:
Note: jQuery does not support getting the offset coordinates of hidden elements or accounting for borders, margins, or padding set on the body element.
Some css should hopefully fix the weird results:
body { margin: 0; padding: 0; border: none; }
I want to know how to use JavaScript to get the distance of an element from the top of the page not the parent element.
http://jsfiddle.net/yZGSt/1/
var elDistanceToTop = window.pageYOffset + el.getBoundingClientRect().top
In my experience document.body.scrollTop doesn't always return the current scroll position (for example if the scrolling actually happens on a different element).
offsetTop only looks at the element's parent. Just loop through parent nodes until you run out of parents and add up their offsets.
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 };
}
UPDATE: This answer has some problems, values will have tiny differences compare to what it should be and will not work correctly in some cases.
Check #eeglbalazs's answer, which is accurate.
Here is some interesting code for you :)
window.addEventListener('load', function() {
//get the element
var elem = document.getElementById('test');
//get the distance scrolled on body (by default can be changed)
var distanceScrolled = document.body.scrollTop;
//create viewport offset object
var elemRect = elem.getBoundingClientRect();
//get the offset from the element to the viewport
var elemViewportOffset = elemRect.top;
//add them together
var totalOffset = distanceScrolled + elemViewportOffset;
//log it, (look at the top of this example snippet)
document.getElementById('log').innerHTML = totalOffset;
});
#test {
width: 100px;
height: 100px;
background: red;
margin-top: 100vh;
}
#log {
position: fixed;
top: 0;
left: 0;
display: table;
background: #333;
color: #fff;
}
html,
body {
height: 2000px;
height: 200vh;
}
<div id="log"></div>
<div id="test"></div>
Use offsetTop
document.getElementById("foo").offsetTop
Demo
offsetTop doesn’t get the distance to the top of the page, but rather to the top of the closest parent element that has a specified position.
You can use a simple technique that adds up the offsetTop of all the parent element of the element you are interested in to get the distance.
// Our element
var elem = document.querySelector('#some-element');
// Set our distance placeholder
var distance = 0;
// Loop up the dom
do {
// Increase our distance counter
distance += elem.offsetTop;
// Set the element to it's parent
elem = elem.offsetParent;
} while (elem);
distance = distance < 0 ? 0 : distance;
Original code from https://gomakethings.com/how-to-get-an-elements-distance-from-the-top-of-the-page-with-vanilla-javascript/
This oneliner seems to work nice
document.getElementById("el").getBoundingClientRect().top + window.scrollY
your fiddle updated
var distanceTop = element.getBoundingClientRect().top;
For details vist a link:
https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
**For anchor links (href="/#about") to anchor <div id="about"> read part 3.
1 (distance_from_top)
Less than 30 seconds solution (Two lines of code "hello world"):
get your element:
var element = document.getElementById("hello");
Get getBoundingClientRect ();
The Element.getBoundingClientRect() method returns the size of an
element and its position relative to the viewport. https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
var rect = element.getBoundingClientRect();
Return object:
Dot notation top
var distance_from_top = rect.top; /* 1007.9971313476562 */
Thats it.
2 (window.scrollTo)
StackOverflow nightmare 2 - set scroll position to this value
Again "hello world" (8,000 answers out there - 7,999 not working or to complex).
https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo
window.scrollTo({
top: element.getBoundingClientRect().top,
behavior: 'smooth'
});
Add offset value to top if you want (For sticky navbars).
"Hello World" code snippet (Get distance from top viewport + click to scrollTo)
var element = document.getElementById("hello");
var rect = element.getBoundingClientRect();
var distance_from_top = rect.top; /* 50px */
console.log(distance_from_top);
function scrollTovView(){
window.scrollTo({
top: distance_from_top,
behavior: 'smooth'
});
}
div{
text-align:center;
border: 1px solid lightgray;
}
<button onclick="scrollTovView()">scrollTo to red DIV</button>
<div style="height: 50px;">50px height</div>
<div id="hello" style="width: 500px; height: 500px; background: red;"></div>
3 (scrollTo & anchors)
scrollTo "conflict" with main anchor navbars
This trick is very buggy if, for example, you use this URL:
www.mysite/about#hello
to
<div id="hello">hello</div>
top is 0 or buggy (The HTML moves to hello section).
window.scrollTo({
top: element.getBoundingClientRect().top,
behavior: 'smooth'
});
For this code to work you should add:
if (this.hash !== "") {
// Prevent default anchor click behavior
event.preventDefault();
Basic example her:
https://www.w3schools.com/howto/howto_css_smooth_scroll.asp
Although it is quite an old discussion, but this works pretty well on chrome / firefox / safari browsers:
window.addEventListener('scroll', function() {
var someDiv = document.getElementById('someDiv');
var distanceToTop = someDiv.getBoundingClientRect().top;
});
Check it out on JSFiddle
scroll to element's top position;
var rect = element.getBoundingClientRect();
var offsetTop = window.pageYOffset + rect.top - rect.height;
document.getElementById("id").offsetTop
(SOURCE : Determine distance from the top of a div to top of window with javascript )
<script type="text/javascript">
var myyElement = document.getElementById("myyy_bar"); //your element
var EnableConsoleLOGS = true; //to check the results in Browser's Inspector(Console), whenever you are scrolling
// ==============================================
window.addEventListener('scroll', function (evt) {
var Positionsss = GetTopLeft ();
if (EnableConsoleLOGS) { console.log(Positionsss); }
});
function GetOffset (object, offset) {
if (!object) return;
offset.x += object.offsetLeft; offset.y += object.offsetTop;
GetOffset (object.offsetParent, offset);
}
function GetScrolled (object, scrolled) {
if (!object) return;
scrolled.x += object.scrollLeft; scrolled.y += object.scrollTop;
if (object.tagName.toLowerCase () != "html") { GetScrolled (object.parentNode, scrolled); }
}
function GetTopLeft () {
var offset = {x : 0, y : 0}; GetOffset (myyElement, offset);
var scrolled = {x : 0, y : 0}; GetScrolled (myyElement.parentNode, scrolled);
var posX = offset.x - scrolled.x; var posY = offset.y - scrolled.y;
return {lefttt: posX , toppp: posY };
}
// ==============================================
</script>
This function returns distance from top of the page, even if your window is scrolled. It can be used in event listeners.
const getElementYOffset = (element) => {
const scrollOnWindow =
window.pageYOffset !== undefined
? window.pageYOffset
: (document.documentElement || document.body.parentNode || document.body)
.scrollTop;
const rect = element.getBoundingClientRect();
let distanceFromTopOfPage = rect.top;
if (scrollOnWindow !== 0) {
distanceFromTopOfPage = rect.top + scrollOnWindow;
}
return distanceFromTopOfPage;
};
You only need this line
document.getElementById("el").getBoundingClientRect().top
in which "el" is the element.
Since window.pageYOffset is a legacy alias of window.scrollY, eeglbalazs answer can be improved to:
const elDistanceToTop = window.scrollY + el.getBoundingClientRect().top;
Using jQuery's offset() method:
$(element).offset().top
Example: http://jsfiddle.net/yZGSt/3/