getBoundingClientRect seems inaccurate when element is being transformed - javascript

I'm trying to use a CSS transform to translate an absolutely positioned SVG element diagonally down/right across the viewport on scroll, and I need the individual paths to change their fill colour as they cross over the next element, but it seems like getBoundingClientRect isn't returning the correct values during scroll.
Here's a demo: https://codepen.io/ahollister/pen/mdyvQLN
And here's the JS:
window.addEventListener('scroll', function(e) {
let scrollPercent = ( window.scrollY ) / ( document.body.clientHeight - window.innerHeight );
let scrollPercentRounded = Math.round( scrollPercent * 100 );
document.querySelector('.arrows-container').style.transform = `translate(${scrollPercentRounded}%, ${scrollPercentRounded}%)`
const arrowsArray = document.querySelectorAll('svg path');
const el = document.querySelector('.bottom');
for ( const a of arrowsArray ) {
if ( a.getBoundingClientRect().bottom > el.offsetTop ) {
a.style.fill = 'red';
}
}
});
I'm trying to get each arrow to change fill colour as they cross the line into the .bottom element, if you comment out the transform line it seems to calculate everything correctly:
document.querySelector('.arrows-container').style.transform = `translate(${scrollPercentRounded}%, ${scrollPercentRounded}%)`
Anyone come across this issue before? How can I get getBoundingClientRect to return the correct values in this instance?

Figured it out, in the end I wasn't taking the scrollY position into account. Ended up with a sort of rudimentary collision detection function which looks like this:
function colorOnCollision( svgPaths, el, color1, color2 ) {
for ( const path of svgPaths ) {
// If path is over el1 or el2
if ( path.getBoundingClientRect().bottom < ( el.offsetHeight - window.scrollY ) ) {
path.style.fill = color1;
} else {
path.style.fill = color2;
}
}
}

Related

Vanilla javascript draggable div within parent

I want to achieve a draggable element in vanilla javascript.
I want to make a small circular div draggable within a square div.
To make myself a bit more clear, I do NOT want to:
Create drag and drop,
Use jQuery UI or any other library or plugin to achieve this, just vanilla javascript
I already have a few events for handling dragging:
parent.addEventListener("mousedown", ..),
document.addEventListener("mouseup", ..)
and
document.addEventListener("mousemove", ..)
My question is how can I keep the draggable inside the given bounds of its parent?
Reference: https://codepen.io/ChickenFlavored/pen/rNxRXGo
You may use getBoundingClientRect that gives DOMRect object(an invincible rectangle that encompass the object) with eight properties: left, top, right, bottom, x, y, width, height.
var parent = document.querySelector('.parent');
var parentRect = parent.getBoundingClientRect();
var draggable = document.querySelector('.draggable');
var draggableRect = draggable.getBoundingClientRect();
You can get the mouse (X,Y) coordinates anytime by using e.clientX and e.clientY, so just check if they are outside the bounding rectable of .parent div if so then only update the draggable div's left and top properties
if( (e.clientX >= parentRect.left && (e.clientX+draggableRect.width <= parentRect.right)) &&
(e.clientY >= parentRect.top && (e.clientY+draggableRect.height <= parentRect.bottom))
//+draggableRect.height and +draggableRect.height accounts for the size of ball itself
){
draggable.style.left = `${e.clientX}px`;
draggable.style.top = `${e.clientY}px`;
}
Note that numbers increase down and towards right in graphics world
https://codepen.io/vsk/pen/PozVryr
UPDATE:
To fix the issue mentioned in comment use this
if(/* mouse was moved withing red container's bounds*/)
else{
//if mouse went out of bounds in Horizontal direction
if(e.clientX+draggableRect.width >= parentRect.right){
draggable.style.left = `${parentRect.right-draggableRect.width}px`;
}
//if mouse went out of bounds in Vertical direction
if(e.clientY+draggableRect.height >= parentRect.bottom){
draggable.style.top = `${parentRect.bottom-draggableRect.height}px`;
}
}
And assign mousemove to document insted of the div container
I hope you could provide a code snippet to try to simulate what you want, a codepen url, codesandbox or similar.
I made this sample using what you provided : )
let x = event.clientX - element.offsetLeft
const elementWidth = element.clientWidth
const fullX = x + elementWidth
const containerW = yourContainer.clientWidth
if(fullX > containerW){
//If element will be out of container
x = containerW - elementWidth // So it will be never out of container
}
element.style.transform = "translate("+x+"px, "+y+"px)"

