My reanimated works more than once. How can make this animation work for once? - react-native

I'm trying to make my tabbar dissappear while scrolling down with animation. onScroll sends boolean value if last y coord is bigger than current y coord its scrolling up and otherwise scrolling down. If I continue to scroll down onScroll still sends true value to my function and animaton works more than once. How can i disable position so only toValue is gonna work and my function will not trigger again and again while returning boolean value is same from onScroll.
function runTiming(value, dest) {
const clock = new Clock();
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 200,
toValue: new Value(0),
easing: Easing.inOut(Easing.cubic),
};
return block([
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.time, 0),
set(state.position, value),
set(state.frameTime, 0),
set(config.toValue, dest),
startClock(clock),
]),
timing(clock, state, config),
cond(state.finished, debug('stop clock', stopClock(clock))),
state.position,
]);
}

This might be due to the onScroll event being fired more than once.
I just coded this yesterday and will give you the code that is working and has been tested:
export const throttle = (func, limit) => {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
class MainComponent extends PureComponent {
constructor(props) {
super(props);
this.offset = 0;
this.isAnimatingMenu = false;
this.onScroll = this.onScroll.bind(this)
};
onScroll = throttle(event => {
const currentOffset = event.nativeEvent.contentOffset.y;
const direction = currentOffset > this.offset ? 'down' : 'up';
const diff = currentOffset - this.offset;
if (direction === 'down' && diff > 9 && !this.isAnimatingMenu) {
this.isAnimatingMenu = true
this.onHideMenu(() => this.isAnimatingMenu = false)
}
if (direction === 'up' && diff < -9 && !this.isAnimatingMenu) {
this.isAnimatingMenu = true
this.onShowMenu(() => this.isAnimatingMenu = false)
}
this.offset = currentOffset;
}, 75)
render() {
<FlatList
windowSize={1}
bounces={false}
style={styles.flex1}
scrollEventThrottle={75}
onScroll={this.onScroll}
renderItem={this.renderItem}
data={this.deriveHomeWidgetsDataFromProps()}
/>
}
}
In the functions onHideMenu and onShowMenu call your animation function to show/hide the menu. onScroll can be implemented on ScrollView or SectionList aswell. If you need more help let me know.

Related

Jerky Animations After Awhile ThreeJs

At first, my animation seems to work fine. However, after a few seconds, the animations become very jerky and laggy, I'm not sure why.
At first I thought it was due to the animation button I had which allows the user to start and stop the animation at will. However, even after I commented out the button, the animation continued to be laggy.
let camera, scene, renderer;
const loader = new GLTFLoader();
let mixer = null;
let controls;
const clock = new THREE.Clock();
let previousTime = 0;
//start and stop button
let runAnim = false;
let isPlay = true;
//animation
function animation() {
if (!isPlay) return;
const elapsedTime = clock.getElapsedTime();
const deltaTime = elapsedTime - previousTime;
previousTime = elapsedTime;
//Update mixer
if (mixer !== null) {
mixer.update(deltaTime);
}
// Update controls
controls.update();
window.requestAnimationFrame(animation);
render();
}
function render() {
renderer.render(scene, camera);
}
module.exports = function getImage() {
const mountRef = useRef(null);
useEffect(() => {
//Model
loader.load(`/gltf/1.gltf`);
mixer = new THREE.AnimationMixer(gltf.scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
animation();
//Camera
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(4, 0, 5);
scene = new THREE.Scene();
// Controls
controls = new OrbitControls(camera, renderer.domElement);
controls.update();
controls.enableDamping = true;
// Animation button
const animateButton = document.getElementById('animate-button');
const stopAnimation = (e) => {
if (runAnim) {
runAnim = false;
isPlay = true;
animation();
console.log('animation starts');
} else {
runAnim = true;
isPlay = false;
console.log('animation stops');
}
};
animateButton.addEventListener('click', stopAnimation);
return () => mountRef.removeChild(renderer.domElement);
}, []);
return (
<div>
<div ref={mountRef}>
<AnimationButton />
</div>
</div>
);
};

How to pause the clock when app is going in the background state

I created a progress bar using react-native-reanimated and react-native-redash that is working fine but now I want to pause the progress or clock when the app is going to the background state and when the app comes to the active state it should be resume with the last position and the time.
clock config
const runTiming = (clock, quizDurationTiming) => {
const state = {
finished: new Value(0),
position: new Value(0),
frameTime: new Value(0),
time: new Value(0),
}
const config = {
toValue: new Value(1),
duration: quizDurationTiming * 10,
easing: Easing.in(Easing.ease),
}
return block([
cond(
not(clockRunning(clock)),
set(state.time, 0),
timing(clock, state, config)
),
cond(eq(state.finished, 1), stopClock(clock)),
state.position,
])
}
const SpecialTestTimer = ({
preparationTime,
appState,
quizDurationTiming,
}) => {
const [isCompleted, setIsCompleted] = useState(false)
const clock = useClock()
const progress = useValue(0)
const [play, setPlay] = useState(true)
const isProgress = useValue(0)
useEffect(() => {
if (appState == "active") setPlay(true)
else setPlay(false)
}, [appState])
useCode(() => set(isProgress, play ? 1 : 0), [play])
useCode(
() => [
cond(and(progress, not(clockRunning(clock))), startClock(clock)),
cond(and(not(progress), clockRunning(clock)), stopClock(clock)),
set(progress, runTiming(clock, quizDurationTiming)),
],
[]
)
}
I wrote this code but not working properly like which I want. when I go to the screen clock is not starting but when I came to the app after the background state then the clock is starting.

React-native flatlist to keep scrolling while touching view

I am trying to implement a drag and drop solution in react-native and I want to make my flatlist scroll if I drag an item in the upper 10 percent and bottom 10 percent of the view. So far the only way i can get it to happen is by calling a function that recursively dispatches a this._flatList.scrollToOffset({ offset, animated: false });. The recursive call stops when a certain condition is met but the scrolling effect is choppy. Any advice on making that smoother?
// on move pan responder
onPanResponderMove: Animated.event([null, { [props.horizontal ? 'moveX' : 'moveY']: this._moveAnim }], {
listener: (evt, gestureState) => {
const { moveX, moveY } = gestureState
const { horizontal } = this.props
this._move = horizontal ? moveX : moveY;
const { pageY } = evt.nativeEvent;
const tappedPixel = pageY;
const topIndex = Math.floor(((tappedPixel - this._distanceFromTop + this._scrollOffset) - this._containerOffset) / 85);
const bottomIndex = Math.floor(((tappedPixel + (85 - this._distanceFromTop) + this._scrollOffset) - this._containerOffset) / 85);
this.setTopAndBottom(topIndex, bottomIndex);
this.scrolling = true;
this.scrollRec();
}
}),
// recursive scroll function
scrollRec = () => {
const { activeRow } = this.state;
const { scrollPercent, data, } = this.props;
const scrollRatio = scrollPercent / 100;
const isLastItem = activeRow === data.length - 1;
const fingerPosition = Math.max(0, this._move - this._containerOffset);
const shouldScrollUp = fingerPosition < (this._containerSize * scrollRatio); // finger is in first 10 percent
const shouldScrollDown = !isLastItem && fingerPosition > (this._containerSize * (1 - scrollRatio)) // finger is in last 10
const nextSpacerIndex = this.getSpacerIndex(this._move, activeRow);
if (nextSpacerIndex >= this.props.data.length) { this.scrolling = false; return this._flatList.scrollToEnd(); }
if (nextSpacerIndex === -1) { this.scrolling = false; return; }
if (shouldScrollUp) this.scroll(-20);
else if (shouldScrollDown) this.scroll(20);
else { this.scrolling = false; return; };
setTimeout(this.scrollRec, 50);
}
/
Yeah, pass an options object to your Animated.event with your listener with useNativeDriver set to true
Animated.event([
null,
{ [props.horizontal ? "moveX" : "moveY"]: this._moveAnim },
{
listener: ...,
useNativeDriver: true
}
]);
Should make things significantly smoother.
edit: I feel I should add that there is probably a saner way to accomplish this, are you only using a FlatList because of it's "infinite" dimensions?

React-Native — How to select a start and end date using react-native-calendars (WIX)?

I am trying to enable date range using react-native-calendars. On my app, the calendar loads a few 'markedDates'; now I need to implement a start and end date functionality without affecting these initial dates. Unfortunately, I am struggling to achieve that. Any ideas on how can I do that?
Thank you in advance.
Pseudo-code
Load calendar with marked dates
Tap on start date
Tap on end date
Continue
Component
export default class Dates extends Component {
static navigationOptions = {
title: 'Choose dates',
}
constructor(props) {
super(props)
this.state = {
selected: undefined,
marked: undefined,
}
}
componentDidMount() {
this._markDate()
}
_markDate = () => {
nextDay = []
const marked = {
[nextDay]: { selected: true, marked: true },
}
Util._findShows(resp => {
resp.map(data => {
nextDay.push(data.date)
})
var obj = nextDay.reduce((c, v) => Object.assign(c, { [v]: { marked: true, dotColor: 'black' } }), {})
this.setState({ marked: obj })
})
}
_selectDate = obj => {
this.setState({ selected: obj.dateString })
}
render() {
return (
<View style={styles.container}>
<CalendarList
// Callback which gets executed when visible months change in scroll view. Default = undefined
onVisibleMonthsChange={months => {
console.log('now these months are visible', months)
}}
// Max amount of months allowed to scroll to the past. Default = 50
pastScrollRange={0}
// Max amount of months allowed to scroll to the future. Default = 50
futureScrollRange={12}
// Enable or disable scrolling of calendar list
scrollEnabled={true}
// Enable or disable vertical scroll indicator. Default = false
showScrollIndicator={true}
markedDates={
// [this.state.selected]: { selected: true, disableTouchEvent: true, selectedDotColor: 'orange' },
this.state.marked
}
onDayPress={day => {
this._selectDate(day)
}}
/>
<View style={styles.ctaArea}>
<TouchableOpacity style={styles.button} onPress={() => this.props.navigation.navigate('Dates')}>
<Text style={styles.btTitle}>Continue</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
I had the same struggle but so I decided to make my own version.
I'm sure it can be done better and have more functionalities but it works okay for me
const [dates, setDates] = useState({});
// get array of all the dates between start and end dates
var getDaysArray = function(start, end) {
for (var arr=[], dt = new Date(start); dt <= new Date(end); dt.setDate(dt.getDate() + 1)){
arr.push(useFormatDate(new Date(dt)));
}
return arr;
};
// empty object
const emptyObj = (obj: Object) => {
var props = Object.getOwnPropertyNames(obj);
for (var i = 0; i < props.length; i++) {
delete dates[props[i]];
}
}
// check if first date is smaller or equals to second date
const compareDate = function(first: string, second: string) {
return (new Date(first) <= new Date(second) ? true : false);
}
// fill with color the date between first and second date selected
const fillRangeDate = function(first: string, second: string) {
emptyObj(dates);
let newDates = dates;
newDates[first]={selected: true, Colors[colorScheme].tint};
newDates[second]={selected: true, color: Colors[colorScheme].tint};
var range = getDaysArray(first, second);
for (var i = 1; i < range.length - 1; i++)
newDates[range[i]]={color: '#70d7c7', textColor: 'white'};
setDates({...newDates})
}
const selectDate = (day) => {
let selectedDate = day.dateString;
let newDates = dates;
// if 2 dates are selected
if (Object.keys(dates).length >= 2) {
var props = Object.getOwnPropertyNames(dates);
if (compareDate(props[0], selectedDate)) {
fillRangeDate(props[0], selectedDate);
} else {
emptyObj(dates);
}
} else {
// 1 date selected
if (Object.keys(dates).length == 0) {
newDates[selectedDate]={selected: true, color: Colors[colorScheme].tint};
} else if (Object.keys(dates).length == 1) { // 2 dates selected
newDates[selectedDate]={selected: true, color: Colors[colorScheme].tint};
// if 2nd date < 1st date, cancel range
var props = Object.getOwnPropertyNames(dates);
if (compareDate(props[0], props[1])) {
var range = getDaysArray(props[0], props[1]);
for (var i = 1; i < range.length - 1; i++) {
newDates[range[i]]={color: '#70d7c7', textColor: 'white'};
}
} else {
emptyObj(dates);
}
}
}
setDates({...newDates})
}
You'll also need to add this function that I implemented as a hook:
const useFormatDate = (date: Date) => {
function padTo2Digits(num) {
return num.toString().padStart(2, '0');
}
return [
date.getFullYear(),
padTo2Digits(date.getMonth() + 1),
padTo2Digits(date.getDate()),
].join('-');
};
Help me to improve this code and maybe create a merge request on the wix/react-native-calendar
Hope this helps

Swiper JS return to first slide

I have slider with swiper js, how it can return to first slide when we click Next Button on end of slide ?
http://idangero.us
You can set the loop parameter. For example:
var mySwiper = new Swiper ('.swiper-header', {
loop: true, //this should allow the last next button to return to the beginning
nextButton: '.swiper-button-next',
prevButton: '.swiper-button-prev',
});
i made function like this:
.swiper-next is: navigation - nextEl
var changed = false;
$(".swiper-next").on('click', function () {
if (changed === true) {
changed = false;
faqSwiper.slideTo(0);
}
if (faqSwiper.isEnd) changed = true;
})
You may use "click" event from Swiper API.
Note: setTimeout is needed to correctly determine slide index
const swiper_instance = new Swiper('.swiper-container', {
// ...
});
swiper_instance.on('click', function (swiper, event) {
const is_next_click = event.target === swiper.navigation.nextEl,
is_prev_click = event.target === swiper.navigation.prevEl,
is_end = this.isEnd,
is_beginning = this.isBeginning;
// slide to begin
if (is_next_click && is_end) {
setTimeout(() => swiper.slideTo(0))
}
// slide to end
if (is_prev_click && is_beginning) {
setTimeout(() => swiper.slideTo(swiper.slides.length))
}
});