Why element getBoundingClientRect() values condition is not working properly? - javascript

I have .square element when I move my mouse it should be moved with the mouse. Almost everything is good when I move my mouse normally but when I move my mouse fastly on the right and bottom side, Then the .square box goes to the down or right side and makes the scrollbar.
How can I make my code better than now OR How to fix the problem?
[NOTE]: Transition should have
let square = document.querySelector(".square");
document.addEventListener("mousemove", function (e) {
if (square !== null) {
let x = e.pageX;
let y = e.pageY;
square.setAttribute("style", `top: ${y}px; left: ${x}px`);
let getBottom = square.getBoundingClientRect().bottom;
let getRight = square.getBoundingClientRect().right;
if (getBottom > window.innerHeight) {
square.setAttribute("style", `bottom: 0; left: ${x}px`);
}
if (getRight > window.innerWidth) {
square.setAttribute("style", `top: ${y}px; right: 0`);
}
if (getRight > window.innerWidth && getBottom > window.innerHeight) {
square.setAttribute("style", `bottom: 0; right: 0`);
}
}
});
*,
*::before,
*::after{
box-sizing: border-box;
}
body{
margin: 0;
border: 1px solid blue;
}
.square {
position: absolute;
height: 50px;
width: 50px;
border: 5px solid red;
transition: .01s ease-in-out;
}
<div class="square"></div>

The problem is caused by defining a transition on the square element's position in CSS, combined with setting the position of the square before reading its current position:
Setting the position immediately to the mouse move position:
square.setAttribute("style", `top: ${y}px; left: ${x}px`);
can move the square out of the viewport and create scroll bars if the mouse is less than the length of the square's side away from the bottom or right of the browser window.
Reading the position of the square next:
let getBottom = square.getBoundingClientRect().bottom;
let getRight = square.getBoundingClientRect().right;
returns the updated position of the square if there is no transition, but with the transition returns where the square is now, before the transition starts. Since the square is not outside the window yet, none of the conditional statements using its old position detect that it will end up outside the window.
The easiest solution is to remove the CSS transition - keeping it at 0.01 second is less than monitor update refresh time and not particularly useful.
Getting the square's position once, before updating its position is another solution.
In either case it may be smoother to update the position of the square at most once, with the position where it transition to.
In this code used to find an answer, the html element's clientWidth and clientHeight properties are a special case of these properties and reflect the size of the view port excluding scroll bars. The transition timing is set to 0.05 seconds to avoid stroboscopic effect from screen refresh:
let square = document.querySelector(".square");
const HTML = document.firstElementChild;
document.addEventListener("mousemove", function (e) {
if (square !== null) {
let domRect = square.getBoundingClientRect()
let x = e.pageX;
let y = e.pageY;
x = Math.min( HTML.clientWidth - domRect.width, x);
y = Math.min( HTML.clientHeight - domRect.height, y);
square.style.top = `${y}px`;
square.style.left = `${x}px`;
//square.setAttribute("style", `top: ${y}px; left: ${x}px`);
}
});
*,
*::before,
*::after{
box-sizing: border-box;
}
body{
margin: 0;
border: 1px solid blue;
}
.square {
position: absolute;
height: 50px;
width: 50px;
border: 5px solid red;
transition: .05s ease-in-out;
}
<div class="square"></div>

Related

Why does the circle in this code snippet lag behind the actual mouse cursor?

I am new to JavaScript and taking JavaScript Training course. The js code is supposed to render a circle at the mouse pointer location and listen for any changes.
const AREA = document.body;
const CIRCLE = document.querySelector('.circle');
function mouseCoordinates(e) {
var horizontalPosition = e.clientX - 26;
var verticalPosition= e.clientY - 26;
// Set horizontal and vertical position.
CIRCLE.style.left = horizontalPosition + 'px';
CIRCLE.style.top = verticalPosition + 'px';
}
AREA.addEventListener('mousemove', mouseCoordinates, false);
body {
width: 100vw;
height: 100vh;
}
.circle {
position: absolute;
top: 1px;
width: 50px;
height: 50px;
color: transparent;
border: 2px solid red;
border-radius: 50%;
}
<div class="circle"></div>
Initially the mouse pointer stays at the center of the circle, but when I am slightly increasing the speed of mouse movement the circle seems to "lag" behind.
Why does this happen?
Is this related to the performance of my system?
Isn't the listener supposed to run as and when the mouse moves and draw the circle exactly over the current position of the mouse?

How to adjust the mouse position after rotating an object?

