Use page coordinates in elementFromPoint? - javascript

I need to know what element sits at the very bottom of the page (usually a <footer> but it can be something else). So I know I can use document.elementFromPoint() but only if the user has scrolled to the bottom because that function uses client coordinates not page coordinates. See my example below, both elements should have green text but only one does.
Is there a way to find an element at a given page coordinate?
$(document).ready(function() {
var body = document.body,
html = document.documentElement;
var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
// Find "Top element"
$(document.elementFromPoint(10, 30)).css('color', 'green');
// Find "Bottom element"
$(document.elementFromPoint(10, height-10)).css('color', 'green');
});
html, body {
margin: 0;
padding: 0;
}
body {
height: 2000px;
position: relative;
}
h1 {
background-color: blue;
display: block;
width: 100%;
}
.footer-ele {
position: absolute;
bottom: 0;
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h1>Top element</h1>
<h1 class="footer-ele">Bottom element</h1>

Related

How do I get a parent div to resize when the children do?

I'm running into this problem with a program I'm working on and I can't figure out the solution. Basically I have a webapp, on initial render the entire page is height X. There is a background color, and that color covers the full height of the page.
Moments after the initial render, data is fetched asynchronously and added to the page. The data causes the total height of the page to get larger. Unfortunately, only the original height of the page shows the background color, below that the background becomes white.
On further investigation, I've discovered this is due to a parent div not resizing when the child div does. Basically, the div getting elements added to it by the asynchronous data loading grows in height, but the parent wrapping around it (and the one that has the background color) does not.
During my investigation, I have found a partial solution: set the "height" to "auto". The problem with this solution is when the page initially loads, the background color is only applied to the part of the page with actual content, rather than filling the screen. So if I have the height at "auto", the color is screwed up when the page first loads before correcting itself after the items are added, and if the height is "100%" then the color is screwed up after the items are added.
I've put together a barebones HTML file that re-creates the issue:
<!DOCTYPE html>
<html>
<head>
<title>Layout Test</title>
<style>
* {
margin: 0;
padding: 0;
}
html {
height: 100%;
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 0;
height: 100%;
}
#root {
background-color: cyan;
height: 100%;
}
#root > h1 {
text-align: center;
}
</style>
</head>
<body>
<div id="root">
<h1>Layout Test</h1>
<div id="container"></div>
</div>
<script>
setTimeout(() => {
const container = document.querySelector('#container');
for (let i = 0; i < 100; i++) {
const h1 = document.createElement('h1');
h1.textContent = 'Hello';
container.appendChild(h1);
}
}, 1000);
</script>
</body>
</html>
You could set min-height: 100vh; on the #root element CSS so that it is always at least the height of the viewport.
Setting the a min-height of 100% on the #root, instead of a height, seems to do the trick:
<!DOCTYPE html>
<html>
<head>
<title>Layout Test</title>
<style>
* {
margin: 0;
padding: 0;
}
html {
height: 100%;
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 0;
height: 100%;
}
#root {
background-color: cyan;
min-height: 100%;
}
#root > h1 {
text-align: center;
}
</style>
</head>
<body>
<div id="root">
<h1>Layout Test</h1>
<div id="container"></div>
</div>
<script>
setTimeout(() => {
const container = document.querySelector('#container');
for (let i = 0; i < 100; i++) {
const h1 = document.createElement('h1');
h1.textContent = 'Hello';
container.appendChild(h1);
}
}, 1000);
</script>
</body>
</html>
You could use height: fit-content; and width: fit-content; on the parent element

How to remove Iframe scrollbar but full page should load?