Changing opacity breaks positioning of image

I'm trying to make a random slideshow of images for a gallery. I have two images on the page – the "front" one and the "back" one, and I have a timer set up to, every few seconds, move the back image to the front and load a new back image. I'm basically doing this by swapping the image objects in the code.
As the back image becomes the front image, I have it fade in gradually from an opacity of 0 to an opacity of 1, while I have the front image do the same in reverse. I implemented this as follows:
var fadeOutCt = 0;
var fadeOutInterval;
// Decrements the opacity of element by amt, until cap
function decrementOpacity( element, amt, cap ) {
var currentOpacity = Number( window.getComputedStyle( element ).getPropertyValue( "opacity" ) );
currentOpacity = currentOpacity - amt;
element.setAttribute( "style", "opacity:" + currentOpacity );
fadeOutCt = fadeOutCt + 1;
if ( fadeOutCt >= cap ) {
element.setAttribute( "style", "opacity:0" );
clearInterval( fadeOutInterval );
}
}
// Calls decrementOpacity to fill the specified interval.
function fadeOut( element, interval ) {
var currentOpacity = Number( window.getComputedStyle( element ).getPropertyValue( "opacity" ) );
fadeOutCt = 0;
if ( currentOpacity > 0 ) {
cap = interval / 10.0;
amt = 1.0 / cap;
fadeOutInterval = setInterval( decrementOpacity, 10, element, amt, cap );
}
}
Separately, I have another routine that resizes the image I load to conform to the user's screen (it also centers the image). I run this immediately before the fade-in or fade-out operation.
function resizeForSlideshow( imgToResize ) {
// Get size of usable area for slideshow
var usableWidth = window.innerWidth;
var titleTable = document.getElementById( "descTable" );
var windowHeight = window.innerHeight;
var tableHeight = titleTable.offsetHeight;
var usableHeight = windowHeight - tableHeight;
// Resize containing div
var slideDiv = document.getElementById( "slideDiv" );
slideDiv.setAttribute( "style", "height:" + usableHeight + "px" );
// Get size of native image to be displayed
var nativeWidth = imgToResize.naturalWidth;
var nativeHeight = imgToResize.naturalHeight;
// Determine width-to-height ratios of those two
var windowRatio = usableWidth / usableHeight;
var imageRatio = nativeWidth / nativeHeight;
if ( imageRatio > windowRatio ) {
// image's width-to-height is greater than window
// image should be set to 100% width, less height
imgToResize.width = usableWidth;
imgToResize.height = usableWidth * ( nativeHeight / nativeWidth );
// relocate image accordingly
var newTop = ( usableHeight - imgToResize.height ) / 2;
imgToResize.style.left = 0;
imgToResize.style.top = newTop + "px";
}
else {
// image's width-to-height is less than window
// image should be set to 100% height, less width
imgToResize.height = usableHeight;
imgToResize.width = usableHeight * ( nativeWidth / nativeHeight );
// relocate image accordingly
var newLeft = ( usableWidth - imgToResize.width ) / 2;
imgToResize.style.top = 0;
imgToResize.style.left = newLeft + "px";
}
}
The problem is, when I fade in or fade out, it breaks the positioning of my images. Instead of being centered, they revert to being on the top left of the page (though their size remains what it should be). I've tried a few things but I'm out of ideas, and I was hoping someone would be able to shed some light on what's going wrong here and how I could fix it.
(If seeing the code in action would help: http://artmonitors.com/slideshow/full-slideshow.html
EDIT: I later figured out the problem.
The problem is that using setAttribute to set opacity also removed the positional settings I had given. Manually setting element.style.opacity made things work.
How about centering it with css?
.slide {
display: block;
margin: auto;
position: relative;
}
You should close the style with ;
element.setAttribute( "style", "opacity:" + currentOpacity +";");
I later figured out the problem. The problem is that using setAttribute to set opacity also removed the positional settings I had given. Manually setting element.style.opacity made things work.

convert function to use css translate3d

I've written a function in Javascript to make images draggable within a container. Even if the image is enlarged it can be dragged all over the screen without disappearing from it. My function relies heaving on using style.top and style.left. Now I've heard that using translate3d might provide better performance. This is interesting because I changed my image scale function, which uses a slider, to scale3d and the scaling is clearly smoother, no doubt. So could anyone help me convert this function I've written to use translate3d? I've tried and tried but have kept failing. Many Thanks:
EDIT: I put up a jsfiddle
https://jsfiddle.net/bx4073tr/
Please note that imgRect is the parent div while img is the image itself (it's in an img tag contained in the div).
function makeImageDraggable(event) {
// Make an image draggable but within bounds of container
let overflow_vertical = false;
let overflow_horizontal = false;
// bounding rectangles to hold image and imageContainer
let imgRect = img.getBoundingClientRect();
let imgContainerRect = imageContainer.getBoundingClientRect();
// find out if image overflows it's container div
// check for vertical overflow, getBoundingClientRect().height will get the real height after the image is scaled
if ( imgRect.height > imageContainer.offsetHeight ) {
overflow_vertical = true;
}
// check for horizontal overflow
if ( imgRect.width > imageContainer.offsetWidth ) {
overflow_horizontal = true;
}
// if there is no overflow, either horizontal or vertical, then do absolutely nothing
if (!overflow_horizontal && !overflow_vertical) {
// nothing to do
} else {
// otherwise make image draggable
event = event || window.event;
// get initial mouse position
let startX = event.clientX;
let startY = event.clientY;
// get position of image to be dragged
let offsetX = pixelToFloat(img.style.left);
let offsetY = pixelToFloat(img.style.top);
// add onmousemove event now we are sure user has initiated a mousedown event
window.onmousemove = function(mousemove_event) {
if (mousemove_event == null) {
mousemove_event = window.event;
}
// calculate bounds so that image does not go off the page
// if there is an overflow, the image will be bigger than the container
// so we need to find the maximum distance we can go upwards, downwards and sideways
// using img.getBoundingClientRect, we can get the width of the scaled image, we also get the width of the container
// divide it by 2 so we can move the same number of pixels in either direction
// max right and left
let max_right = -1 * ( ((imgRect.right - imgRect.left) - (imgContainerRect.right - imgContainerRect.left))/2 );
// should be a positive number
let max_left = -1 * (max_right);
// max bottom and top
let max_bottom = -1 * ( ((imgRect.bottom - imgRect.top) - (imgContainerRect.bottom - imgContainerRect.top))/2 );
// should be a positive number
let max_top = -1 * (max_bottom);
// Dragging image left and right
if (!overflow_horizontal) {
} else {
let scrollX = (offsetX + mousemove_event.clientX - startX);
// img.style.left will keep increasing or decreasing, check if it approaches max_left or max_right
if (scrollX >= max_left || scrollX <= max_right) {
//return false;imageContainer.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)';
} else {
if (scrollX < max_left) { img.style.left = min(scrollX, max_left) + 'px'; }
if (scrollX > max_right) { img.style.left = max(scrollX, max_right) + 'px'; }
}
}
// Dragging image top to bottom
if (!overflow_vertical) {
} else {
let scrollY = (offsetY + mousemove_event.clientY - startY);
// as an expanded image is pulled downwards, img.style.top keeps increasing to approach max_top
// if it reaches max top, simply do nothing, else keep increasing
// check for both conditions, approaching max_top and approaching max_bottom
if (scrollY >= max_top || scrollY <= max_bottom) {
// return false;
} else {
if (scrollY < max_top) { img.style.top = min(scrollY, max_top) + 'px'; }
if (scrollY > max_bottom) { img.style.top = max(scrollY, max_bottom) + 'px'; }
}
}
// return
return false;
}
}
// cancel mousemove event on mouseup
window.onmouseup = function(mouseup_event) {
window.onmousemove = null;
// Should not return false as it will interfere with range slider
}
// return false
return false;
}
Works now.
See makeDraggable method in the fiddle below:
https://jsfiddle.net/daibatzu/0u74faz6/6/
All you have to do is add this function to the event listener for the image like:
var img = document.getElementById('myImage');
img.addEventListener('mousedown', function(event) { makeDraggable(event); });
Code
function makeDraggable(event) {
// get bounding rectangle
let imgRect = img.getBoundingClientRect();
let parentRect = parent.getBoundingClientRect();
// check overflow
let overflow_horizontal = (imgRect.width > parent.offsetWidth ? true : false);
let overflow_vertical = (imgRect.height > parent.offsetHeight ? true : false);
// get start position
let startX = event.pageX - translateX, startY = event.pageY - translateY;
let max_left = parentRect.left - imgRect.left;
let max_top = parentRect.top - imgRect.top;
window.onmousemove = function(evt) {
// set event object
if (evt == null) { evt = window.event; }
// Say max_left is 160px, this means we can only translate from 160px to -160px to keep the image visible
// so we check if the image moves beyond abs(160), if it does, set it to 160 or -160 depending on direction, else, let it continue
translateX = (Math.abs(evt.pageX - startX) >= max_left ? (max_left * Math.sign(evt.pageX - startX)) : (evt.pageX - startX));
translateY = (Math.abs(evt.pageY - startY) >= max_top ? (max_top * Math.sign(evt.pageY - startY)) : (evt.pageY - startY));
// check if scaled image width is greater than it's container. if it isn't set translateX to zero and so on
translateX = overflow_horizontal ? translateX : 0, translateY = overflow_vertical ? translateY : 0;
// translate parent div
parent.style['-webkit-transform'] = 'translate(' + translateX + 'px, ' + translateY + 'px)';
// return
return false;
}
window.onmouseup = function(evt) {
// set mousemove event to null
window.onmousemove = null;
}
return false;
};

finding the maximum scroll position of a page

I have made the body of the page 200% tall so that it fits on a screen twice. Using javascript I am making it keep scrolling to the top or bottom when you scroll. For this, I need to find out the lowest scroll point of the page on any browser or screen size so that it stops when it gets there.
No JQuery please.
Thank you.
My code: (it is still being put together so needs a bit of work)
function getScrollXY() {
var x = 0, y = 0;
if( typeof( window.pageYOffset ) == 'number' ) {
// Netscape
x = window.pageXOffset;
y = window.pageYOffset;
} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
// DOM
x = document.body.scrollLeft;
y = document.body.scrollTop;
} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
// IE6 standards compliant mode
x = document.documentElement.scrollLeft;
y = document.documentElement.scrollTop;
}
return [x, y];
}
function scrollup() {
if (xy[1] > 0) {
window.scrollBy(0,-100);
setTimeout(scrollup,200);
} else {
null;
}
}
function scrolldown() {
if (xy[1] < ) {
window.scrollBy(0,100);
setTimeout(scrolldown,200);
} else {
null;
}
}
function dothescroll() {
var xy = getScrollXY();
var y = xy[1];
setTimeout(function(){
if (xy[1] > y) {
scrollup();
} else {
scrolldown();
}
},200);
}
This is the cross browser compatible version:
var limit = Math.max( document.body.scrollHeight, document.body.offsetHeight,
document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight );
While this is not part of any specification, you could try window.scrollMaxY.
Returns the maximum number of pixels that the document can be scrolled vertically.
https://developer.mozilla.org/docs/Web/API/Window/scrollMaxY
Fallback if unavailable:
var scrollMaxY = window.scrollMaxY || (document.documentElement.scrollHeight - document.documentElement.clientHeight)
Note, documentElement isn't always the scrolling element (some browsers use body instead). The solution is the new scrollingElement property, which returns a reference to the Element that scrolls the document. It is specified, but still a working draft.
var limit = document.body.offsetHeight - window.innerHeight;
// document.body.offsetHeight = computed height of the <body>
// window.innerHeight = available vertical space in the window
Compare xy[1] < limit
Note: You might need to increase the value by margin and/or padding of the body. You can also try using clientHeight instead of offsetHeight.
My solution based on the solutions above and what I use in 2022.
AKA scrollMaxY
const scrollMaxValue = () => {
const body = document.body;
const html = document.documentElement;
const documentHeight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
const windowHeight = window.innerHeight;
return documentHeight - windowHeight;
};
scrollMaxValue()
window.scrollTo(0, document.body.scrollHeight);
this will work..