I am trying to create an eye that follows cursor movement.
I got the horizontal and vertical coordinate of the mouse and the browser width and height.
Everything works perfectly. Except that I used rotate(45 deg) on the design of the eye so now the ball is not moving in the right position.
I was thinking about a math equation that finds the distance between the old and new coords, but I am not sure how to implement it.
Here is the full code:
https://jsfiddle.net/Mr_MeS/3ym6kuec/3/
so this is the .eye where its rotated
.eye {
width: 37.5px;
height: 37.5px;
background: white;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
border-radius: 75% 0;
overflow: hidden;
cursor: pointer;
}
.ball {
width: 7.5px;
height: 7.5px;
background: #222f3e;
border-radius: 50%;
border: 5px solid #576574;
position: relative;
top: 50%;
left: 50%;
}
and here is the JS that does the work and needs to be edited.
var balls = document.getElementsByClassName("ball");
document.onmousemove = function () {
var x = event.clientX * 100 / window.innerWidth + "%";
var y = event.clientY * 100 / window.innerHeight + "%";
//event.clientX => get the horizontal coordinate of the mouse
//event.clientY => get the Vertical coordinate of the mouse
//window.innerWidth => get the browser width
//window.innerHeight => get the browser height
for (var i = 0; i < 2; i++) {
balls[0].style.left = x;
balls[0].style.top = y;
balls[0].style.transform = "translate(-" + x + ",-" + y + ")";
}
}
Now, if I remove the rotation from the .eye, it works perfectly, expect that the whole shape doesn't look to be in position.
If I keep the 45deg rotation, the shape is good, but the ball moves wrongly.
You could try to put the eye-background (the white part that needs to rotate 45 degrees) into a div (or pseudo-element) that's inside the .eye element. In that way you don't need to rotate the container element, so the coordination of the ball element stays the same.
Another point, why are you using that for-loop? I think running the code once will be sufficient :)
EDIT: I've been playing around with your example a bit and fixed it. What happens is that if you rotate an element, the direction in which things will transform (and top/left positioning) will also change. So moving the element 10px to the left, will go 10px to the left, under a 45 degree angle, because it's rotated 45 degrees.
What I did now was to put an element (.inner) inside the eye div, which I gave a counter-rotation of -45 degrees. In this way, the container element of the ball has the correct orientation again, which fixes the problem: https://jsfiddle.net/bxprjvgL/
HTML:
<div class="eye">
<div class="inner">
<div class="shut"><span></span></div>
<div class="ball"></div>
</div>
</div>
CSS:
.inner {
width: 100%;
height: 100%;
transform: rotate(-45deg);
}

why is child element's width changing when parent's width changes?

