My issue if the following:
The duration of the video is 6.357 seconds. The progress.currentTime only goes to 6.154 and stops.
Since that this.state.progress = 0.97 (not 1.0). As an outcome my ProgressBar doesn't go to the end. It stops on 0.97 position and onProgressPress() doesn't work.
Could someone please assist?
Here is my code:
export default class VideoComp extends Component {
state = {
paused: false,
progress: 0,
duration: 0
}
onProgressPress = (e) => {
const position = e.nativeEvent.locationX;
const progress = (position / 250) * this.state.duration;
this.player.seek(progress);
}
onMainButtonPress = () => {
if(this.state.progress >= 1) {
this.player.seek(0);
};
this.setState(state => {
return {
paused: !state.paused
}
})
}
handleEnd = () => {
this.setState({
paused: true
})
}
handleProgress = (progress) => {
this.setState({
progress: progress.currentTime / this.state.duration
});
}
handleLoad = (meta) => {
this.setState({
duration: meta.duration
})
}
render() {
const { width } = Dimensions.get('window');
const height = width * 0.5625;
return(
<View style = {styles.videoWrapper}>
<Video
source = {{uri: this.props.videoURL}}
ref = {ref => this.player = ref}
style = {{width: '100%', height}}
paused = {this.state.paused}
resizeMode = 'contain'
onLoad = {this.handleLoad}
onProgress = {this.handleProgress}
onEnd = {this.handleEnd}
/>
<View style = {styles.controls}>
<TouchableWithoutFeedback onPress = {this.onMainButtonPress}>
<IconSimpleLine name = {!this.state.paused ? 'control-pause' : 'control-play'} color = {text} size = {20}/>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress = {this.onProgressPress}>
<View>
<ProgressBar
progress = {this.state.progress}
width = {250}
height = {5}
color = {text}
borderColor = {text}
unfilledColor = 'rgba(255, 255, 255, 0.3)'
/>
</View>
</TouchableWithoutFeedback>
<Text style = {styles.duration}>
{secondsToTime(Math.floor(this.state.progress * this.state.duration))}
</Text>
</View>
</View>
)
}
}
UPDATE
I tried the next:
handleProgress = (progress) => {
this.setState({
progress: Math.floor(progress.currentTime) / this.state.duration
});
}
handleLoad = (meta) => {
this.setState({
duration: Math.floor(meta.duration)
})
}
The ProgressBar line now goes to the very end but it moves by a second. I mean, it moves a bit, stops on one second, moves a bit farther, stops on one second and so on. It doesn't move smoothly (each millisecond).
But it's not the correct solution.
the Video component has an onEnd prop. use that to update your state to 100%
Try to user parseFloat() for calculation purpose.
in your question, I don't know which value is coming in float or in a fraction number but just for example use as this.
this.setState({
progress: parseFloat(progress.currentTime / this.state.duration)
});
and
const progress = parseFloat(position / 250)) * this.state.duration;
Hope it will help you.
Related
I am trying to create a custom range slider, and to get the value of the translationX and pass it as a prop and/or into my redux, however this has seemed impossible, as it does not update, even when I use useState.
Has any one experienced this issue in the past.
Below is my current code
const CustomSlider = ({ onPressOut, testData }) => {
const translateX = useSharedValue(SCREEN_WIDTH / 2);
const context = useSharedValue({ x: SCREEN_WIDTH / 2 });
console.log(testData);
const handleGesture = Gesture.Pan()
.onStart(() => {
context.value = { x: translateX.value };
})
.onUpdate(event => {
translateX.value = event.translationX + context.value.x;
if (translateX.value < SCREEN_WIDTH / 2) {
translateX.value = Math.max(translateX.value, MIN_TRANSLATE_X);
} else {
translateX.value = Math.min(translateX.value, MAX_TRANSLATE_X);
}
testData(translateX.value);
});
const rBottomSheetStyle = useAnimatedStyle(() => {
return {
left: translateX.value,
};
});
return (
<View style={styles.container}>
<View style={styles.negativeLineStyle} />
<View style={styles.positiveLineStyle} />
<GestureDetector gesture={handleGesture}>
<AnimatedTouchableOpacity
activeOpacity={1}
onPressOut={onPressOut}
style={[rBottomSheetStyle, styles.rangeSliderContainer]}>
<LinearGradient
colors={[SECONDARY, PRIMARY]}
start={[1, 0.9]}
end={[1, 0]}
style={styles.rangeSliderStyle}
/>
</AnimatedTouchableOpacity>
</GestureDetector>
</View>
);
};
I'm currently trying to fade through a series of images. Basically, I always want to display one image at a time, then animate its opacity from 1 to 0 and that of the next image in the series from 0 to 1, and so on. Basically something like this, which I've already implemented for the web in ReactJS and CSS animations:
However, I seem to keep getting stuck on using React Native's Animated library and refs. I've tried storing the opacity of all the images in an array which itself is contained in an useRef hook. Then, using Animated, I'm trying to perform two parallel animations which change the opacity of the current image index and that of the next index. This is what I've come up with:
export default function StartImageSwitcher() {
const images = Object.values(Images).map((img) => img.imageNoShadow);
const [currentImage, setCurrentImage] = useState(0);
const opacity = useRef<Animated.Value[]>([
new Animated.Value(1),
...Array(images.length - 1).fill(new Animated.Value(0)),
]).current;
useEffect(() => {
let nextImage = currentImage + 1;
if (nextImage >= images.length) nextImage = 0;
Animated.parallel([
Animated.timing(
opacity[currentImage],
{
toValue: 0,
duration: 2000,
useNativeDriver: true,
},
),
Animated.timing(
opacity[nextImage],
{
toValue: 1,
duration: 2000,
useNativeDriver: true,
},
),
]).start(() => {
setCurrentImage(nextImage);
});
}, [currentImage]);
images.map((image, index) => console.log(index, opacity[index]));
return (
<View style={styles.imageWrapper}>
{
images.map((image, index) => (
<Animated.Image style={{ ...styles.image, opacity: opacity[index] }} source={image} key={index} />
))
}
</View>
);
}
However, this doesn't seem to work at all. When mounted, it only shows the first image, then fades that one out and all the other images in and gets stuck there:
Anyone got an idea where I messed up? I feel like I'm not using the useRef() hook in combination with the Animated library like I'm supposed to.
Your solution is pretty clever, and it generally looks like it should work to me. Performance might take a hit though as the number of images increases. I thought of an alternate method: partition the images array and then use setInterval to alternate between two Animated.Images, which get their source from each array.
// from https://stackoverflow.com/questions/41932345/get-current-value-of-animated-value-react-native
const getAnimatedValue = (value: Animated.Value) => Number.parseInt(JSON.stringify(value));
export default function StartImageSwitcher() {
// partition images into two arrays
const images1 = [];
const images2 = [];
Object.values(Images).forEach((img, i) => {
(i % 1 === 0 ? images1 : images2).push(img.imageNoShadow)
});
// use refs for the indexes so values don't become stale in the setInterval closure
const images1Index = useRef(0);
const images2Index = useRef(0);
const image1Opacity = useRef(new Animated.Value(1)).current;
const image2Opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
const swapImageInterval = setInterval(() => {
const newImage1Opacity = getAnimatedValue(image1Opacity) === 1 ? 0 : 1;
const newImage2Opacity = getAnimatedValue(image2Opacity) === 1 ? 0 : 1;
Animated.parallel([
Animated.timing(image1Opacity, {
toValue: newImage1Opacity,
duration: 2000,
useNativeDriver: true,
}),
Animated.timing(image2Opacity, {
toValue: newImage2Opacity,
duration: 2000,
useNativeDriver: true,
}),
]).start(() => {
if (newImage1Opacity === 1) {
// image 2 is now faded out, so we can swap out its source
const nextIndex = images2Index.current === images2.length - 1 ? 0 : images2Index.current + 1;
images2Index.current = nextIndex;
} else {
// image 1 is faded out, so we can swap out its source
const nextIndex = images1Index.current === images1.length - 1 ? 0 : images1Index.current + 1;
images1Index.current = nextIndex;
}
})
}, 5000)
return () => clearInterval(swapImageInterval);
}, [images1Index, images2Index]);
return (
<View style={styles.imageWrapper}>
<Animated.Image style={{ ...styles.image, opacity: image1Opacity }} source={images1[images1Index.current]} />
<Animated.Image style={{ ...styles.image, opacity: image2Opacity }} source={images2[images2Index.current]} />
</View>
);
}
I have already written a component that enables 360 product views for users. Users can interact with image (rotate to the right or left). This Component works well when images are from local files.
I want to fetch images from Cloudinary and render them. A product may have 100+ images so I want to show loader till all images are loaded. Once done image will render and when the user rotates new image is rendered. I was also looking for npm lib that takes care of loading and rendering images.
import React, { Component } from "react";
import "./styles.css";
import { Row, Space, Typography, Image } from "antd";
import { Md3DRotation } from "react-icons/md";
// You can play with this to adjust the sensitivity
// higher values make mouse less sensitive
const pixelsPerDegree = 1;
class React360 extends Component {
static defaultProps = { dir: "puma", numImages: 83 };
state = {
dragging: false,
imageIndex: 0,
dragStartIndex: 0,
images: [],
show360: false,
imgs:null
};
componentDidMount = () => {
document.addEventListener("mousemove", this.handleMouseMove, false);
document.addEventListener("mouseup", this.handleMouseUp, false);
// document.addEventListener("touchstart", this.handleMouseMove, false);
// document.addEventListener("touchend", this.handleMouseUp, false);
};
componentWillUnmount = () => {
document.removeEventListener("mousemove", this.handleMouseMove, false);
document.removeEventListener("mouseup", this.handleMouseUp, false);
// document.removeEventListener("touchstart", this.handleMouseMove, false);
// document.removeEventListener("touchend", this.handleMouseUp, false);
};
handleMouseDown = (e) => {
// console.log("e.screenX",Math.round(e?.touches?.[0]?.clientX))
e.persist();
this.setState((state) => ({
dragging: true,
dragStart: e.screenX || Math.round(e?.touches?.[0]?.clientX),
dragStartIndex: state.imageIndex,
}));
};
handleMouseUp = () => {
this.setState({ dragging: false });
};
updateImageIndex = (currentPosition) => {
let numImages = this.props.numImages;
const pixelsPerImage = pixelsPerDegree * (360 / numImages);
const { dragStart, imageIndex, dragStartIndex } = this.state;
// pixels moved
let dx = (currentPosition - dragStart) / pixelsPerImage;
let index = Math.floor(dx) % numImages;
if (index < 0) {
index = numImages + index - 1;
}
index = (index + dragStartIndex) % numImages;
// console.log(index, dragStartIndex, numImages)
if (index !== imageIndex) {
this.setState({ imageIndex: index === 0 ? 1 : index });
}
};
handleMouseMove = (e) => {
// console.log("handleMouseMove",this.state.dragging)
console.log("screenX", Math.round(e?.touches?.[0]?.clientX));
if (this.state.dragging) {
this.updateImageIndex(e.screenX || Math.round(e?.touches?.[0]?.clientX));
}
};
preventDragHandler = (e) => {
e.preventDefault();
};
renderImage = () => {
const { imageIndex, images } = this.state;
return (
<>
<div className="react360">
<img
alt=""
src={
require(`../../assets/puma_opti/IMG_00${
imageIndex > 9 ? imageIndex : "0" + imageIndex
}.JPG`).default
}
/>
</div>
<Row justify="center" style={{ marginTop: "1.5em" }}>
<Space>
<Md3DRotation size={25} color="#8C8C8C" />
<Typography.Title
level={5}
type="secondary"
style={{ fontFamily: "Gilroy" }}
className="helptext-360"
>
Drag to view 360 degrees
</Typography.Title>
</Space>
</Row>
</>
);
};
render = () => {
return (
<div
className="react-360-img"
onMouseDown={this.handleMouseDown}
onDragStart={this.preventDragHandler}
// onTouchStart={this.handleMouseMove}
onTouchStart={this.handleMouseDown}
onTouchMove={this.handleMouseMove}
onTouchEnd={this.handleMouseUp}
>
{this.renderImage()}
</div>
);
};
}
1.For library you can use React Loading Skeleton : https://www.npmjs.com/package/react-loading-skeleton
2.If you want to display loading when you get data you have to add condition to your jsx, for instance:
fetchData.loading?(
return <Skeleton/>
):(
<div className="react360">
data.map(res=>{
<img
alt=""
src={
require(`../../assets/puma_opti/IMG_00${
imageIndex > 9 ? imageIndex : "0" + imageIndex}.JPG`).default
}
/>
}))
</div>
N.B: fetchdata is the status of your API call (if is loading or successful), data is data obtained from API.
Every element of the array should be displayed for some time and the time for which each element is displayed should be determined by a value in each element.
let array=[{display:"a",time:10},{display:"b",time:15},{display:"c",time:22}]
class App extends React.Component{
state={stateDisplay:"",
stateTime:""
}
componentWillMount(){
var i=0;
let handle=setInterval(()=>{
var element= array[i]
this.setState({
stateDisplay:element.display,
stateTime:element.time,
})
i=i+1;
if(i===array.length){
clearInterval(handle)
}
},10000)
}
render(){
return(
<div> {this.state.stateDisplay} </div>
)}}
i have done something like this but using setinterval the delay can only be set for a constant time,here 10s.
I want the first element to display for 10s and then the next element for 15s, third for 22s which is the time value for each element of the array.
I know i cant do that using setinterval is there a way to do this using Settimeout?
This was almost like a little challenge, heres what i managed to come up with, its in typescript, if you need js, just remove interfaces and type annotations
/* eslint-disable #typescript-eslint/no-explicit-any */
/* eslint-disable prettier/prettier */
/* eslint-disable no-shadow */
/* eslint-disable no-console */
import React, { FC, useState, useEffect, useCallback } from 'react';
import { View, Button, Text } from 'react-native';
interface Data {
duration: number;
bgColor: string;
}
const dataArr: Data[] = [
{ duration: 3, bgColor: 'tomato' },
{ duration: 6, bgColor: 'skyblue' },
{ duration: 9, bgColor: 'gray' },
];
const Parent = () => {
const [currentIdx, setCurrentIdx] = useState<number>(0);
const [elementData, setElementData] = useState<Data>(dataArr[currentIdx]);
useEffect(() => {
console.log('idx', currentIdx);
if (currentIdx > dataArr.length) return;
setElementData({ ...dataArr[currentIdx] });
}, [currentIdx]);
const pushNext = () => {
setCurrentIdx(currentIdx + 1);
};
const handleRestart = () => {
setCurrentIdx(0);
setElementData({ ...dataArr[0] });
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Timer
data={elementData}
onCountDownComplete={pushNext}
restart={handleRestart}
/>
</View>
);
};
interface Props {
data: Data;
onCountDownComplete: () => void;
restart: () => void;
}
const Timer: FC<Props> = ({ data, onCountDownComplete, restart }) => {
const [seconds, setSeconds] = useState<number>(data.duration);
// update on data change
useEffect(() => {
setSeconds(data.duration);
}, [data]);
const callback = useCallback(() => {
onCountDownComplete();
}, [onCountDownComplete]);
useEffect(() => {
let interval: any = null;
if (seconds > -1) {
interval = setInterval(() => {
if (seconds - 1 === -1) {
callback();
} else {
setSeconds(seconds - 1);
}
}, 1000);
} else {
return;
}
return () => {
clearInterval(interval);
};
}, [seconds, callback]);
return (
<View
style={{ backgroundColor: data.bgColor, padding: 16, borderRadius: 10 }}
>
<Text style={{ marginBottom: 24 }}>{seconds}</Text>
<Button title="restart" onPress={restart} />
</View>
);
};
Hopefully, the title isn't too ambiguous, but I am struggling how to summarise my issue in just a few words.
What I am attempting to achieve is the one image with appear for 1.5 seconds, the opacity of image will go from 0.3 to 1 and a string of text "Completed" appears below the image, and will then rotate and move to the next image where it will have the same outcome, constantly looping through each image.
The issue is, it only seems to rotate once. It will update the styles and add the text below the image but fail to rotate.
I have a 3d carousel component that rotates the items within an array logos when a function is called rotate().
const [rotateDeg, setRotateDeg] = useState(0);
const [currentIndex, setCurrentIndex] = useState(0);
const rotate = () => {
const maxIndex = logos.length - 1;
const incrementIndex = currentIndex + 1;
const newIndex = incrementIndex > maxIndex ? 0 : incrementIndex;
setCurrentIndex(newIndex);
setRotateDeg(rotateDeg - 360 / logos.length);
};
return (
<Container>
<Carousel ref={carousel} logosLength={logos.length} rotateDeg={rotateDeg}>
{logos.map((item, index) => {
const { key } = item;
return (
<Item
key={key}
index={index}
logosLength={logos.length}
currentIndex={currentIndex}
>
<LoadingSpinLogo
item={item}
delay={1500 * (index + 1)}
rotate={() => rotate()}
key={key}
isCurrent={currentIndex === index}
/>
</Item>
);
})}
</Carousel>
</Container>
);
The component LoadingSpinLogo is where I seem to be having an issue.
I have tried to pass different dependencies into the useEffect callback but it seems to cause weird issues with the delay.
const LoadingSpinLogo = ({ item, rotate, delay, isCurrent }) => {
const [completed, setCompleted] = useState(false);
const [divStyle, setDivStyle] = useState({ opacity: 0.3 });
const props = useSpring(divStyle);
const customStyles = {
height: "78px",
width: "115px",
backgroundRepeat: "no-repeat",
backgroundPosition: "center center",
display: "block",
margin: "auto"
};
const updateCompleted = () => {
setCompleted(true);
setDivStyle({ opacity: 1, from: { opacity: 0.3 } });
rotate();
};
useEffect(() => {
const timeoutID = setTimeout(function() {
updateCompleted();
}, delay);
return () => {
// Clean up the subscription
window.clearInterval(timeoutID);
};
}, []);
return (
<LoadingSpinContainer>
<animated.div style={props}>
<ImageServer png={item.url} customStyles={customStyles} />
</animated.div>
{completed ? "Completed" : null}
</LoadingSpinContainer>
);
};
Here is a CodeSanbox of my components.
Any idea where I am going wrong here?
Any help would be greatly appreciated.
Removing the [] in your useEffect function seems to works fine for me
useEffect(() => {
const timeoutID = setTimeout(function() {
updateCompleted();
}, delay);
return () => {
// Clean up the subscription
window.clearInterval(timeoutID);
};
}, );