How do I prevent window.pageYOffset from resetting on render? - javascript

I have a react component where I want to change the header based on the scroll event. I'm attaching an event handler and based on scroll position and toggling a display class to hide ro show the desired elements.
Where I'm having trouble is, there seems to be some glitchy behavior when my component tries to re render. I have an example in codesandbox below.
import React from "react";
import "./styles.css";
export default function App() {
const [scrollY, setScrollY] = React.useState(0);
React.useEffect(() => {
const handleScroll = () => {
console.log(window.pageYOffset);
setScrollY(window.pageYOffset);
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [setScrollY]);
const scrolled = () => scrollY > 40;
return (
<div className="App">
<div className={`header ${scrolled() ? "d-none" : ""}`}>Header Main</div>
<div>
<div className={`header-secondary ${scrolled() ? "d-none" : ""}`}>
Header Secondary
</div>
<div className={`header-scrolled ${!scrolled() ? "d-none" : ""}`}>
HeaderScrolled
</div>
<div>Scroll Position: {scrollY}</div>
{[...Array(100)].map((e, i) => (
<div>
<div className={scrolled()}>{`SCROLLING`}</div>
</div>
))}
</div>
</div>
);
}
My code sandbox: https://codesandbox.io/s/wizardly-saha-0oklr
If you notice I have my hide/unhide compnent condition set at 40px. When you scroll slowly around 40px the header will snap back and for some reason the window.pageYOffset will reset to 0. I can't figure out why this is?
If you scroll past fast enough it doesn't matter but right around where I toggle the display class there is some odd behavior.
EDIT: Updated Example
Effectively what I need to do is have a smmoth transition from Header Main to Header Secondary. I can't really change styling on Header Main because I don't own that part of the product.

The problem is about your header. When you are at the top header affects the height of the scrolling body because it is position relative. When you start to scroll down it becomes fixed and leaves the body so it doesn't affect. When you scroll back to top it affect scroll height again.
So there are some tricks to resolve this issue. Giving padding-top: 50px; to scrolling element and using header always fixed will save you.
.App {
padding-top: 50px;
}
.header {
width: 100%;
height: 50px;
background-color: teal;
position: fixed;
top: 0;
}
.header-scrolled {
width: 100%;
height: 50px;
background-color: green;
color: white;
position: fixed;
top: 0;
}
https://codesandbox.io/s/lingering-pine-puunf

I was having the exact same issue for two days. The fix I implemented was to render a "sticky-nav" component in addition to the original "nav" component. This way the original nav will still exist on top (but above the visible window so you'd never know) and when you scroll down the "sticky-nav" appears. Since the original nav is always there and doesn't change position, it won't affect the scroll position and cause the glitch when slowly scrolling back up.

Related

Why isn't the width of an image changing after I use "width: 8%;" in CSS?

I'm new to react and I was trying to make a simple website with music notes (just some images) and wanted each note to change color when hovering over it. I know I can do this with :hover, but I wanted to try out useState for practice. I finally got the toggle feature (just the feature that made it change color when hovering) to work, but then in the process, the width got messed up. All other parts of css (position, color etc.) are working so I'm a bit confused as to why the width is staying the original width. Here is the code I have currently. The toggle feature is only on note3 right now because that's the note I was playing around with.
The first bit of code is essentially part of my index.js file with the music note I was working on.
const Body = () => {
const [isNote3, setNote3] = useState("true");
const ToggleNote3 = () =>{
setNote3(!isNote3);
}
const [isB1, setB1] = useState("true");
const ToggleB1 = () =>{
setB1(!isB1);
}
return (
<div>
<div className="sheetmusic">
<img className="sheet" src={sheetmusic} />
</div>
<div className="notes">
<div className={isNote3 ? "note3" : "n3"}>
<img onMouseEnter={ToggleNote3 } src={note1} />
</div>
</div>
</div>
)
}
The second snippet is the relevant css that corresponds with note3.
.n3{
filter: invert(100%) sepia(26%) saturate(5791%) hue-rotate(58deg) brightness(116%) contrast(101%);
left: 25%;
position: absolute;
max-width: 8%;
max-height: auto;
top: 30%;
}
.note3 {
position: absolute;
left: 25%;
max-width: 8%;
max-height: auto;
top: 30%;
}
Here is also a picture of the current situation on my website. (the large note is the one that currently toggles). 3
I've tried playing around with it for a bit and just don't seem to know the issue. Any help would be GREATLY appreciated, thanks so much.
From your CSS snippet both classes note3 and n3 have the same value for max-width so I don't see why the width would change. Try using different values.
Edit: In CSS by default img width and height are set to auto. So what you need to do is add img { max-width: 100%; } to confine all your images to the width of the parent container. See https://codesandbox.io/s/relaxed-mcnulty-p72by?file=/src/styles.css

Can't animate modal on ReactJS

I'm currently creating my custom implementation of a modal. All works perfectly fine so far but I can't seem to animate it and I can't get my head around it.
This is my Modal component
import React from 'react'
import Slider from './Slider'
import {IoIosCloseCircleOutline} from "react-icons/io"
import styled from "styled-components";
export default function Modal(props) {
const Modal = styled.div `
transform: translateX(${({animateSlideInRight}) => (animateSlideInRight ? "0" : "100vw")});
transition: transform 1s;
width: 1000px;
height: 650px;
z-index: 100;
position: fixed;
background: white;
transition: all 1.1s ease-out;
box-shadow:
-2rem 2rem 2rem rgba(black, 0.2);
visibility: visible;
display: flex;
border-bottom-right-radius: 100px;
`
const closeModal = () => {
props.setShow(false)
}
const data = props.data
if (!props.show) {
return null
}
return (
<div className="modalWrapper">
<Modal className="modal" id="modal" animateSlideInRight = {props.show}>
<div className="modalHeaderWrapper">
<IoIosCloseCircleOutline className="modalCloseCross" onClick={closeModal}/>
<img src={data[0].logo} alt="logo" />
<h2>{data[0].title}</h2>
</div>
<div className="modalRightFlex">
<Slider
images={[data[0].image1Carrousel, data[0].image2Carrousel, data[0].image3Carrousel]}
/>
<div className="modalRightDescription">
<h1>Description</h1>
<p>{data[0].description}</p>
<h1>Technologies</h1>
<div className="modalTechnologiesWrapper">
{data[0].technologiesUsed.map((image) => {
return <img src={image}/>
})}
</div>
</div>
</div>
</Modal>
</div>
)
}
As you see my modal is a styledComponent that defines whether to translate in X or not depending on the show state. In my scenario I had to lift up state since I'm opening this modal from clicking on a card which in itself is a different component, so their ancestor is taking care of the states.
My current CSS for modal is as seen in the styled div.
Things I have tried
1-tried having a regular div and handle the animation through CSS with keyframes --> It works for sliding in but it doesn't when I close (in that instance I had my show state defining a class name for the modal with a different animation for each of them)
2-tried to set a animate state and define the className based on whether that state is true or false. It works the first time when I close (despite having to introduce a timeout of the animation duration between setting animate to false and show to false) but then it goes bonkers and starts flickering everywhere.
Anyway someone can see the issue? Many thanks
edit
Sanbox link: https://codesandbox.io/s/trusting-shape-vxujw
You should define Modal in the outer scope of the component rendering it, the animation does not complete and you resetting it by redefining it on the next render.
Also resetting an animation should be done with none instead of giving an actual length.
Moreover, there might be more CSS bugs related that can hide your modal animation like z-index and position, if your question is focused on an animation problem you should remove all the noise around it.
See working example:
const Animation = styled.div`
transform: ${({ animate }) => (animate ? "none" : "translateX(500px)")};
transition: transform 1s;
`;
function Modal(props) {
return <Animation animate={props.show}>hello</Animation>;
}
function Component() {
const [show, toggle] = useReducer((p) => !p, false);
return (
<>
<Modal show={show} />
<button onClick={toggle}>show</button>
</>
);
}
Also, you shouldn't return null when you don't want to animate, you will lose the close animation.
// remove this code
if (!props.show) {
return null;
}

Show a certain element in a component on scroll down and hide on scroll Up Reactjs

I was creating a button that will be visible when the user scrolls down and will get invisible if the user reaches the top.
I am using React hooks to track the scrolling but can't access up to which part the user has scrolled. I am using Html section of 100vh height each (3 sections) and a fixed header on top there is a login/register button which will be shown when the user scrolls down ie as soon as the user reaches 2nd section and will remain visible until the user reaches the 1st section ie root section. I have used scroll-snap-type: y mandatory; is CSS of the main container
React Code goes here:
import React, { useRef, useEffect, useState } from 'react'
import { useHistory } from "react-router-dom";
import Header from './Header'
import Sec1 from './Sec1'
import Sec2 from './Sec2'
import Sec3 from './Sec3'
import './Home.css'
function Home() {
let history = useHistory();
const loginregister = () => {
//on click login move to login register page
}
var mainConatiner = useRef(null)
useEffect(() => {
mainConatiner.current.addEventListener('scroll', () => {
console.log("srolling");
})
})
return (
<div className="main-container" ref={mainConatiner}>
<Header loginregister={loginregister} />
<div className="middle">
{/* Root */}
<Sec1 loginregister={loginregister} />
{/* Bank */}
<Sec2 />
{/* Section 3*/}
<Sec3 />
</div>
</div>
)
}
export default Home
CSS code goes here
.main-container {
background: #FFFCF5;
height: 100vh;
width: 100vw;
display: grid;
grid-template-rows: 0fr 1fr;
overflow-y: scroll;
scroll-padding-top: 10vh;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
}
::-webkit-scrollbar {
width: 0px;
}
.header {
display: grid;
grid-template-columns: 1fr 2fr 0.4fr;
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
section {
height: 100vh;
scroll-snap-align: center;
padding-top: 15vh;
display: grid;
justify-content: center;
}
I want to get the section the user is viewing as on a single scroll the section changes. The useEffect() hook is called multiple times
I have done similar thing with my project and I am going to show you how.
My idea was that I had a Chat window, and when user scrolls up to read previous messages, he can see a button to go back to bottom.
I had something like this in my ReactJS file that renders my div.
import React, { Component } from 'react';
import $ from 'jquery';
class Chat extends Component{
constructor(props){
super(props);
this.state = {
scroll : true,
chatMessages : []
}
}
componentDidMount(){
if(this.state.scroll){
this.scrollToBottom();
}
this.scrollToBottom(); //Initially scroll to the bottom of page.
}
scrollToBottom(){ //Scroll to the bottom of a div
$('#chat_messages_container').scrollTop($('#chat_messages_container')[0].scrollHeight);
}
handleScroll(){ //Handle every scroll event
let elem = $('#chat_messages_container');
if(elem!=undefined){
if((elem[0].scrollHeight - elem.scrollTop()) > 1300){
this.setState({
scroll : false
});
}
else{
this.setState({
scroll : true
})
}
}
}
render(){
let chatPoruke = [];
for (var i = 0; i < this.state.chatMessages.length; i++) { //Getting messages
var poruka_obj = this.state.chatMessages[i];
chatPoruke.push(
<ChatMessage props={poruka_obj} key={i}/>
);
}
let scrollBackToBottom = "";
if(!this.state.scroll){ //If false -> it renders the button for scroll to bottom
scrollBackToBottom = <div id="scrollToBottom" onClick={()=>{this.scrollToBottom()}}><i className="fas fa-arrow-down"></i> Scroll Down <i className="fas fa-arrow-down"></i></div>
}
return(
<div id="chatdiv">
<div id="chat_messages_container" onScroll={()=>{this.handleScroll()}}>
{chatPoruke}
</div>
<div id="chat_bottom">
{scrollBackToBottom}
</div>
</div>
)
}
}
export default Chat;
How it all works :
Inside return, you can see that I have a chat_messages_container div, which will contain all the messages one by one. I have added onScroll event to (whenever a user scrolls inside that div), it would trigger my handleScroll() function.
handleScroll() function decides if user has scrolled enough for my component to update its state.scroll to false, once it changes it to false, my button shows up.

How to force scroll in overflow-y div

I'm trying to force an offset scroll in a div that contain a ngfor list
I tried this on the div that as the overflow-y:
#ViewChild('list') listRef: ElementRef;
then on click I try this with some log to be sure it's called
this.listRef.nativeElement.offsetTop = 500;
Nothing happens, any idea how I can achieve this ?
EDIT :
Html :
<div
#gridContainerId
class="gridContainer">
<a *ngFor="let item of images;">
<img src="{{getImage(item)}}"/>
</a>
</div>
css :
.gridContainer {
height: 90vh;
overflow-y: scroll;
}
component :
#ViewChild('gridContainerId') ref: ElementRef;
this.store.pipe(
select(fromImages.getImages),
takeWhile(() => this.componentActive)
).subscribe(imagesItems => {
this.images = imagesItems;
updateScroll();
}
);
updateScroll(){
this.ref.nativeElement.scrollTop = this.ref.nativeElement.scrollHeight;
}
I've been recently in your same scenario, when click certain button, the div scrolled to bottom, i achieved it with this code:
this.messagesContainer.nativeElement.scrollTop = this.messagesContainer.nativeElement.scrollHeight;
the property scrollTop is to specify the number of pixels to be scrolled from the top of the view.
and the property scrollHeight is to get the total height of the div.

Javascript: prevent internal element "scrolling" in an element

I have a script that has a div with a width larger than its' parent, with the parent being set to overflow: hidden;. I have javascript that is setting the left positioning of the big div to create "pages". You can click a link to move between pages.
All of that works great, but the problem is if you tab from one "page" element to another, it completely messes up all the left positioning to move between the pages.
You can recreate this bug in the fiddle I set up by setting your focus to one of the input boxes on page ONE and tabbing until it takes you to page two.
I've set up a demo here.
The code that is important is as follows:
HTML:
<div class="form">
<div class="pagesContainer">
<div class="page" class="active">
<h2>Page One</h2>
[... Page 1 Content here...]
</div>
<div class="page">
<h2>Page Two</h2>
[... Page Content here...]
</div>
</div>
CSS:
.form {
width: 400px;
position: relative;
overflow: hidden;
border: 1px solid #000;
float: left;
}
.pagesContainer {
position: relative; /*Width set to 10,000 px in js
}
.form .page {
width: 400px;
float: left;
}
JS:
slidePage: function(page, direction, currentPage) {
if (direction == 'next') {
var animationDirection = '-=';
if (page.index() >= this.numPages) {
return false;
}
}
else if (direction == 'previous') {
var animationDirection = '+=';
if (page.index() < 0) {
return false;
}
}
//Get page height
var height = page.height();
this.heightElement.animate({
height: height
}, 600);
//Clear active page
this.page.removeClass('active');
this.page.eq(page.index()).addClass('active');
//Locate the exact page to skip to
var slideWidth = page.outerWidth(true) * this.difference(this.currentPage.index(), page.index());
this.container.animate({
left: animationDirection + slideWidth
}, 600);
this.currentPage = page;
}
The primary problem is that whatever happens when you tab from say, an input box on page one to something on page 2, it takes you there, but css still considers you to be at left: 0px;. I've been looking all over for a solution but so far all google has revealed to me is how to stop scrollbar scrolling.
Any help or suggestions would be appreciated, thanks!
P.S. The html was set up like this so that if javascript is disabled it will still show up all on one page and still function properly.
I updated your fiddle with a fix for the first tab with the form: http://jsfiddle.net/E7u9X/1/
. Basically, what you can do is to focus on the first "tabbable" element in a tab after the last one gets blurred, like so:
$('.form input').last().blur(function(){
$('.form input').first().focus();
});
(This is just an example, the first active element could be any other element)
Elements with overflow: hidden still have scrolling, just no scroll bars. This can be useful at times and annoying at others. This is why your position left is at zero, but your view of the element has changed. Set scrollLeft to zero when you change "pages", should do the trick.

Categories