Maintain scroll position on expanding page with JavaScript - javascript

I am trying to maintain scroll position despite content being added to the top of the page, when you scroll to the top in a React app I am working on.
I have tried:
Getting the position of the top element on the page before the content is added and then using something like window.scrollTo(0, elementPosition)
Calculating the difference between the page height before and after the added content and scrolling back to the original position based on the difference.
Determining where to scroll based on how far the Viewport was from the bottom before the added content was displayed as that position should not be changing.
None of it worked and I am at a loss. This is the code that is responsible for creating the content, rendering a portion of the content and then adding content once you scroll to the top:
//Creating the content:
useEffect(() => {
fetch('/api/endpoint', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(res => {
setLogUsers(res.users);
setTotalAmount(res.log.length);
setMessages(limitedRender(res.log));
})
.catch(err => console.log(err))
}, [renderAmount])
return(
messages.map(e => {
return(
<Msg message = {e}/>
);
})
);
//Selecting a portion of the content and detecting when you scroll to the top:
const [totalAmount, setTotalAmount] = useState();
const [renderAmount, setRenderAmount] = useState(10);
const limitedRender = (arr) => {
if (arr.length >= renderAmount) {
return arr.slice(arr.length - (renderAmount), arr.length)
}
else {
return arr
}
}
const posTop = () => {
return (
typeof window.scrollY != 'undefined' ? window.scrollY :
document.documentElement && document.documentElement.scrollTop ?
document.documentElement.scrollTop :
document.body.scrollTop ? document.body.scrollTop : 0
);
}
document.addEventListener('scroll', () => {
if (posTop() === 0) {
if (totalAmount - renderAmount >= 10) {
setRenderAmount(renderAmount + 10);
}
else if (totalAmount - renderAmount < 10) {
setRenderAmount(renderAmount + (totalAmount - renderAmount));
}
}
})
//Also may be important. I use this bit of code to ensure that the component stays scrolled to the bottom on initial render.
const btmUpView = () => {
window.scrollTo(0, document.body.scrollHeight);
}
btmUpView();
return(
<div>
<div>
<MsgFeed/>
</div>
<div>
<input type="text"></input>
<button onClick={() => sendMsg()}>Send</button>
</div>
</div>
);

Related

Recat.js Vanilla JavaScript Fade in animation effect

I want to make a fade-in animation effect using javaScript scroll event. What should I do?
The current style is working on style components. Please advise
const handleAnimation = ( ) => {
const windowHeight = window.innerHeight;
const scrollY = window.scrollY;
const bannerTop = ref.current && ref.current.getBoundingClientRect().top;
const bannerAbsolute = scrollY + bannerTop;
return { windowHeight: windowHeight, scrollY: scrollY };
}
useEffect(() => {
window.addEventListener('scroll', handleAnimationScroll);
return () => {
window.removeEventListener('scroll', handleAnimationScroll);
};
}, []);
handleAnimationScroll();

Having conditional rendering on an id of div tag

Currently I'm trying to move my image from center to left and left to right as the user scrolls down. For achieving this, I'm using useEffect to manipulate my DOM events. I want the layout in such a way that after the user has scrolled 600 pixels in height the image starts moving to the right. For this I tried conditionally rendering a div tag but I get an error in my useEffect since it doesnt recognize the other element. So how can I move my image when it reaches a certain height?
CodeSandbox: https://codesandbox.io/s/misty-sun-e6odq?file=/src/App.js
Code:
const [display, setDisplay] = useState(false);
useEffect(function onFirstMount() {
const changeBackground = () => {
let value = window.scrollY;
console.log(value);
let img = document.getElementById("moveLeft");
let img2 = document.getElementById("moveRight");
img.style.transform = `translateX(-${value * 0.5}px)`;
img2.style.transform = `translateX(${value * 0.5}px)`;
if (value > 600) {
setDisplay(true);
} else {
setDisplay(false);
}
};
window.addEventListener("scroll", changeBackground);
return () => window.removeEventListener("scroll", changeBackground);
}, []);
return (
<>
<div className="App">
<div class="inflow">
<div class="positioner">
<div class="fixed">
<div id={display?"moveRight":"moveLeft"}>
<img
alt="passport"
src="https://cdn.britannica.com/87/122087-050-1C269E8D/Cover-passport.jpg"
/>
</div>
</div>
</div>
</div>
</div>
<div className="App2">
</div>
<div className="App2"></div>
</>
As you have two calls to getElementById and you dynamically change the id based on state, you will always have an undefined element.
You could fix it like so
let img =
document.getElementById("moveLeft") ||
document.getElementById("moveRight");
const val = img.id === "moveLeft" ? -(value * 0.5) : value * 0.5;
img.style.transform = `translateX(${val}px)`;
And remove the img2 call, as there is always one of the above in the document. Example of this here: https://codesandbox.io/s/stoic-babycat-6bl5i?file=/src/App.js
You could also try to achieve it with only one image:
useEffect(
function onFirstMount() {
const changeBackground = () => {
if (!imgRef || !imgRef.current) return;
const value = window.scrollY;
if (value > 600) {
imgRef.current.style.display = `block`;
} else {
imgRef.current.style.display = `none`;
return;
}
const progress = (window.innerWidth / 100) * moveRatio;
if (
animData.current.isGoingRight &&
animData.current.currentPos >= window.innerWidth - 200
) {
animData.current.isGoingRight = false;
} else if (
!animData.current.isGoingRight &&
animData.current.currentPos <= startPosition
) {
animData.current.isGoingRight = true;
}
if (animData.current.isGoingRight)
animData.current.currentPos += progress;
else animData.current.currentPos -= progress;
imgRef.current.style.transform = `translateX(${animData.current.currentPos}px)`;
};
window.addEventListener("scroll", changeBackground);
return () => window.removeEventListener("scroll", changeBackground);
},
[imgRef]
);
This one moves in relation to the window inner width, you can see it here https://codesandbox.io/s/hidden-microservice-xb2ku?file=/src/App.js, that's just an example, I'm sure there are another approaches

Action based on visiblity and duration of element in viewport

I am using IntersectionObserver to know whether a particular element is visible to the user or not. If it is visible then I am fetching some data from the server-side. Now I would like to know whether there is any API/approach to know how long an element is visible to the user.
The use case that I am trying to solve is this - If an element is visible in the viewport for 5secs then fetch some data from the server.
Thanks in advance.
const scrollArea = document.querySelector('.scroll-area');
let executeApiCall = null;
const observer = new IntersectionObserver( event => {
console.log(`intersect`, event);
if ( event[0].intersectionRatio === 0 ) {
console.log('element not in viewport');
clearTimeout(executeApiCall);
} else {
console.log('element is in viewport');
if(!executeApiCall) {
executeApiCall = setTimeout(() => {
console.log('api call');
}, 5000);
}
}
}, {
root: scrollArea
});
See: https://stackblitz.com/edit/intersection-observer-hynpuj
If return true
after setTimeout execute your next code...
let elem = document.getElementById("app");
function isInViewportFunc(el){
var rect = el.getBoundingClientRect();
return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top < window.innerHeight;
}
console.log(
isInViewportFunc(elem)
)
<br/>
<br/><br/>
<br/>
<br/><br/><br/><br/><br/><br/><br/>
<div id="app">Text</div>

Horizontal Scrolling on React Component Using Vertical Mouse Wheel

I have a component that resizes into a horizontal row of bootstrap cards when in a smaller desktop window. For users without a horizontal mouse wheel and not using a touchpad, I would like to allow users to scroll horizontally using their vertical mouse wheel movements when hovering over this particular component.
Here is the original StackOverflow issue I based my code off of:
https://stackoverflow.com/a/15343916/8387497
Horizontal Scroll helper component:
function horizontalScroll (event) {
const delta = Math.max(-1, Math.min(1, (event.nativeEvent.wheelDelta || -event.nativeEvent.detail)))
event.currentTarget.scrollLeft -= (delta * 10)
event.preventDefault
}
How I've implemented it on component requiring horizontal scrolling:
<Row className='announcements-home' onWheel={horizontalScroll} >
When I've placed this horizontalScroll helper function within the React onWheel event, it scrolls horizontally AND vertically. My desired outcome is just horizontal scrolling. Also, Firefox does not appear to respond at all to horizontal scrolling with these changes.
Okay, so the issue seems to be that you only refer to the function event.preventDefault rather than invoking it.
Adding some brackets at the end to invoke it should do the trick:
event.preventDefault().
I however found this issue while looking for some simple code to use, so I will also leave the hook I made for this if others in the same situation:
import { useRef, useEffect } from "react";
export function useHorizontalScroll() {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = e => {
if (e.deltaY == 0) return;
e.preventDefault();
el.scrollTo({
left: el.scrollLeft + e.deltaY,
behavior: "smooth"
});
};
el.addEventListener("wheel", onWheel);
return () => el.removeEventListener("wheel", onWheel);
}
}, []);
return elRef;
}
Usage:
import React from "react";
import { useSideScroll } from "./useSideScroll";
export const SideScrollTest = () => {
const scrollRef = useHorizontalScroll();
return (
<div ref={scrollRef} style={{ width: 300, overflow: "auto" }}>
<div style={{ whiteSpace: "nowrap" }}>
I will definitely overflow due to the small width of my parent container
</div>
</div>
);
};
Note:
The scroll behavior "smooth" seems to be giving some trouble when trying to do continuous scrolling. This behavior can be omitted to have proper continuous scrolling, but it will look jerky.
As far as I know, there is no easy solution for this. I have however created a rather involved solution in my own project, so thought some people may appreciate that also: https://gist.github.com/TarVK/4cc89772e606e57f268d479605d7aded
onWheel = (e) => {
e.preventDefault()
var container = document.getElementById('container')
var containerScrollPosition = document.getElementById('container').scrollLeft
container.scrollTo({
top: 0,
left: largeContainerScrollPosition + e.deltaY
behaviour: 'smooth' //if you want smooth scrolling
})
}
There is another small problem with TarVK's proposed hook. Once you scroll to the end and continue scrolling nothing happens, when we are used to containing elements starting to scroll as well. So I made a fix for that:
export function useHorizontalScroll () {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = (e) => {
if (e.deltaY === 0) return;
if (
!(el.scrollLeft === 0 && e.deltaY < 0) &&
!(el.scrollWidth - el.clientWidth - Math.round(el.scrollLeft) === 0 &&
e.deltaY > 0)
) {
e.preventDefault();
}
el.scrollTo({
left: el.scrollLeft + e.deltaY,
behavior: 'smooth'
});
};
el.addEventListener('wheel', onWheel);
return () => el.removeEventListener('wheel', onWheel);
}
}, []);
return elRef;
}
It's conditionally preventing default behavior only when there is space to scroll in that direction, so when there is no space to scroll, for example the whole page will start to scroll. The change is here:
if (
!(el.scrollLeft === 0 && e.deltaY < 0) &&
!(el.scrollWidth - el.clientWidth - Math.round(el.scrollLeft) === 0 &&
e.deltaY > 0)
) {
e.preventDefault();
}
I can not comment, because my reputation is not enough.
#arVK's answer works, but using 'scrollBy' instead of 'scrollTo' can get smooth wheel.
import { useRef, useEffect } from "react";
export function useHorizontalScroll() {
const elRef = useRef();
useEffect(() => {
const el = elRef.current;
if (el) {
const onWheel = e => {
if (e.deltaY == 0) return;
e.preventDefault();
el.scrollBy(e.deltaY, 0);
};
el.addEventListener("wheel", onWheel);
return () => el.removeEventListener("wheel", onWheel);
}
}, []);
return elRef;
}
You can use onWheel event directly:
import React, { useRef } from "react";
export const Content = () => {
const ref = useRef<HTMLDivElement>(null);
const onWheel = (e: UIEvent) => {
const elelemnt = ref.current;
if (elelemnt) {
if (e.deltaY == 0) return;
elelemnt.scrollTo({
left: elelemnt.scrollLeft + e.deltaY,
});
}
};
return (
<div ref={ref} onWheel={onWheel}>
TEST CONTENT
</div>
);
};

Why isn't my scrolling position working in react?

I'm trying to make a sticky nav with an active state while scrolling. So when you're scrolling over each section, the nav has an active state. Kind of like what is seen here:
https://codepen.io/rishabhp/pen/aNXVbQ
The problem I'm having is some of my numbers aren't correct. Here's the code:
handleScroll = () => {
let sections = document.querySelectorAll('.deal-details__container'),
nav = document.querySelectorAll('.overview-nav'),
navHeight = nav[0].clientHeight;
let totalScroll = document.body.getBoundingClientRect().top;
window.addEventListener('scroll', () => {
sections.forEach(section => {
let topOffset = section.getBoundingClientRect().top;
let top = topOffset - navHeight,
bottom = top + section.clientHeight;
if (totalScroll >= top && totalScroll <= bottom) {
this.setState({ activeSection: true });
} else {
this.setState({ activeSection: false });
}
});
});
console.log(totalScroll);
};
For instance my totalScroll inside of the forEach only amounts to -20 or something similar.
I feel like I'm missing something simple. Any thoughts?

Categories

Resources