I am trying to create a tooltip element that has a min width of 50px and a max width of 200px. I place the tooltip element inside another element so that I can easily control when the tooltip appears or disappears when there is a hover event on the parent.
The problem that I have is that the tooltip element's width appears to be controlled by the parent's width even though I specified that the child(tooltip) has an absolute position.
let p = document.getElementById( 'parent' );
let b = true;
setInterval( ()=> {
b = !b;
let w = 10;
if( b ) {
w = 300;
}
p.style.width = `${w}px`
}, 5000 );
#parent {
background-color: cyan;
width: 100px;
height: 25px;
position: relative;
transition: width 2s;
}
#tooltip {
position: absolute;
top: calc( 100% + 5px );
left: 5px;
min-width: 50px;
max-width: 200px;
background-color: yellow;
padding: 5px;
border: 1px solid black;
}
<div id="parent">
<div id="tooltip">
My long tooltip text that wraps to multiple lines as needed.
</div>
</div>
I would like the tooltip (yellow div) to keep it's size at 200px in this example, but we can see that when the parent changes width, the tooltip width also changes. Why?
Is there a way to fix this problem?
Clarification: In this example: https://codepen.io/anon/pen/ePPWER we see that the tooltip text looks nice on one line. I don't want the tooltip's div to change its width when the parent changes width, because it forces the tooltip text to wrap onto 2 lines which is undesirable.
If we check the specification related to the width of absolutely positioned element we can read this:
'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
So in your case the width of your element is shrink to fit:
Calculation of the shrink-to-fit width is similar to calculating the
width of a table cell using the automatic table layout algorithm.
Roughly: calculate the preferred width by formatting the content
without breaking lines other than where explicit line breaks occur,
and also calculate the preferred minimum width, e.g., by trying all
possible line breaks. CSS 2.1 does not define the exact algorithm.
Thirdly, calculate the available width: this is found by solving for
'width' after setting 'left' (in case 1) or 'right' (in case 3) to 0.
Then the shrink-to-fit width is: min(max(preferred minimum width,
available width), preferred width).
To make it easy, and without considering the min/max-width, the width of your element will try to fit the content without exceding the width of its parent container (containing block). By adding min/max-width you simply add more constraint.
One idea of fix it to remove positon:relative from the parent element so that it's no more the containing block of the position:absolute element (it will be the initial containing block which is wide enough to avoid the available width constraint).
Then use margin instead of top/left to control the position:
let p = document.getElementById( 'parent' );
let b = true;
setInterval( ()=> {
b = !b;
let w = 10;
if( b ) {
w = 300;
}
p.style.width = `${w}px`
}, 5000 );
#parent {
background-color: cyan;
width: 100px;
height: 25px;
transition: width 2s;
}
#tooltip {
position: absolute;
margin-top: 30px;
min-width: 50px;
max-width: 200px;
background-color: yellow;
padding: 5px;
border: 1px solid black;
}
<div id="parent">
<div id="tooltip">
My long tooltip text that wraps to multiple lines as needed.
</div>
</div>
ID Tooltip is being used under Parent. When parent's width changes, it also suggest that tooltip's total width is changed. Since you have used mix-width and max-width it will expand till it reaches max-width. If you want it to be fixed then simple use width.
It is because the .parent has a position: relative. This will keep all children (position: absolute included) as confined by the parent div.
Not sure if this will work for you because it is pulling the tooltip out of the parent and making it's own with span wrapping the text. Alternatively, you'll need to change the parent from being relative otherwise it'll continually affect the child.
let p = document.getElementById('parent');
let b = true;
setInterval(() => {
b = !b;
let w = 10;
if (b) {
w = 300;
}
p.style.width = `${w}px`
}, 5000);
#parent {
background-color: cyan;
width: 100px;
height: 25px;
transition: width 2s;
position: relative;
}
#root {
position: relative;
}
#tooltip {
width: 100%;
}
#tooltip span {
position: absolute;
top: calc( 100% + 5px);
left: 5px;
min-width: 50px;
max-width: 200px;
background-color: yellow;
padding: 5px;
border: 1px solid black;
}
<div id="root">
<div id="parent"></div>
<div id="tooltip">
<span>My long tooltip text that wraps to multiple lines as needed.</span>
</div>
</div>

How to align the popup div direction in css?

I am working on a website that while mouse over a DVD shows the details like you see in picture 1, however, it doesn't work on those DVD placed to the right of the screen as you can see in picture 2, the content got chopped.
How to let it automatically choose which direction to display the content? Like if this DVD is close to the right screen, show content to the left?
Many thanks!
.imgbox .imgbox_content{
display: none;
}
.imgbox:hover .cover{
display: none;
}
.imgbox:hover .imgbox_content{
display: block;
z-index:2;
width:600px;
border-radius: 1%;
border:1px solid gray;
-webkit-transition: 1s;
position:absolute;
background: black;
color:white;
}
Here's a quick solution using JS. Not seeing any JS/HTML though, so you'll need to adapt it to whatever your code looks like:
var imgboxes = document.querySelectorAll('.imgbox');
imgboxes.forEach(function (imgbox) {
var rect = imgbox.getBoundingClientRect(),
screen_width = document.body.clientWidth,
popup_width = <<POPUP_CONTENT_WIDTH>>;
if (rect.right + popup_width > screen_width) {
imgbox.classList.add('to_left');
}
});
and then change your CSS to something like this for the popup:
.imgbox:hover .imgbox_content{
display: block;
z-index:2;
width:600px;
border-radius: 1%;
border:1px solid gray;
-webkit-transition: 1s;
position:absolute;
background: black;
color:white;
}
imgbox:hover .imgbox_content.to_left {
/* this assumes you've got a position:relative item wrapping the imgbox and that .imgbox_content is a child */
right: 0;
}
In order to get even more complete, you can handle screen resizes too:
window.addEventListener('resize', calculate_pos_imgboxes)
var calculate_pos_imgboxes = function() {
var imgboxes = document.querySelectorAll('.imgbox');
imgboxes.forEach(function (imgbox) {
var rect = imgbox.getBoundingClientRect(),
screen_width = document.body.clientWidth,
popup_width = <<POPUP_CONTENT_WIDTH>>;
if (rect.right + popup_width > screen_width) {
imgbox.classList.add('to_left');
} else {
imgbox.classList.remove('to_left');
}
});
}
calculate_pos_imgboxes();
The easiest way to do this is to just force your popups to appear at the center of the screen. You can do this using position:absolute. However, if you want the popups bounded to the location of the item - then you have to do some calculations.
It would look like this.
Get width of your popup.
Get distance from left edge of target to right edge of screen.
Compare 1 and 2
If dist < width, then offset to the left. Otherwise, offset to the right.
The code for this would look something like (using jQuery).
var padding = 20;
var elem = $(yourpopup);
var popupwidth = elem.width();
var position = p.position();
var dist = position.left - popupwidth;
// if the popup is wider than distance to edge plus padding, then offset margin using negative value.
if (dist < popupwidth){
elem.css('margin-left', "'-" + popupwidth + padding + "px'" );
}

