I am very new using React and I have the following problem; I have defined a function out of the return (), and I am calling it afterwards so it shows only when the page is loaded.
class Landing extends Component {
render() {
scrollbar = () => {
const progress = document.getElementById("progressBar");
const totalHeight = document.body.scrollHeight - window.innerHeight;
window.onscroll = function () {
const progressHeight = (window.pageYOffset / totalHeight) * 100;
progress.style.height = progressHeight + "%";
};
};
return (
<div>
<div id="progressBar"> {this.scrollbar()}</div>
<div id="scrollPath"></div>
</div>
)
}
}
The error that I get is:
"scrollbar is undefined" // Search for the keywords to learn more about each error.
Any help is much appreciated.
Thank you!
You must define the method (scrollbar) above the render method.
class Landing extends Component {
scrollbar = () => {
const progress = document.getElementById("progressBar");
const totalHeight = document.body.scrollHeight - window.innerHeight;
window.onscroll = function () {
const progressHeight = (window.pageYOffset / totalHeight) * 100;
progress.style.height = progressHeight + "%";
};
};
render() {
return (
<>
<div id="progressBar">{this.scrollbar}</div>
<div id="scrollPath"></div>
</>
)
}
}
I hope this solve your issue. :)
First, you shouldn't be doing DOM manipulation like that when using react, so ideally you would rewrite the scrollbar function to be more inline with react.
The error you are getting is because this is referring to the class, meaning this.scrollbar is looking for a class property called scrollbar. You do not have a class property called scrollbar, but a function declared within the render method.
You have two options to solve this error.
Reference the function instead of a class property:
<div id="progressBar">{scrollbar()}</div>
Change the function to be a class property:
class Landing extends Component {
scrollbar = () => {
const progress = document.getElementById("progressBar");
const totalHeight = document.body.scrollHeight - window.innerHeight;
window.onscroll = function () {
const progressHeight = (window.pageYOffset / totalHeight) * 100;
progress.style.height = progressHeight + "%";
};
};
Once you do one of these, you still might see some unexpected behavior. By doing:
<div>{this.scrollbar()}</div>
You are rendering the result of the function call. The function has no return value though, so it will render undefined. Since it really doesn't need to be rendered, you can just call in inside the render function directly:
render() {
this.scrollbar();
return (
<div>
<div id="progressBar"></div>
<div id="scrollPath"></div>
</div>
)
}
1) Move your scrollbar function outside render.
2) remove () these braces while calling scrollbar inside render function.
scrollbar = () => {
const progress = document.getElementById("progressBar");
const totalHeight = document.body.scrollHeight - window.innerHeight;
window.onscroll = function () {
const progressHeight = (window.pageYOffset / totalHeight) * 100;
progress.style.height = progressHeight + "%";
};
};
render() {
return (
<div>
<div id="progressBar"> {this.scrollbar}</div>
<div id="scrollPath"></div>
Related
I am trying to track when a header element contains a specific class or not. The header gets hidden based on scroll down and appears based on scroll up. Several classes get applied to the header during this time period of scrolling up or down. I need the sibling (#main) of header to get a class (scroll-down) added only when the specific class (is-fixed) is applied on the header. Below is the script...
const header = document.querySelector("header");
let lastScroll = 0;
const throttle = (func, time = 100) => {
let lastTime = 0;
return () => {
const now = new Date();
if (now - lastTime >= time) {
func();
time = now;
}
};
};
const validateHeader = () => {
const windowY = window.scrollY;
const windowH = window.innerHeight;
if (windowY > windowH) {
// We passed the first section, set a toggable class
header.classList.add("is-fixed");
// Determine is we ready to animate
if (windowY > windowH + 40) {
header.classList.add("can-animate");
if (windowY < lastScroll) {
// Determine if we scrolling up
header.classList.add("scroll-up");
} else {
header.classList.remove("scroll-up");
}
} else {
header.classList.remove("scroll-up");
}
} else {
header.classList.remove("is-fixed", "can-animate");
}
lastScroll = windowY;
};
window.addEventListener("scroll", throttle(validateHeader, 100));
<body>
<header id="header" class="fixed-top">stuff</header>
<section>stuff</section>
<section>stuff</section>
<main id="main" class="components">stuff</main>
</body>
--UPDATE--
I edited a couple lines to add or remove a class on #main only when #header gets a specific class... (the purpose is adjusting the position of a side menu (in main) when the header gets hidden/shown).
const header = document.querySelector("header");
let lastScroll = 0;
const throttle = (func, time = 100) => {
let lastTime = 0;
return () => {
const now = new Date();
if (now - lastTime >= time) {
func();
time = now;
}
};
};
const validateHeader = () => {
const windowY = window.scrollY;
const windowH = window.innerHeight;
if (windowY > windowH) {
// Set class on scrolling down
header.classList.add("is-fixed");
// Determine ready to animate
if (windowY > windowH + 0) {
header.classList.add("can-animate");
if (windowY < lastScroll) {
// Determine scrolling up
header.classList.add("scroll-up");
main.classList.remove("move-sidenav"); // for side nav
} else {
header.classList.remove("scroll-up");
main.classList.add("move-sidenav"); // for side nav
}
} else {
header.classList.remove("scroll-up");
main.classList.add("move-sidenav"); // for side nav
}
} else {
header.classList.remove("is-fixed", "can-animate");
main.classList.remove("move-sidenav"); // for side nav
}
lastScroll = windowY;
};
window.addEventListener("scroll", throttle(validateHeader, 100));
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();
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
I want to return the progression in percentage when the scroll event is trigger,
I dont nkow how to update the state to get the right result, and display it in the div tag
thanks in advance.
This is my script:
import React from "react";
class ProgressBar extends React.Component {
constructor(props) {
super(props);
this.state = {
percentage: 0,
};
}
getProgressPercentage() {
let maxHeight = document.body.scrollHeight - window.innerHeight;
let percent = (window.pageYOffset / maxHeight) * 100;
let progress = Math.round(percent) + "%";
// console.log(progress);
return progress;
}
componentDidMount() {
document.addEventListener("scroll", this.getProgressPercentage);
}
componentWillUnmount() {
document.removeEventListener("scroll", this.getProgressPercentage);
}
render() {
return <div> {this.state.percentage} </div>;
}
}
export default ProgressBar;
Use this.setState() (docs, resource)
getProgressPercentage() {
let maxHeight = document.body.scrollHeight - window.innerHeight;
let percent = (window.pageYOffset / maxHeight) * 100;
let progress = Math.round(percent) + "%";
// console.log(progress);
this.setState({ percentage: progress })
return progress;
}
componentDidMount() {
document.addEventListener("scroll", () => this.getProgressPercentage());
}
componentWillUnmount() {
document.removeEventListener("scroll", () => this.getProgressPercentage());
}
Why your code cannot work as you intend:
When you write this:
document.addEventListener("scroll", this.getProgressPercentage)
this.getProgressPercentage() will NOT be executed as a method of your component, rather as a function. Meaning this will not be what you think : it will NOT be your component. Therefore there will be no setState on this. Hence the error you get: this.setState is not a function.
More info here and here about this in JavaScript, and here about arrow functions
You should use setState:
getProgressPercentage() {
let maxHeight = document.body.scrollHeight - window.innerHeight;
let percentage = (window.pageYOffset / maxHeight) * 100;
this.setState({ percentage });
}
which will trigger a rerender with the correct value of percentage.
I removed the progress variable because in my opinion it should be in the render.
Use setState, reference : Description
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
getProgressPercentage() {
let maxHeight = document.body.scrollHeight - window.innerHeight;
let percent = (window.pageYOffset / maxHeight) * 100;
let progress = Math.round(percent) + "%";
this.setState({ percentage })
}
Simple use setState method on instance like below.
getProgressPercentage() {
//your code
this.setState({ percentage });
}
Thanks for answers, this is the solution i found ,anyway i dont understand why "setState" not working without "requestAnimationFrame' in this conxtext.
getProgressPercentage() {
let maxHeight = document.body.scrollHeight - window.innerHeight;
let percent = (window.pageYOffset / maxHeight) * 100;
let progress = Math.round(percent);
this.setState({ percentage: progress });
}
lisenOnScrollEvent() {
document.addEventListener("scroll", () => {
requestAnimationFrame(() => {
this.getProgressPercentage();
});
});
}
componentDidMount() {
this.lisenOnScrollEvent();
}
I want to execute an exeternal function inside the JQuery method. The problem appear when I try to call the method, the one looks undefined. How could I solve this? I amb using Typescript with Angular 2
ngAfterViewInit() {
jQuery(".mo-table").scroll(function () {
var trueDiveHeight = jQuery(".mo-table")[0].scrollHeight;
var divHeight = jQuery(".mo-table").height();
var scrollLeft = trueDiveHeight - divHeight;
if (jQuery(".mo-table").scrollTop() >= scrollLeft - 150) {
this.onSearch();
console.log("new bottom")
}
});
}
The method onSearch is an external function, and is Undefined.
onSearch(): void {
this.updateTableReport(this.scrollId, this.buildParams())
}
Any help would be appreciated.
Change
jQuery(".mo-table").scroll(function () {
to
jQuery(".mo-table").scroll( ()=> {
your this is not refering to your component
or the old js way:
ngAfterViewInit() {
var self = this; //<-- assign this to self here
jQuery(".mo-table").scroll(function () {
var trueDiveHeight = jQuery(".mo-table")[0].scrollHeight;
var divHeight = jQuery(".mo-table").height();
var scrollLeft = trueDiveHeight - divHeight;
if (jQuery(".mo-table").scrollTop() >= scrollLeft - 150) {
self.onSearch(); //<-- use self here
console.log("new bottom")
}
});
}