As a newer to React but someone familiar with JavaScript, I am having some trouble knowing where exactly to put certain pieces of functionality in a given React ponent, and am not sure if this particular issue I am having is an issue with my React logic or just a lag-time when loading an image, although if it is the latter I do not understand this because I preload the image in a hidden div.
My expected workflow is the following:
- Load two images, one "preloaded" with
height:0
as part of ahide
class. - When the window scrolls, effectively toggle the
hide
class on either of these two images, so as to effectively change which image is displayed based on the scroll position. The background color of the header will change as well when this happens.
In my ponent Header.jsx
I have the following:
ponentDidMount() {
if (this.props.changeColorOnScroll) {
window.addEventListener("scroll", this.headerColorChange);
}
}
headerColorChange() {
console.log("Scroll Fire!");
const { classes, color, changeColorOnScroll} = this.props;
const windowsScrollTop = window.pageYOffset;
if (windowsScrollTop > changeColorOnScroll.height) {
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[changeColorOnScroll.color]);
document
.getElementById("headerLogoContainer").getElementsByClassName("logo")[0]
.classList.add('hide');
document
.getElementById("headerLogoContainer").getElementsByClassName("logoalt")[0]
.classList.remove('hide');
} else {
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[changeColorOnScroll.color]);
document
.getElementById("headerLogoContainer").getElementsByClassName("logo")[0]
.classList.remove('hide');
document
.getElementById("headerLogoContainer").getElementsByClassName("logoalt")[0]
.classList.add('hide');
}
}
I believe this is the right place to attach an event listener in ponentDidMount()
. My issue is as I scroll, the window freezes up as the image changes, even though I preload the image here:
render() {
const {
classes,
color,
rightLinks,
leftLinks,
brand,
logo,
logoalt,
fixed,
absolute
} = this.props;
const appBarClasses = classNames({
[classes.appBar]: true,
[classes[color]]: color,
[classes.absolute]: absolute,
[classes.fixed]: fixed
});
let brandComponent;
if(!logo){
brandComponent = (
<Button className={classes.title}>
{brand}
</Button>
);
}else{
brandComponent = (
<div id='headerLogoContainer'>
<img className='logo' src={logo}></img>
<img className='logoalt hide' src={logoalt}></img>
</div>
);
}
return (
<AppBar className={appBarClasses}>
<Toolbar className={classes.container}>
{leftLinks !== undefined ? brandComponent : null}
<div className={classes.flex}>
{leftLinks !== undefined ? (
<Hidden smDown implementation="css">
{leftLinks}
</Hidden>
) : (
brandComponent
)}
</div>
As you can see, in the ponentDidMount()
I setup the event listener and then in the render I setup the <div id='headerLogoContainer'>
as part of my brandComponent
. I see the brandComponent
successfully render to the DOM, but on the first scroll (down) it locks up the screen (in Chrome) or doesn't show the logoalt
right away (in Edge and Firefox). After a few seconds the images switch and then I can scroll up or down, toggling it with no problem. This leads me to think it might be a preloading issue but why would that be when I have preloaded the image in the brandComponent
and see it in the DOM. No additional network requests are being made, just toggling the hide
image to show.
So my question is, how can I change an image source in React, based on a scroll event, without a lag or freezing the scroll position?
EDIT:
I've changed my code to leverage internal state according to a suggested answer, and it still has the same effect of a lag when the image changes:
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
mobileOpen: false,
logoaltState: false
};
this.handleDrawerToggle = this.handleDrawerToggle.bind(this);
this.headerColorChange = this.headerColorChange.bind(this);
}
handleDrawerToggle() {
this.setState({ mobileOpen: !this.state.mobileOpen });
}
ponentDidMount() {
if (this.props.changeColorOnScroll) {
window.addEventListener("scroll", this.headerColorChange);
}
}
headerColorChange() {
console.log("Scroll Fire!");
const { classes, color, changeColorOnScroll} = this.props;
const windowsScrollTop = window.pageYOffset;
if (windowsScrollTop > changeColorOnScroll.height) {
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[changeColorOnScroll.color]);
this.setState({ logoaltState: true });
} else {
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[changeColorOnScroll.color]);
this.setState({ logoaltState: false });
}
}
and then in my render...
render() {
const {
classes,
color,
rightLinks,
leftLinks,
brand,
logo,
logoalt,
fixed,
absolute
} = this.props;
const{
mobileOpen,
logoaltState
} = this.state
const appBarClasses = classNames({
[classes.appBar]: true,
[classes[color]]: color,
[classes.absolute]: absolute,
[classes.fixed]: fixed
});
let brandComponent;
if(!logo){
brandComponent = (
<Button className={classes.title}>
{brand}
</Button>
);
}else{
brandComponent = (
<div id='headerLogoContainer'>
<img className='logo' src={logo} style={{display: logoaltState === false ? "block" : "none"}}></img>
<img className='logoalt' src={logoalt} style={{display: logoaltState === true ? "block" : "none"}}></img>
</div>
);
}
Different code/method, same issue...
EDIT 2:
I don't think it's this application logic that's the issue, because as I mention, it only freezes up the first time the image/state needs to change. I think it's some kind of first-load issue, even though I do not have any network requests, maybe I am including the image source incorrectly? Or in React images need to preloaded by some other method, because the image source is slighty off when it's included... i.e. /static/media/logo.ad924167.png
as oppose to require("assets/img/tovlogo.png"
I think this is because of my node server settings but again, am not really sure what is going on as I am fairly new to this ecosystem... I'm using the Material UI React Template, that is where this ponent is derived from.
The theme file itself was this: so I am wondering how it handles the images, given the nature of the actual src
as opposed to the require
... but then again there are no additional requests for the image... it's just a CSS/style change...
EDIT 3:
This is what my debugger (Chrome:Performance) looks like as it is freezing up:
I realize looking at the paint time that the image is quite large, but shouldn't preloading address that... I suppose not because that is a network preload not a paint preload with display:none;"
... maybe I can try opacity
CSS property instead but this plicates other CSS... is there a better way to do this generally?
FINAL EDIT:
My image is over 10,000 pixels wide... I was using it as a logo so didn't realize until I did the performance metrics how bad it was... I'm guessing this is the reason it froze up... sorry React. :(
As a newer to React but someone familiar with JavaScript, I am having some trouble knowing where exactly to put certain pieces of functionality in a given React ponent, and am not sure if this particular issue I am having is an issue with my React logic or just a lag-time when loading an image, although if it is the latter I do not understand this because I preload the image in a hidden div.
My expected workflow is the following:
- Load two images, one "preloaded" with
height:0
as part of ahide
class. - When the window scrolls, effectively toggle the
hide
class on either of these two images, so as to effectively change which image is displayed based on the scroll position. The background color of the header will change as well when this happens.
In my ponent Header.jsx
I have the following:
ponentDidMount() {
if (this.props.changeColorOnScroll) {
window.addEventListener("scroll", this.headerColorChange);
}
}
headerColorChange() {
console.log("Scroll Fire!");
const { classes, color, changeColorOnScroll} = this.props;
const windowsScrollTop = window.pageYOffset;
if (windowsScrollTop > changeColorOnScroll.height) {
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[changeColorOnScroll.color]);
document
.getElementById("headerLogoContainer").getElementsByClassName("logo")[0]
.classList.add('hide');
document
.getElementById("headerLogoContainer").getElementsByClassName("logoalt")[0]
.classList.remove('hide');
} else {
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[changeColorOnScroll.color]);
document
.getElementById("headerLogoContainer").getElementsByClassName("logo")[0]
.classList.remove('hide');
document
.getElementById("headerLogoContainer").getElementsByClassName("logoalt")[0]
.classList.add('hide');
}
}
I believe this is the right place to attach an event listener in ponentDidMount()
. My issue is as I scroll, the window freezes up as the image changes, even though I preload the image here:
render() {
const {
classes,
color,
rightLinks,
leftLinks,
brand,
logo,
logoalt,
fixed,
absolute
} = this.props;
const appBarClasses = classNames({
[classes.appBar]: true,
[classes[color]]: color,
[classes.absolute]: absolute,
[classes.fixed]: fixed
});
let brandComponent;
if(!logo){
brandComponent = (
<Button className={classes.title}>
{brand}
</Button>
);
}else{
brandComponent = (
<div id='headerLogoContainer'>
<img className='logo' src={logo}></img>
<img className='logoalt hide' src={logoalt}></img>
</div>
);
}
return (
<AppBar className={appBarClasses}>
<Toolbar className={classes.container}>
{leftLinks !== undefined ? brandComponent : null}
<div className={classes.flex}>
{leftLinks !== undefined ? (
<Hidden smDown implementation="css">
{leftLinks}
</Hidden>
) : (
brandComponent
)}
</div>
As you can see, in the ponentDidMount()
I setup the event listener and then in the render I setup the <div id='headerLogoContainer'>
as part of my brandComponent
. I see the brandComponent
successfully render to the DOM, but on the first scroll (down) it locks up the screen (in Chrome) or doesn't show the logoalt
right away (in Edge and Firefox). After a few seconds the images switch and then I can scroll up or down, toggling it with no problem. This leads me to think it might be a preloading issue but why would that be when I have preloaded the image in the brandComponent
and see it in the DOM. No additional network requests are being made, just toggling the hide
image to show.
So my question is, how can I change an image source in React, based on a scroll event, without a lag or freezing the scroll position?
EDIT:
I've changed my code to leverage internal state according to a suggested answer, and it still has the same effect of a lag when the image changes:
class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
mobileOpen: false,
logoaltState: false
};
this.handleDrawerToggle = this.handleDrawerToggle.bind(this);
this.headerColorChange = this.headerColorChange.bind(this);
}
handleDrawerToggle() {
this.setState({ mobileOpen: !this.state.mobileOpen });
}
ponentDidMount() {
if (this.props.changeColorOnScroll) {
window.addEventListener("scroll", this.headerColorChange);
}
}
headerColorChange() {
console.log("Scroll Fire!");
const { classes, color, changeColorOnScroll} = this.props;
const windowsScrollTop = window.pageYOffset;
if (windowsScrollTop > changeColorOnScroll.height) {
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[changeColorOnScroll.color]);
this.setState({ logoaltState: true });
} else {
document.body
.getElementsByTagName("header")[0]
.classList.add(classes[color]);
document.body
.getElementsByTagName("header")[0]
.classList.remove(classes[changeColorOnScroll.color]);
this.setState({ logoaltState: false });
}
}
and then in my render...
render() {
const {
classes,
color,
rightLinks,
leftLinks,
brand,
logo,
logoalt,
fixed,
absolute
} = this.props;
const{
mobileOpen,
logoaltState
} = this.state
const appBarClasses = classNames({
[classes.appBar]: true,
[classes[color]]: color,
[classes.absolute]: absolute,
[classes.fixed]: fixed
});
let brandComponent;
if(!logo){
brandComponent = (
<Button className={classes.title}>
{brand}
</Button>
);
}else{
brandComponent = (
<div id='headerLogoContainer'>
<img className='logo' src={logo} style={{display: logoaltState === false ? "block" : "none"}}></img>
<img className='logoalt' src={logoalt} style={{display: logoaltState === true ? "block" : "none"}}></img>
</div>
);
}
Different code/method, same issue...
EDIT 2:
I don't think it's this application logic that's the issue, because as I mention, it only freezes up the first time the image/state needs to change. I think it's some kind of first-load issue, even though I do not have any network requests, maybe I am including the image source incorrectly? Or in React images need to preloaded by some other method, because the image source is slighty off when it's included... i.e. /static/media/logo.ad924167.png
as oppose to require("assets/img/tovlogo.png"
I think this is because of my node server settings but again, am not really sure what is going on as I am fairly new to this ecosystem... I'm using the Material UI React Template, that is where this ponent is derived from.
The theme file itself was this: https://www.creative-tim./product/material-kit-react?partner=104080 so I am wondering how it handles the images, given the nature of the actual src
as opposed to the require
... but then again there are no additional requests for the image... it's just a CSS/style change...
EDIT 3:
This is what my debugger (Chrome:Performance) looks like as it is freezing up:
I realize looking at the paint time that the image is quite large, but shouldn't preloading address that... I suppose not because that is a network preload not a paint preload with display:none;"
... maybe I can try opacity
CSS property instead but this plicates other CSS... is there a better way to do this generally?
FINAL EDIT:
My image is over 10,000 pixels wide... I was using it as a logo so didn't realize until I did the performance metrics how bad it was... I'm guessing this is the reason it froze up... sorry React. :(
Share edited Jul 20, 2018 at 15:40 Summer Developer asked Jul 8, 2018 at 22:44 Summer DeveloperSummer Developer 2,0968 gold badges35 silver badges68 bronze badges1 Answer
Reset to default 6Instead of manually changing classes on DOM elements, I think it will be easier for you if you instead leverage the ponent state and switch between the active image depending on the scroll position.
Example
class App extends React.Component {
state = { active: 0 };
ponentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
ponentWillUnmount() {
window.removeEventListener("scroll", this.handleScroll);
}
handleScroll = event => {
const { pageYOffset } = window;
const { active } = this.state;
if (pageYOffset >= 500 && active === 0) {
this.setState({ active: 1 });
} else if (pageYOffset < 500 && active === 1) {
this.setState({ active: 0 });
}
};
render() {
const { active } = this.state;
return (
<div style={{ height: 1000, width: 1000 }}>
<div
style={{
height: "100%",
width: "100%",
backgroundColor: "red",
display: active === 0 ? "block" : "none"
}}
/>
<div
style={{
height: "100%",
width: "100%",
backgroundColor: "blue",
display: active === 1 ? "block" : "none"
}}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>