Determine distance from the top of a div to top of window with javascript

How do I determine the distance between the very top of a div to the top of the current screen? I just want the pixel distance to the top of the current screen, not the top of the document. I've tried a few things like .offset() and .offsetHeight, but I just can't wrap my brain around it. Thanks!
You can use .offset() to get the offset compared to the document element and then use the scrollTop property of the window element to find how far down the page the user has scrolled:
var scrollTop = $(window).scrollTop(),
elementOffset = $('#my-element').offset().top,
distance = (elementOffset - scrollTop);
The distance variable now holds the distance from the top of the #my-element element and the top-fold.
Here is a demo: http://jsfiddle.net/Rxs2m/
Note that negative values mean that the element is above the top-fold.
Vanilla:
window.addEventListener('scroll', function(ev) {
var someDiv = document.getElementById('someDiv');
var distanceToTop = someDiv.getBoundingClientRect().top;
console.log(distanceToTop);
});
Open your browser console and scroll your page to see the distance.
This can be achieved purely with JavaScript.
I see the answer I wanted to write has been answered by lynx in comments to the question.
But I'm going to write answer anyway because just like me, people sometimes forget to read the comments.
So, if you just want to get an element's distance (in Pixels) from the top of your screen window, here is what you need to do:
// Fetch the element
var el = document.getElementById("someElement");
use getBoundingClientRect()
// Use the 'top' property of 'getBoundingClientRect()' to get the distance from top
var distanceFromTop = el.getBoundingClientRect().top;
Thats it!
Hope this helps someone :)
I used this:
myElement = document.getElemenById("xyz");
Get_Offset_From_Start ( myElement ); // returns positions from website's start position
Get_Offset_From_CurrentView ( myElement ); // returns positions from current scrolled view's TOP and LEFT
code:
function Get_Offset_From_Start (object, offset) {
offset = offset || {x : 0, y : 0};
offset.x += object.offsetLeft; offset.y += object.offsetTop;
if(object.offsetParent) {
offset = Get_Offset_From_Start (object.offsetParent, offset);
}
return offset;
}
function Get_Offset_From_CurrentView (myElement) {
if (!myElement) return;
var offset = Get_Offset_From_Start (myElement);
var scrolled = GetScrolled (myElement.parentNode);
var posX = offset.x - scrolled.x; var posY = offset.y - scrolled.y;
return {lefttt: posX , toppp: posY };
}
//helper
function GetScrolled (object, scrolled) {
scrolled = scrolled || {x : 0, y : 0};
scrolled.x += object.scrollLeft; scrolled.y += object.scrollTop;
if (object.tagName.toLowerCase () != "html" && object.parentNode) { scrolled=GetScrolled (object.parentNode, scrolled); }
return scrolled;
}
/*
// live monitoring
window.addEventListener('scroll', function (evt) {
var Positionsss = Get_Offset_From_CurrentView(myElement);
console.log(Positionsss);
});
*/
I used this function to detect if the element is visible in view port
Code:
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
$(window).scroll(function(){
var scrollTop = $(window).scrollTop(),
elementOffset = $('.for-scroll').offset().top,
distance = (elementOffset - scrollTop);
if(distance < vh){
console.log('in view');
}
else{
console.log('not in view');
}
});

Categories

Resources