I have the following object list:
mediaList[
{id:1, url:"www.example.com/image1", adType:"image/jpeg"},
{id:2, url:"www.example.com/image2", adType:"image/jpg"},
{id:3, url:"www.example.com/video1", adType: "video/mp4"}
]
I need to create a slideshow that has a configurable duration (1s, 5, 10s).
So far I can generate a list of the media from the mediaList
renderSlideshow(ad){
let adType =ad.adType;
if(type.includes("image")){
return(
<div className="imagePreview">
<img src={ad.url} />
</div>
);
}else if (adType.includes("video")){
return(
<video className="videoPreview" controls>
<source src={ad.url} type={adType}/>
Your browser does not support the video tag.
</video>
)
}
}
render(){
return(
<div>
{this.props.mediaList.map(this.renderSlideshow.bind(this))}
</div>
)
}
What I want to do now is generate the media one at a time with configurable durations for autoplay.
I know I need to use some form of a setTimeout function like this example:
setTimeout(function(){
this.setState({submitted:false});
}.bind(this),5000); // wait 5 seconds, then reset to false
I'm just not sure how to implement it for this scenario. (I believe I'll need to use css for the fade transitions but I'm just stumped on how to create the transition in the first place)
If you want to change the media on every 5 seconds, you will have to update the state to re-render your components You can also use setInterval instead of setTimeout. setTimeout will be triggered only once, setInterval will be triggered every X milliseconds. Here what it may look like:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { activeMediaIndex: 0 };
}
componentDidMount() {
setInterval(this.changeActiveMedia.bind(this), 5000);
}
changeActiveMedia() {
const mediaListLength = this.props.medias.length;
let nextMediaIndex = this.state.activeMediaIndex + 1;
if(nextMediaIndex >= mediaListLength) {
nextMediaIndex = 0;
}
this.setState({ activeMediaIndex:nextMediaIndex });
}
renderSlideshow(){
const ad = this.props.medias[this.state.activeMediaIndex];
let adType = ad.adType;
if(type.includes("image")){
return(
<div className="imagePreview">
<img src={ad.url} />
</div>
);
}else if (adType.includes("video")){
return(
<video className="videoPreview" controls>
<source src={ad.url} type={adType}/>
Your browser does not support the video tag.
</video>
)
}
}
render(){
return(
<div>
{this.renderSlideshow()}
</div>
)
}
}
Basically what the code is doing is that every 5 seconds, will change the activeMediaIndex to the next one. By updating the state, you will trigger a re-render. When rendering the media, just render one media (you can also render the previous one and the next one like a classic slideshow). Thus way, every 5 seconds, you will render a new media.
Don't forget to clear the timeout to prevent memory leaks:
componentDidMount() {
this.timeout = setTimeout(() => {
...
}, 300);
}
componentWillUnmount() {
clearTimeout(this.timeout);
}
Related
I have a custom build Next JS carousel that keeps an index of the slide we are on and renders an image or video to the screen. It seems that because it is autoplaying and each slide gets removed and readded every 6 or so seconds the page downloads constant copies of the same video or image when it comes around again. Is this not the correct way of doing this?
So the important part is here
{slides.map((item, key) => (
<>
{key == this.state.currentImageIndex && (
<div>
It will remove and readd content every slide. Does it waste resrouces constantly downloading them? Is there a better way?
import React from 'react'
//Importing data to use as the slides
import { slides } from './data/slides'
import VideoSlide from './VideoSlide'
import ImageSlide from './ImageSlide'
let slideTimer = null
class Carousel extends React.Component {
constructor(props) {
super(props)
this.state = {
currentImageIndex: 0, //Array index of slide from /data/slides
slideTime: 6, //Timing duration of a slide
}
this.setMyInterval = this.setMyInterval.bind(this)
}
//Runs when this componenet is included in the page
componentDidMount() {
this.setMyInterval()
}
changeSlide(current) {
/*Don't run if we are just clicking the same slide number*/
if (current != this.state.currentImageIndex) {
this.setState({ currentImageIndex: current }) //Change to selected slide1
clearTimeout(slideTimer) //Reset the timer so it's not half way through the countdown when we select another slide
this.setMyInterval() //Rebuild the slide countdown so we are starting at 0 and in sync with our CSS animation
}
}
setMyInterval() {
slideTimer = setInterval(() => {
const lastIndex = slides.length - 1 //Check when we need to loop
const { currentImageIndex } = this.state //Get current slide from state
const shouldResetIndex = currentImageIndex === lastIndex
const index = shouldResetIndex ? 0 : currentImageIndex + 1 //Reset to 0 if we are at the end
this.setState({
currentImageIndex: index, //Set new slide as number obtained from above
})
}, this.state.slideTime * 1000) //Repeat every x seconds
}
componentWillUnmount() {
clearInterval(slideTimer)
}
render() {
return (
<>
<div className="backgroundSlide">
{slides.map((item, key) => (
<>
{key == this.state.currentImageIndex && (
<div>
<VideoSlide url={slides[0].video} />
{slides[this.state.currentImageIndex].image && (
<ImageSlide
url={slides[this.state.currentImageIndex].image}
timer={this.state.slideTime}
/>
)}
</div>
)}
</>
))}
</div>
</>
)
}
}
export default Carousel
It's not clear from the question that what kind of Carousel logic you have. Usually, we render the whole carousel elements and then let the carousel do its job. As an example consider this CSS only carousel,
https://css-tricks.com/css-only-carousel/
If you use such an approach, you can have all of your slides rendered one time and don't have to worry about the removal/addition of any slides.
I'm using an range slider <input> element inside a React component to scrub/seek through an HTML5 <audio> element.
see the shortened code of the component:
class Player extends Component {
componentDidMount() {
if(this.player) {
this.player.addEventListener("timeupdate", e => {
this.props.update_time(e.target.currentTime)
this.props.set_duration(e.target.duration)
this.timeBar.value = e.target.currentTime
});
this.timeBar.addEventListener("change", e => {
console.log(this.player)
this.player.currentTime(e.target.value)
})
}
}
render() {
return (
<div className="player">
<input
type="range"
id="seek"
defaultValue={this.props.time}
max={this.props.duration}
ref={ref => this.timeBar = ref}
/>
<audio ref={ref => this.player = ref} >
<source
src={this.props.url}
type="audio/mpeg"
/>
</audio>
</div>
)
}
}
If I move the slider (thus triggering the "change" event), I get the following error:
TypeError: this.player.currentTime is not a function
However, if I try to set the currentTime value directly in the console on the object that is returned by console.log(this.player) it works perfectly fine.
I tried pretty much everything and ran out ouf ideas. The file is fully loaded, the server supports streaming, the this.player object is returned correctly, even inside the event listener.
Thanks for your help!
i am making a custom video player in which there is an overlay containing the controls of the video player
my player starts to work in full length and height.
now i want to hide the overlay after 5 seconds i stop the mouse over.
now the problem is that when the below function mouse over in .ts file is called the synchronization of the timer is harmed.
so if i move my mouse continuously the overlay starts to flicker.
please provide me the solution to the problem.
following is my html code
<div class="video-container" #videoFullscreen>
<div class="video-wrapper" mouse-move>
<video class="video video-js" data-dashjs-player id="myVideo" autoplay #videoPlayer>
<source src="{{ videoSource }}" type="video/mp4" />
</video>
<!-- overlay -->
<div class="overlay" [class.hideOverlay]="hideTop">
<!-- top controls -->
.
.
<!-- lower controls -->
</div>
</div>
this is my type script code
#HostListener('document:mousemove', [ '$event' ]) //fuction to display and hide element sue to mouseover
onMouseMove($event) {
this.hideTop = false;
setTimeout(() => {
this.hideTop = true;
}, 5000);
}
this is my css code :
.overlay {
display: flex;
}
.hideOverlay {
display:none;
}
please help me to solve this problem.
Store the lastHover time and compare against it.
private lastHover = 0;
#HostListener(...)
onMouseMove($event) {
this.lastHover = new Date().getTime()
This.hideTop = true;
setTimeout( () => {
...
if(lastHover + 5000 < new Date().getTime()) {
This.hideTop = true;
}
}, 5000)
}
A neat solution would be to use rxjs to solve this like shown below:
ngOnInit(): void {
fromEvent<MouseEvent>(document, 'mousemove').pipe(tap(() => {
console.log("show it!");
this.hideTop = false
}), switchMap((event) =>
timer(5000).pipe(tap(() => {
console.log("hideit");
this.hideTop = true;
}))
)).subscribe();
}
Don't forget to unsubscribe if your component gets destroyed to prevent memory leaks!
First we make an Observable from the documents mousemove event.
Now if the event triggers we set hideTop to true.
And here comes the interesting part: we use switchMap with a timer Observable. switchMap automatically unsubscribes from the inner Observable if the outer one emits a new value. Therefore the inner Observable only emits after the user actually stopped moving the mouse for 5 seconds.
Apologies that my answer is in jQuery, but the concept is fairly basic
What we need to do is check if the timeout event has already been fired, and reset it on a mousemove event during that time. This is done by checking if the class for hiding the element is applied or not
//Timer variable
var timer;
//Detect mousemove event on parent element
$("html").on("mousemove", "#outer", function() {
//Is the element already hidden?
if ($("#inner").hasClass("hide")) {
//Show the element
$("#inner").removeClass("hide")
} else {
//Reset the timer to 5 seconds
clearTimeout(timer);
timer = setTimeout(hideBox, 5000);
}
})
function hideBox() {
$("#inner").addClass("hide")
}
https://jsfiddle.net/xcL52zf3/1/
You'll need to swap out the jQuery event handlers and element targetting with the equivalent for you TypeScript library
I'm trying to rewrite one of my JS plugins to react, as a way of learning.
I have a panel that when hidden/shown needs to be updated with several classnames as well as some that need to wait for a css animation to complete (why the timer).
How should I do this in a react way? Using querySelector to change classnames seem very wrong..?
Detailed explanation
When showPanel is triggered the following need to happen
the body/html element need updated css (hence me adding classes)
an existing overlay fades in (adding a class to that)
the modal div is displayed (adding a class for that)
the modal div is told to be active AFTER the animation has been run (hence the timer and class "am-animation-done")
What I preferably would like to have/learn is best practice to do this in reactjs. I'm thinking a toggle state that when triggered sets the state to visible/hidden and if set to "visible" the class changes below happens. My biggest issue is the timer thing.
showPanel = () => {
document.querySelector('body').classList.add('am-modal-locked');
document.querySelector('html').classList.add('am-modal-locked');
document.querySelector('.am-overlay').classList.add('fadein');
document.querySelector('.am-modal').classList.add('am-show');
const timer = setTimeout(() => {
document.querySelector('.am-modal').classList.add('am-animation-done');
}, 500);
return () => clearTimeout(timer);
};
hidePanel = () => {
document.querySelector('.am-modal').classList.remove('am-show');
document.querySelector('.am-modal').classList.remove('am-animation-done');
document.querySelector('.am-overlay').classList.add('fadeout');
const timer = setTimeout(() => {
document.querySelector('.am-overlay').classList.remove('fadein');
document.querySelector('.am-overlay').classList.remove('fadeout');
document.querySelector('body').classList.remove('am-modal-locked');
document.querySelector('html').classList.remove('am-modal-locked');
}, 500);
return () => clearTimeout(timer);
};
Source code updated for clarifaction
This is a lot simpler in React, here's an example with hooks
function Panel() {
const [hidden, setHidden] = useState(false);
const toggleCallback = useCallback(() => setHidden(hidden => !hidden), []);
const cls = hidden ? 'hide' : 'show';
return (
<div className={cls}>
<button onClick={toggleCallback}>Toggle</>
</div>
)
}
You can use the state to dinamically change classnames inside your component
className={this.state.isPanelVisible}
And maybe instead of setting it as boolean you can set your variable to the class you need at the moment.
React working with virtual DOM so you should play with state and change class of that particular element like below example:
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick(id) {
if(this.state.active){
this.setState({'active': false,'class': 'album'})
}else{
this.setState({'active': true,'class': 'active'})
}
}
<div className={this.state.class} onClick={this.handleClick.bind(this.data.id}>
<p>Data</p>
</div>
In very basic use cases you can write the logic inside of the class itself.
<div className={active ? "active" : "disabled"} />
In more advanced cases I would suggest to use something like classnames package.
https://www.npmjs.com/package/classnames
<div className={classNames({ foo: true, bar: true, boo: false })} />
Which would result in div having class foo and bar
This is mainly regarding one component, but if you really have to affect class of something so far away as body would be, than you are most likely gonna need useQuerySelector or put the state somewhere high and then base the logic on it.
Yes that's not a very good way to do it. Instead you should use state variables to toggle your classes as well. There is no need to manually manipulate DOM. The you can set up your timeout inside the callback of your first setState to change state again.
Maybe something like this:
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
class1: 'on',
class2: 'off'
}
}
toggle = () => {
this.setState({class1: 'off'}, () => {
setTimeout(() => {
this.setState({class2: 'on'})
}, 2000)
})
}
render() {
const {class1, class2} = this.state;
return (
<div>
<h1 className={`${class1} ${class2}`} onClick={this.toggle}>Class toggle</h1>
</div>
)
}
}
use this approach for change styling on state change
<div className={`rest_of_classes ${isClassChange ? 'change_class_name': ''}`} />
I'm already develop a timer on which we can enter a name and time in seconds to start the timer.
I want now to create a button and if I click, it display another timer with name, and time in seconds.
I don't know how I can do this...
Here is the timer with the inputs... https://jsfiddle.net/q806zeps/37/
I think if I can duplicate this ReactDOM.render(<App/>, document.getElementById('timer')); but I thonk it's not possible ?
Thanks
Please have a look at this code, hope it solves your problem.
class App extends React.Component {
constructor(props){
super(props)
this.state = {
counter : 1
}
}
//add timer code by oneshubh
addTimer(){
this.setState({counter: this.state.counter+1})
}
render(){
var timerDom = [];
for(var i = 0;i<this.state.counter;i++){
timerDom.push(<Wrapper />)
}
return (
<div>
{timerDom}
<button onClick={()=>this.addTimer()} >add timer</button>
</div>);
}
}
You can use an array on the state of Wrapper to track the individual timers within your application.
state: {
timers: []
}
Clicking the button, adds an object into your state with your current keys.
{
libelle: 'Input name',
seconds: 10
}
Iterate over this array to render you Minuteur components by passing the correct index.
Here is an updated fork of your fiddle. Creating timers using an array