How to move content along with window resize so that it appears in the same position?

I have a page with a grid containing three div elements. Each one of this div has the size of the viewport so at any time just one of the divs if visible and the other two are outside. So the grid is three times big the viewport.
Resizing the window will cause the divs, hence the grid, to resize as well.
The html is pretty simple:
<div class="container">
<div class="square square1">1</div>
<div class="square square2">2</div>
<div class="square square3">3</div>
</div>
Styled like this:
body {
margin: 0;
padding: 0;
}
.container {
position: relative;
height: 100vh;
width: 300vw;
}
.square {
height: 100vh;
position: absolute;
width: 100vw;
}
.square1 {
background: red;
left: 0;
}
.square2 {
background: green;
left: 100vw;
}
.square3 {
background: yellow;
left: 200vw;
}
The initial position, set via javascript, is on the middle div.
What happens is that resizing the window makes the whole document to move proportionally with the resizing. So, if at some point I'm seeing just the second div, resizing the window will make the third to appear more and more.
I'm quite sure that with some javascript I could move the grid so that it appears fixed while resizing, but I can't figure out a formula.
I tried something like this:
var windowW = $(window).width();
$(window).resize(function() {
var newWidth = $(window).width();
var diff = windowW - newWidth;
var windowLeftPos = $(window).scrollLeft();
$(window).scrollLeft(windowLeftPos - diff / 2);
});
But it's just a blind guess. I tried other formulas with multiplication and division and scale factors, but nothing worked.
Any idea?
Here's a working example showing what I mean.
Initially you see just the green div. Resizing the window, on of the two other divs will appear, instead I would like to see only the green one.
Edit: the question similar to mine is very interesting but it seems to me also very different. The main huge difference is that I'm resizing and moving DOM elements that stay outside the viewport. Besides, the answers are pretty focused on the image/background aspect ratio, which is part of the question, but it's not the case for me. I don't have a problem resizing elements, just compensating the movement due to the resizing
Update: I edited the pen and I think I'm getting closer to the desired result: http://codepen.io/anon/pen/vGeRgJ
It seems to kind of work, but it doesn't especially when I'm closer to one of the extremes, like all on the left or on the right.
Here is an updated version for you, from where you can easily make your own adjustments.
Since jQuery doesn't throttle the resize event by default, I made this one in plain javascript.
To get rid of the vertical scroll, and I also added a getScrollbarSize function as a bonus :)
function getWidth() { return window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; }
function getLeft() { return document.body.scrollLeft; }
function setLeft(v) { document.body.scrollLeft = v; }
function getScrollbarSize() {
var div, width;
div = document.createElement('div');
div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
div = div.firstChild;
document.body.appendChild(div);
width = div.offsetWidth - div.clientWidth;
document.body.removeChild(div);
return width;
};
(function(t,w,l,l2) {
document.querySelector('.container').style.height = 'calc(100vh - ' + getScrollbarSize() + 'px)';
w = getWidth(), l = w, l2 = l / w, setLeft(w);
window.addEventListener("resize", function(e) {
if ( !t ) {
t = setTimeout(function() {
t = null;
resizeHandler(e);
}, 66); /* throttle timeout */
}
}, false);
function resizeHandler(e) {
w = getWidth();
l = getLeft();
setLeft(w * l2);
}
window.addEventListener("scroll", function(e) {
if ( !t ) {
l2 = getLeft() / w;
}
}, false);
}());
body {
margin: 0;
padding: 0;
}
.container {
position: relative;
height: 100vh;
width: 100vw;
}
.square {
height: 100%;
position: absolute;
width: 100vw;
}
.square1 {
background: red;
left: 0;
}
.square2 {
background: green;
left: 100%;
}
.square3 {
background: yellow;
left: 200%;
}
<div class="container">
<div class="square square1">1</div>
<div class="square square2">2</div>
<div class="square square3">3</div>
</div>

Categories

Resources