After adding Iframe inside contentArea, I am getting two scroll bars. I wanted to hide iframe scrollbar without hiding any content of external website link. How can I do that?
I added the below snippet code and tried couple of things like scrollbar="no" but didn't work.
Help me on this and thank you in advance.
I need contentArea scrollbar. Just wanted to hide iframe scrollbar without hiding external website content.
body{margin:0;padding:0;}
.contentArea{height:100%; width:100%; position:absolute; top:0;left:0;overflow-y:scroll;}
iframe{height:100%; width:100%; position:absolute; top:0;left:0;border:0;}
<div class="contentArea">
<iframe src="https://ajaymalhotra.in" title="Iframe Example"></iframe>
</div>
You need to make the size of the iframe match the size of the content in the iframe. Their is no native way of doing this in the browser, and if your doing this cross-domain then you will need JS code on both the parent and in the iframe.
Here is a much longer answer that I wrote a few years ago on how to do this.
iframe Auto Adjust Height as content changes
Alternatively their is this library that will make things a lot simpler for you.
https://github.com/davidjbradshaw/iframe-resizer
Try this, you will able to hide the scroll bar of iframe but you can still scroll your page
<div style='width: 500px; height: 120px; overflow: hidden;'>
<iframe style='width: 518px; height: 120px;' src=''></iframe>
</div>
You can use overflow:hidden
.contentArea {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
overflow-y: hidden; //This should hide the scrollbar for you
}
This is not entirely possible without controlling the domain where the iframe points. However this is about as close as you can get to what you're looking for and have it reliable.
body {
margin: 0;
padding: 0;
text-align: center;
}
.contentArea {
display: flex;
flex-direction: column;
flex-grow: 1;
flex-basis: 0;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow-x: auto;
overflow-y: scroll;
}
.flex-box {
flex-grow: 1;
flex-basis: 0;
overflow: hidden;
/* Set height as percentage of viewport */
min-height: 100%;
}
iframe {
/*
DO not adjust height of iframe here.
set the height on .flex-box
*/
width: calc(100% + 16px);
height: 100%;
margin: 0;
padding: 0;
border: none;
}
.additional-content {
padding: 15px;
background-color: #CDDC39;
}
<div class="contentArea">
<div class="additional-content">Content Before. Remove and iframe will fill this space.
</div>
<div class="flex-box">
<iframe id="myIframe" src="https://ajaymalhotra.in" title="Iframe Example"></iframe>
</div>
<div class="additional-content">
Some content after.<br> Remove and iframe will fill this space. Remove both top and bottom additonal content and the iframe will fill entire window.
</div>
</div>
I used this idea to 'seamlessly' embed a WordPress page into a Magento2 Page, using an iframe... since I really needed the WP template.. and it works.
Added this JS
// stackoverflow.com/questions/1145850/
function getDocHeight(doc) {
doc = doc || document;
var body = doc.body, html = doc.documentElement;
var height = Math.max( body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight );
return height;
}
function setIframeHeight(id) {
const ifrm = document.getElementById(id);
const doc = ifrm.contentDocument? ifrm.contentDocument: ifrm.contentWindow.document;
ifrm.style.height = getDocHeight( doc ) + 2 + "px";
}
With this iframe code
<iframe id='wp-twoblock-0'
src='https://www.example.com/wp/twoblock'
onload='setIframeHeight(this.id);'
style='border:0px;vertical-align:bottom;width:100%;overflow:hidden;'
target='_PARENT' marginwidth='0'
marginheight='0' frameborder='0' scrolling='no' ></iframe>
Principally, it works this way:
Loads the iframe content
Runs the setiframeHeight
which then adjusts the iframe height to the proper content height .. with the given CSS and voila!
No iframe content is hidden, no double scrollbars!
Hope it helps! Demo here: https://copy.pc.pl/test.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="">
<meta name="viewport" content="width=, initial-scale=">
<title></title>
<script>
function getDocHeight(doc) {
doc = doc || document;
var body = doc.body,
html = doc.documentElement;
var height = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
return height;
}
function setIframeHeight(id) {
const ifrm = document.getElementById(id);
const doc = ifrm.contentDocument ? ifrm.contentDocument : ifrm.contentWindow.document;
ifrm.style.height = getDocHeight(doc) + 2 + "px";
}
</script>
</head>
<body>
<h1>Parent</h1>
<iframe id='wp-twoblock-0' src='otherdocument.html' onload='setIframeHeight(this.id);' style='border:0px;vertical-align:bottom;width:100%;overflow:hidden;' target='_PARENT' marginwidth='0' marginheight='0' frameborder='0' scrolling='no'></iframe>
</body>
</html>
Hide scroll bar
Unfortunately, you can't select items from an Iframe that is from a different origin unless you can specify that it is allowed in the header. There a way better answer on how to set up cross-origin header -
Edit cross-domain Iframe content Locally Only
You should not hide the Iframe's scroll bar, because that is the one that the user will want to use to scroll. Hide the content scroll bar like this.
body {
margin: 0;
padding: 0;
}
.contentArea {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
overflow-y: scroll;
}
iframe {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
border: 0;
}
.contentArea::-webkit-scrollbar {
display: none;
}
.contentArea {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
<body>
<div class="contentArea">
<iframe src="https://ajaymalhotra.in" title="Iframe Example"></iframe>
</div>
</body>
for comparability with other browsers read this. https://www.w3schools.com/howto/howto_css_hide_scrollbars.asp
You can hide the scroll bar with CSS
.contentArea iframe::-webkit-scrollbar {
width: 0px;
}
This should do what you need.

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>

Scroll div depending on parent position

I have a parent div with two child divs. The first child should be fixed when the parent is in viewport. The second child should scroll into position and overlap the first. Both child divs should be removed and follow the parent as soon as they reach the bottom of the parent.
Right now, I'm adding a class on scroll position but I'm not sure how to detect when the child is at bottom of parent and then remove the class.
var sticky = $('.sticky'),
scroll = $(window).scrollTop();
if (scroll >= 70) {
sticky.addClass('fixed');
} else {
sticky.removeClass('fixed');
}
How can I make the child divs follow the parent in the best way? I've tried to search for something similar what I want but couldn't find any good explanation.
This fiddle is what I've got so far.
If I am understanding this correctly, what you could do is measure bottom of parent div and child sticky div relative to the document.body, and if child element's bottom crossing parent's bottom you can remove .fixed class.
Something like this.
$(window).scroll(function(){
var sticky = $('.sticky'),
scroll = $(window).scrollTop();
if (scroll >= 70) {
sticky.addClass('fixed')
}else {
sticky.removeClass('fixed');
}
if(getBottom('.sticky') >= getBottom('.holder')){
sticky.removeClass('fixed');
}
});
function getBottom(element){
var $elm = $(element);
var offset = $elm.offset();
var top = offset.top;
return top + $elm.outerHeight();
}
body { margin: 0; }
section {
height: 2000px;
padding-top: 100px;
}
div {
width: 300px;
height: 100px;
}
.holder {
border: 1px solid black;
width: 500px;
height: 200px;
position: relative;
}
.sticky {
top:30px;
left:10px;
background: orange;
z-index: 9999;
position: relative;
}
.other-div {
background: gold;
top: 20px;
z-index: 0;
}
.fixed {
position: fixed;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Divs</title>
</head>
<body>
<section>
<div class="holder">
<div class="other-div fixed">This div should stay fixed for a while</div>
<div class="sticky">This div will become fixed on scroll</div>
</div>
</section>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
</body>
</html>
Jsfiddle of the above snippet https://jsfiddle.net/azs06/3ubshm4t/7/
Note, I made some css changes, which you can adjust as you need.

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