I've encountered an issue while trying to achieve a parallax style effect for one of my components. Currently, I am transforming the inline style by setting a scroll listener once the component mounts and changing the component state upon scroll. However, the inline style does not seem to change during re-rendering even though I have checked the output of the state in the render function and seems to have correct outputs. I tried checking previous answers, but I haven't been able to correct this issue.
Currently, my setup is as follows:
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
style: {
transform: 'translateY(0)'
}
};
this.parallax = this.parallax.bind(this);
}
componentDidMount() {
window.addEventListener('scroll', this.parallax);
}
componentWillUnMount() {
window.removeEventListener('scroll', this.parallax);
}
parallax() {
function onScroll() {
let scrolled = window.pageYOffset;
this.setState({
style: {
transform: `translateY(${scrolled})`
}
});
}
if(window.pageYOffset < window.innerHeight) {
window.requestAnimationFrame(onScroll.bind(this));
}
}
render() {
return (
<div className="home-wrapper" style={this.state.style}>
</div>
);
}
}
If anyone has any suggestions I will appreciate it. Thanks!
Initially it works fine because you have translateY(0) and in css 0 is fine without 'px' but once you update you are trying to do translateY(35) but css expects a px value so just change to translateY(${scrolled}px)
Related
In a functional react component, I'm trying to check whether a call to action button (a different component) is within the viewport. If it's not, I want to display a fixed call to action button at the bottom of the viewport, which shows/hides, depending on whether the other button is visible.
I can do this using a combination of Javascript and react hooks, but although the code works in some components in my app, it doesn't work in others; I'm guessing due to react lifecycles.
I'm also aware that this is NOT the way I should be doing things in react, so would prefer to achieve the same result, but in a proper 'react way'.
I've been looking at using refs, but ideally wanted to avoid having to change my functional component to a class, as I'd like to use react hooks for the show/hide of the fixed cta. However, if this is a requirement in order to get the functionality I want, I could go for that.
Here's what I've got so far - basically, I want to replace document.querySelector with a react method:
useEffect(() => {
const CTA = document.querySelector('#CTANextSteps');
const ApplyStyle = () => (isInViewport(CTA) ? setVisible(false) : setVisible(true));
ApplyStyle();
window.addEventListener('scroll', ApplyStyle);
window.addEventListener('resize', ApplyStyle);
return function cleanup() {
window.removeEventListener('scroll', ApplyStyle);
window.removeEventListener('resize', ApplyStyle);
};
});
const isInViewport = (elem) => {
const bounding = elem.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
As mentioned above, this function works in some areas of the app without issue, but doesn't in others; I get a Cannot read property 'getBoundingClientRect' of null error. I was surprised it worked at all, but rather than tinkering with it to try and get it working everywhere, I want to rewrite it properly.
As always, any assistance would be much appreciated. Thanks.
I was able to do it with the depedency react-visibility-sensor#5.1.1
I followed the tutorial in this link and it worked fine with me.
I don't know if this is the correct way to do it, but it works!
Here is the link https://www.digitalocean.com/community/tutorials/react-components-viewport-react-visibility-sensor
I'll put an example just in case the previous link ever goes out.
import React, { Component } from 'react';
import VisibilitySensor from 'react-visibility-sensor';
class VisibilitySensorImage extends Component {
state = {
visibility: false
}
render() {
return (
<VisibilitySensor
onChange={(isVisible) => {
this.setState({visibility: isVisible})
}}
>
<img
alt={this.props.alt}
src={this.props.src}
style={{
display: 'block',
maxWidth: '100%',
width: '100%',
height: 'auto',
opacity: this.state.visibility ? 1 : 0.25,
transition: 'opacity 500ms linear'
}}
/>
</VisibilitySensor>
);
}
}
export default VisibilitySensorImage;
Context
Newer to React; I have a parent component that is generating anywhere from 1-6 youtube videos, using react-youtube components. The Idea is that the YouTube components will be generated dynamically based on youtube video ids in an array. Currently, I'm generating the components like
{this.state.videoIds.map((videoId, index) =>
<YouTube
key={index}
opts={opts}
videoId={videoId}
onReady={this._onReady} />
)}
As far as generating the components, it's working as expected. However, the issue is, when this.state.videoIds gets updated, it causes all of the YouTube components to be refreshed. That behavior makes sense to me.
Issue
The issue I have is the following: If the functionality stays the same, I need a way to be able to track each of the YouTube components player time, so that if a refresh happens, I can pass that data back down to the components when they are refreshed so they are able to be played from where they left off.
So my question is, is there a way to dynamically generate these components without needing to refresh all of the existing components?
For what it's worth, I have thought about just having a parent component that will statically add the components for each use case, but that seems awfully clunky, and I don't really want to go that route.
Edit
This feels relevant, I'm divvying up the screen space evenly based on how many players are loaded at any given time, code looks like the following:
calculateVideoWidth = () => {
if (this.state.videoIds.length <= 3) {
return (document.body.clientWidth - 15) / this.state.videoIds.length;
} else {
return (document.body.clientWidth - 15) / this.MAX_NUMBER_OF_COLUMNS;
}
};
calculateVideoHeight = () => {
if (this.state.videoIds.length <= 3) {
return window.innerHeight - 4;
} else {
return (window.innerHeight - 4) / this.MAX_NUMBER_OF_ROWS;
}
};
In my render method, I'm doing the following:
let opts = {
height: this.calculateVideoHeight(),
width: this.calculateVideoWidth()
};
After looking at #Tholle's comment, it occurs to me that the 'reload' is coming from the resizing. Which I guess makes sense. There has to be a better way to do this?
Edit 1
Check out this link to see what I am experiencing: Video App. In the input box, just add a video id separated by a ','.
This code might help you to start video from wherever user left.
The thing is we need to maintain or store the last played state of video wherever user left, some where permanently not erasable on reload, so here implemented using localStorage.
import React from "react";
import ReactDOM from "react-dom";
import YouTube from "react-`enter code here`youtube";
class App extends React.Component {
state = {
videoIds: ["2g811Eo7K8U"],
videosPlayState: {}
};
componentDidMount() {
let videosPlayState = localStorage.getItem("videosPlayState") || "{}";
this.setState({
videosPlayState: JSON.parse(videosPlayState)
});
}
addVideo = () => {
this.setState(prevState => ({
videoIds: [...prevState.videoIds, "9kBACkguBR0"]
}));
};
_onStateChange = (event, videoId) => {
let newPlayState = { ...this.state.videosPlayState
};
newPlayState[videoId] = event.target.getCurrentTime();
localStorage.setItem("videosPlayState", JSON.stringify(newPlayState));
};
_onReady(event, videoId) {
event.target.seekTo(this.state.videosPlayState[videoId] || 0);
}
render() {
const opts = {
height: "200",
width: "400",
playerVars: {
autoplay: 1
}
};
return ( <
div className = "App" >
<
div className = "video-container" > {
this.state.videoIds.map((videoId, index) => ( <
YouTube onStateChange = {
event => {
this._onStateChange(event, videoId);
}
}
onReady = {
event => {
this._onReady(event, videoId);
}
}
key = {
index
}
videoId = {
videoId
}
opts = {
opts
}
className = {
"item"
}
/>
))
} <
/div> <
div >
<
button onClick = {
this.addVideo
} > Add video < /button> <
/div> <
/div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render( < App / > , rootElement);
here is the css:
.video-container .item {
padding: 25px;
display: inline-block;
}
.video-container {
display: flex;
flex-wrap: wrap;
}
here is a link might help you enter link description here
After playing with flexbox a bit more, I was able to solve this issue. I ended up with the following:
html, body, #root, #root > div, div.container, iframe {
width: 100%;
height: 100%;
margin: 0;
}
div.container {
display: flex;
flex-wrap: wrap;
}
span {
flex-basis: 33%;
flex-grow: 1;
}
For reference, here is my jsx
<div>
<div className="container">
{this.state.videoIds.map((videoId, index) =>
<YouTube
id="flex-item"
key={index}
videoId={videoId}
onStateChange={this._onStateChange}
onReady={this._onReady}/>
)}
</div>
</div>
I figured this could be done with flexbox, but I hadn't come up with the solution for it yet. I hope this helps someone else out.
I have a div that grows in height as an animation. Instead of growing outside of the viewable area (and user having to scroll down), I'd like the window to automatically scroll with the div height. Locking the scroll position at the bottom of the page would work.
!!This is in React!!
I've tried millions of google/SO answers, none work/ arent specific enough.
code https://github.com/coryb08/corydbaker npm install && npm start
You provided very little information, but since we know it's in React, you could use JavaScript to make sure your div is scrolled all the way to the bottom at all all times.
class FullMenu extends Component {
constructor() {
super()
this.state = {
class: "",
div: ""
}
this.scrollToBottom = this.scrollToBottom.bind(this);
}
componentDidMount() {
setInterval(this.scrollToBottom, 20);
}
scrollToBottom() {
var scrollingElement = (document.scrollingElement || document.body); /* you could provide your scrolling element with react ref */
scrollingElement.scrollTop = scrollingElement.scrollHeight;
}
render() {
return (
<div id="FullMenu">
{this.state.div}
<div id="triangleDiv">
<img
className={this.state.class}
onClick={() => {
this.setState({ class: "bounce" })
let that = this
setTimeout(function() {
that.setState({
class: "",
div: <div className="menuDiv" />
})
}, 1000)
}}
src={triangle}
id="triangle"
/>
</div>
</div>
)
}
}
Note that above solution keeps the window scrolled at all times. If you wanted to scroll it only during animation, then you should use react's CSSTransitionGroup and use clearInterval to stop this behavior in transitionEnd lifecycle hook.
You can use CSS alone
all you have to do is set the div styling
display: flex;
flex-direction: column-reverse
this should flip the div scroll and position it at the bottom
I've got the following React app where I'm using react-spring to animate between routes and animate different elements based on the current scroll position.
When I use overflow: scroll on the Home component I'm then unable to return anything from my handleScroll method (it just returns 0):
handleScroll(e) {
let windowScrollPosition = window.scrollY
this.setState({ windowScrollPosition: windowScrollPosition }, () => console.log(this.state.windowScrollPosition, 'this.state.windowScrollPosition'))
}
Is there a way around this?
I need to use overflow: scroll to solve this issue unfortunately.
Any help is much appreciated!
Well that would make sense. If you have set an element to scroll that is 100% height then the window would never scroll the element would.
So you need to get the scroll position from the element with elem.scrollTop
You can create a ref to the element in React
constructor(props) {
super(props);
this.scrollContainer = React.createRef();
}
return (
<div className="scroll-container" ref={this.scrollContainer}></div>
)
And then in your handle scroll method use:
handleScroll(e) {
let windowScrollPosition = this.scrollContainer.current.scrollTop | window.scrollY;
this.setState({ windowScrollPosition: windowScrollPosition }, () => console.log(this.state.windowScrollPosition, 'this.state.windowScrollPosition'))
}
How to I scale a react component already created into the browser that already has a height and width predefined???
componentDidMount(x,y,z){
this.setState({height:window.innerHeight+'px'});
}
Not sure if this is the right way to go, I am not sure if I have to get th viewport size first and then later try to scale the on the browser later. How to can be accomplished?
You can assign the styles directly to the DOM element. For example:
render() {
return <div style={{ height: window.innerHeight }} />;
}
If you need to calculate the style after initial render, perhaps after some user interaction, you could calculate and store the styles in state:
// ... calculate and set state logic somewhere
// eg: someFunction() { this.setState({ height: window.innerHeight }); }
render() {
let styles = {};
if (this.state.height) {
styles.height = this.state.height;
}
return <div style={styles} />;
}