I have intersection observer object it works, but I want it to notify my once some element is 100pixels over or at bottom of intersection point.
With default config it just changes value of .isIntersection once the element is exactly in view. But I want to do some stuff when elements are 100pixels above or below the viewport.
This is my code:
var iObserver = new IntersectionObserver(function(element) {
console.log('elementi', element); // I want to trigger here when elementi is 100px or less of distance to the viewport.
});
var el;
for (var i = 0; i < elements.length; i++) {
el = elements[i];
console.log('eli', el);
iObserver.observe(el);
}
UPDATE
Thanks to user for answer I used this and it worked:
var iObserver = new IntersectionObserver(function(entryEvent) {
//...
}, {'rootMargin': '100px 0px 100px 0px'});
You can define the rootMargin top and bottom in the options you pass to the observer.
In the demo, hover the red rectangle, when it reaches a distance of 10px from the .container the observer is called:
const options = {
root: document.querySelector('.container'),
rootMargin: '10px 0px 10px 0px',
};
let i = 0;
const iObserver = new IntersectionObserver((entries) => console.log(`intersection ${i++}`), options);
iObserver.observe(document.querySelector('.element'));
.container {
position: relative;
height: 20px;
background: lightblue;
}
.element {
position: absolute;
top: calc(100% + 30px);
height: 100px;
width: 100px;
background: red;
margin-bottom: 20px;
transition: top 5s;
}
.element:hover {
top: calc(100% - 30px);
}
.as-console-wrapper {
height: 50px;
}
<div class="container">
<div class="element">1</div>
</div>
Related
I have this navbar and everytime I click an option in the navbar the absolute positioned indicator gets the position of the option on the left and the width with the help of getBoundingClientRect() and it is moved to the target.
The problem is when I resize the window the indicator changes it's position and moves away.To stay in the same place when I resize the window I applied an eventListener to the window and everytime is resized I get the new values of left and width with getBoundingClientRect().
It works but I wonder if that is a bad way to do it because of the calculations that happen everytime the window is resized and if that is the case what is a better way to do this.
Here is the code:
const navigator = document.querySelector('.navigator');
const firstOption = document.querySelector('.first-option');
const navOptions = document.querySelectorAll('.nav-option');
const nav = document.querySelector('nav');
navigator.style.left = `${firstOption.getBoundingClientRect().left}px`;
navigator.style.width = `${firstOption.getBoundingClientRect().width}px`;
nav.addEventListener('click', function(e) {
if(e.target.classList.contains('nav-option')) {
navOptions.forEach(option => option.classList.remove('nav-option-active'));
e.target.classList.add('nav-option-active');
navigator.style.left = `${e.target.getBoundingClientRect().left}px`;
navigator.style.width = `${e.target.getBoundingClientRect().width}px`;
};
});
window.addEventListener('resize', function() {
let navOptionActive = nav.querySelector('.nav-option-active');
navigator.style.left = `${navOptionActive.getBoundingClientRect().left}px`;
navigator.style.width = `${navOptionActive.getBoundingClientRect().width}px`;
});
* {
margin: 0;
padding: 0;
}
nav {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin: 100px auto;
padding: 7vh 30vw;
width: auto;
background:#eeeeee;
}
.nav-option {
padding: 0 15px;
font-size: 22px;
cursor: pointer;
}
.navigator {
position: absolute;
left: 0;
bottom: 0;
height: 5px;
background: orangered;
transition: .4s ease all;
}
#media (max-width: 1200px) {
.nav-option {
font-size: 18px;
padding: 10px;
}
}
<nav>
<div class="navigator"></div>
<div class="nav-option first-option nav-option-active">HOME</div>
<div class="nav-option">INFO</div>
<div class="nav-option">CONTACT</div>
<div class="nav-option">ABOUT</div>
<div class="nav-option">MENU</div>
</nav>
You can make your <nav> element tightly wrap the buttons, then position the underline relative to the <nav>. A new wrapper <div> around the <nav> takes care of the margins and gray background. Instead of getBoundingClientRect() you then need to use offsetLeft and offsetWidth.
Note that this doesn't handle the changes in response to your #media query. For that, you could add a resize listener that specifically only handles changes across the 1200px threshold. Alternatively, you could reparent the underline to be a child of the actual nav button while it's not animating. Neither solution is great, but both would get the job done.
const navigator = document.querySelector('.navigator');
const firstOption = document.querySelector('.first-option');
const navOptions = document.querySelectorAll('.nav-option');
const nav = document.querySelector('nav');
navigator.style.left = `${firstOption.offsetLeft}px`;
navigator.style.width = `${firstOption.offsetWidth}px`;
nav.addEventListener('click', function(e) {
if(e.target.classList.contains('nav-option')) {
navOptions.forEach(option => option.classList.remove('nav-option-active'));
e.target.classList.add('nav-option-active');
navigator.style.left = `${e.target.offsetLeft}px`;
navigator.style.width = `${e.target.offsetWidth}px`;
};
});
* {
margin: 0;
padding: 0;
}
.nav-wrapper {
margin: 100px 0;
display: flex;
justify-content: center;
background: #eeeeee;
}
nav {
position: relative;
display: flex;
}
.nav-option {
padding: 7vh 15px;
font-size: 22px;
cursor: pointer;
}
.navigator {
position: absolute;
left: 0;
bottom: 0;
height: 5px;
background: orangered;
transition: .4s ease all;
}
#media (max-width: 1200px) {
.nav-option {
font-size: 18px;
padding: 10px;
}
}
<div class="nav-wrapper">
<nav>
<div class="navigator"></div>
<div class="nav-option first-option nav-option-active">HOME</div>
<div class="nav-option">INFO</div>
<div class="nav-option">CONTACT</div>
<div class="nav-option">ABOUT</div>
<div class="nav-option">MENU</div>
</nav>
</div>
If you have to use getBoundingClientRect (which honestly has nothing wrong with it), you can throttle the call, so that only the last resize after sufficient time has passed will execute. There are zillion ways of doing this, I will leave one example:
window.onresize = (function(id = null, delay = 600, oEvent = null){
return function fire(event){
return (new Promise(function(res,rej){
if (id !== null){
oEvent = event;
rej("busy");
return;
}
id = setTimeout(function(){
res(oEvent || event);
},delay);
})).then(function(event){
id = null;
console.log(event, "do getBoundingClientRect call");
}).catch(function(){void(0);});
};
}());
Replace console.log with what you want to do.
Your other option is to switch to intersection observer, if you can restructure your rendering logic. That will require some work
I want to make custom infinite scroll, so when I try this
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
if(window.innerHeight-scrollPosition >100){
console.log("end")
}
but it does not work.
If your wanting to know when your 100 pixels away from the end, then you can get the current element scrollHeight and subtract the parent elements height and then subtract your extra 100.
Now compare this to the parentElements scrollTop, if it's greater then your scrollbar is within this 100px section..
Example below.. If you scroll down until your within 100 pixels of the end, the background will change silver.
document.body.innerText =
new Array(400).fill('Scroll me down, ').join('');
window.addEventListener('scroll', (e) => {
const body = document.body;
const parent = body.parentElement;
const pixelsFromBottom =
body.scrollHeight -
parent.clientHeight
-100;
body.classList.toggle('inf'
,parent.scrollTop > pixelsFromBottom);
});
.inf {
background-color: silver;
}
This will work not with just Body, but also any sub controls too, below I've created a header footer, with and a scrollable region.
const scroller = document.querySelector('main');
const target = document.querySelector('.content');
target.innerText =
new Array(400).fill('Scroll me down, ').join('');
scroller.addEventListener('scroll', (e) => {
const body = target;
const parent = body.parentElement;
const pixelsFromBottom =
body.scrollHeight -
parent.clientHeight
-100;
parent.classList.toggle('inf'
,parent.scrollTop > pixelsFromBottom);
});
html, body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
background-color: cyan;
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
}
main {
position: relative;
flex: auto;
overflow-y: scroll;
background-color: white;
}
.content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.inf {
background-color: silver;
}
<header>This is a header</header>
<main><div class="content">main</div></main>
<footer>This is the footer</footer>
I am trying to make a feature where a button slides up to take you back to the top after scrolling. However for some reason it just won't work. It works fine when I use
if (scroll >= 84) {
$("#ID").fadeIn();
} else {
$("#ID").fadeOut();
}
But when it just doesnt work with either animate or css.
The way i keep track of the scroll is with a eventlistener, and declaring a let with the value:
let scroll = this.scrollY;
I hope this is enough information to give you an idea of what im going for. Thanks for the help in advance
Try this (run and scroll down):
var timeout;
for (var i = 0; i < 200; i++) {
document.body.innerHTML += "Example Text<br>"
}
window.addEventListener("scroll", () => {
var scrollDistance = window.scrollY
if (scrollDistance >= 100) {
clearTimeout(timeout);
document.querySelector("button").style.display = "";
setTimeout(function() {
document.querySelector("button").style.opacity = "1"
}, 1);
document.querySelector("button").style.cursor = "pointer";
} else {
clearTimeout(timeout);
document.querySelector("button").style.opacity = "0";
document.querySelector("button").style.cursor = "";
timeout = setTimeout(function() {
document.querySelector("button").style.display = "none"
}, 500);
}
})
document.querySelector("button").addEventListener("click", () => {
window.scroll({
top: 0,
left: 0,
behavior: 'smooth'
});
})
button {
position: fixed;
right: 10px;
bottom: 10px;
border-radius: 50%;
height: 50px;
width: 50px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffcccb;
border: 2px solid black;
opacity: 0;
outline: none;
transition: opacity 0.5s;
}
button>div {
width: 20px;
height: 20px;
border-width: 5px 5px 0px 0px;
border-style: solid;
border-color: black;
transform: translate(0, 4px) rotate(-45deg);
}
<button>
<div></div>
</button>
Note that most of the code is just setup. This is the actual code that does the scrolling work:
document.querySelector("button").addEventListener("click", () => {
window.scroll({
top: 0,
left: 0,
behavior: 'smooth'
});
})
It simply listens for the click of the button, and scrolls to the top.
Maybe an obvious question but how do I make an element with a absolute position not overflow its container when moving it's position right? I know I could change it to relative position or move it 99% but for my project that won't due. I tried using margins, padding, object-fit, all with no success. Thanks for any help
var green = document.getElementById('green');
function myFunct() {
green.style.right = '100%';
}
h1 {
position: relative;
width: 80%;
height: 100px;
margin: auto;
background-color: red;
}
#green {
position: absolute;
right: 0px;
height: 100px;
background-color: green;
width: 20px;
}
<h1>
<div id = 'green'></div>
</h1>
<button onclick="myFunct()">FindHighScore</button>
Use CSS calc()
var green = document.getElementById("green");
function myFunct() {
green.style.right = "calc(100% - 20px)";
}
Or, apply left: 0 and right: auto (reset)
var green = document.getElementById("green");
function myFunct() {
green.style.left = "0";
green.style.right = "auto";
}
A <div> should not be in a <h1> tag by the way.
You can set overflow to hidden at parent container.
<h1> permitted content is Phrasing content
var green = document.getElementById('green');
function myFunct() {
green.style.right = '100%';
}
div:not(#green) {
position: relative;
width: 80%;
height: 100px;
margin: auto;
background-color: red;
overflow: hidden;
}
#green {
position: absolute;
right: 0px;
height: 100px;
background-color: green;
width: 20px;
}
<div>
<div id='green'></div>
</div>
<button onclick="myFunct()">FindHighScore</button>
My code allows scrolling vertically in the bottom section to control scrolling horizontally in the top section.
My jsfiddle
You'll see the colors shift through a gradient. Works pretty well. Problem is that I can't quite seem to get the inverse to work. Scrolling horizontally in the top controls scrolling in the bottom.
Any ideas?
Here's the script that makes it work:
// Add event listener for scrolling
$("#bottom").on("scroll", function bottomScroll() {
var scrolledleft = parseInt($("#bottom").scrollTop()) * 1;
console.log(scrolledleft + scrolledright)
$("#top").scrollLeft(scrolledleft + scrolledright)
})
//Move right column to bottom initially
$("#top").scrollLeft($("#top").height())
//Get actual distance scrolled
var scrolledright = parseInt($("#top").scrollLeft())
Your event handlers need to temporarily cancel each other so that they don't both fire at once. You want to calculate your position percentage based on the current scrollLeft / (width of child div - width of container), then apply that percentage to the other element, and likewise for top/height. Also I changed the height of #top to 50% in CSS.
var handler = function (e) {
var src = e.target;
// the first element that triggers this function becomes the active one, until it's done
if (!activeScroller) activeScroller = src.id;
else if (activeScroller != src.id) return;
var $b = $("#bottom");
var $t = $("#top");
var scrollH = $("#bottom-content").height() - $b.height();
var scrollW = $("#top-content").width() - $t.width();
var scrollPct = 0;
if (src.id == "top") {
if (scrollW > 0) {
scrollPct = $t.scrollLeft() / scrollW;
}
$b.scrollTop(scrollH * scrollPct);
} else {
if (scrollH > 0) {
scrollPct = $b.scrollTop() / scrollH;
}
$t.scrollLeft(scrollW * scrollPct);
}
// give all animations a chance to finish
setTimeout(function () { activeScroller = ""; }, 100);
};
var activeScroller = "";
$("#top,#bottom").on("scroll", handler);
#top {
position: absolute;
margin: auto;
top: 0;
right: 0;
left: 0;
width: 100%;
height: 50%;
position: fixed;
overflow: auto;
background: red;
}
#top-content {
height: 100%;
width: 2000px;
background: linear-gradient(90deg, red, blue);
}
#bottom {
position: absolute;
margin: auto;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 50%;
position: fixed;
overflow: auto;
background: green;
z-index: 100;
}
#bottom-content {
height: 2000px;
width: 100%;
background: linear-gradient(0deg, orange, green);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="top">
<div id="top-content"></div>
</div>
<div id="bottom">
<div id="bottom-content"></div>
</div>
Check out this:
https://jsfiddle.net/1p7gp72h/1/
I'm not sure what your end goal is here.
$("#top").on("scroll", function topScroll() {
var scrolledleft = parseInt($("#top").scrollTop()) * 1;
$("#bottom").scrollLeft(scrolledleft + scrolledright)
});
#top {
top: 0;
right: 0;
left: 0;
width: 5000px;
height: 100%;
overflow: auto;
background: red;
overflow-x: scroll;
overflow-y: hidden;
white-space:nowrap;
}
Scroll to left ::
$('div').scrollLeft(1000);
Scroll back to normal/ scroll to right ::
$('div.slick-viewport').scrollLeft(-1000);