I am using Expo and the latest version of React-Native and I want to provide a subtle tactile feedback for Components like a View or Button.
I currently use animatable to start a pulse animation on the onPress() event but the animation only fires once the finger is released.
I want a subtle size reduction whilst press then a smooth tween back when released - that would feel elegant and not annoying to me.
Can this be done? I thought Animation or Animatable would have easily supported this but I can’t find any similar examples.
You could make your own touchable using the Gesture Responder System
Basically you'll use the props onStartShouldSetResponder, onResponderGrant, and onResponderRelease passed to an Animated.View.
class MyTouchable extends React.PureComponent {
render(){
<Animated.View
style={{ your styles, and animation values }}
onStartShouldSetResponder={() => true}
onResponderGrant={({ nativeEvent }) => {
//run your animations here
}}
onResponderRelease={({ nativeEvent }) => {
//animate back to zero, call your onPress function
//passed via props
}}
/>
}
}
Related
I have a Material-UI button whose markup looks like this:
<Button
disableFocusRipple={true}
disableRipple={true}
color="inherit"
onClick={openBlogMenu}
className={classes.blogButtonStyle}
>
<LibraryBooksIcon />
Blog
{!blogMenu && <KeyboardArrowDownIcon />}
{blogMenu && <KeyboardArrowUpIcon />}
</Button>
<BlogDropDown pageURL={pageURL} />
Here, the openBlogMenu changes the blogMenu Redux state which toggles BlogDropDown into view. As apparent, I'm also reading this state to toggle between the two arrow icons, KeyboardArrowDownIcon and KeyboardArrowUpIcon.
So far, this setup works smooth. However, I'm keen on doing away with KeyboardArrowUpIcon altogether and instead, making KeyboardArrowDownIcon rotate 180 degrees upon click.
I know it's as easy as just adding one of the available pseudo-classes, e.g., :active or :focus to the icon element. But then it'll only rotate if the icon itself is clicked rather than the entire button.
I also understand one could just dynamically add a transform attribute (transform: rotate(180deg);) to the icon's CSS in the click handler, but that wouldn't animate the transition.
Any tips?
P.S.: For illustration of what I'm looking for, check out menu option More on https://www.flipkart.com/
You can use CSS to only flip the icon, not the button. Here is the solution that works perfectly fine for me:
const useStyles = makeStyles (theme => ({
open : {
transform: "scaleX(1)",
},
close: {
transform: "scaleX(-1)",
},
}));
export default function Test() {
const classes = useStyles();
const drawerToggle = () => { setOpen(!open) };
const [open, setOpen] = React.useState(false);
return (
<IconButton aria-label="open drawer" onClick={drawerToggle} edge="start">
<MenuOpenRoundedIcon className={clsx(!open && classes.close, open && classes.open)}/>
</IconButton>
)
}
Use clsx (or any equivalent conditional class selector) to assign when the icon needs to be flipped based on the state of the button. Here, I'm using a state hook, open, and toggle its boolean value on click. Later, I used clsx to decide, based on the value of open, which class I assign to the icon, i.e., which direction I need to flip it.
If you want to flip vertically, you can use scaleY instead.
Here is the code I wrote in the code sandbox with both Button and IconButton examples (vertical and horizontal): https://codesandbox.io/s/festive-maxwell-3kcge?file=/src/App.js
I am wondering if it is possible to debounce a jsx element's rendering. I have an animation of a panel expanding that has some content in it. If the panel is empty (just the background) the animation is smooth and works as expected. The animation and associated components are all from Material-UI. The issue arises when the content is in the panel already so the animation (width expansion) just skips out to the width of the content making the animation look choppy. Here is a similar example to what I am referring to. This code is my code I am using in my panel and they work the same in terms of the expansion. Only difference is the content in this example is just lorem ipsum so the animation appears to work fine. Is it possible to debounce the <CardContent /> component's rendering like this?
{ open &&
(
_.debounce(e => {
return (
<CardContent>
{/*content in here*/}
</CardContent>
)
}, 300)
)
}
or something similar (this doesn't work) so the panel will be fully expanded through the animation before the content is rendered?
I'd like to add a comment however my reputation isn't above 50 yet so unfortunately I can't :p.
JS Animation
To answer your question, if you are using JavaScript animations like GSAP or AnimeJS you can have a state flag called. isAnimating = null. Then when your animation starts set the flag to true and when it's finished set it to false. Then in your render() method write a conditional render like this.state.isAnimating === false && <CardContent />.
CSS Animation
If you are using CSS animation you can use the transitionend event to wait for the animation to end. Therefore you have to create a ref on the animating DOM element to which you can attach the event.
Does that help you a bit? Please let me know if you have any other questions.
Here is a solution using react spring library
I added a state variable in order to display or not the card content
const [contentVisible, setContentVisible] = React.useState(false);
I created a spring to handle the width transition, I set contentVisible to true as soon as the width approaches the 300
const { width } = useSpring({
width: open ? 300 : 120,
onFrame: ({ width }) => setContentVisible(width > 299.8)
});
Finally, I created an animated card component
const AnimatedCard = animated(Card);
...
<AnimatedCard classes={{ root: classes.card }} style={{ width }}>
I have a ScrollView using Animated Event to do some basic animations. Everything works great but now I am wanting to animate blurRadius on images but the blur doesn't kick in until a few seconds after scrolling began. I've tried just about every animation solution possible...
This is the ScrollView... (AnimatedEvent.scrollYOffset is just a shortcut function I made) (Normally I have the throttle at 16 but found 7 was a little better but not even close to good)
<ScrollView
onScroll={AnimatedEvent.scrollYOffset(animY)}
scrollEventThrottle={7}
style={[QuickStyle.fillScreen,{
backgroundColor: 'rgb(16, 50, 74)',
flex: 1,
}]}>
This is one of the images with the blur property...
const blurry = this.state.animY.interpolate({
inputRange: [0,150],
outputRange: [0,7],
extrapolate: 'clamp'
});
const slide1 = <View style={[{
borderRadius: borderRadius,
overflow: 'hidden',
width: width,
height: height,
backgroundColor: 'rgb(28, 181, 251)' //light blue
}]}>
<Animated.Image blurRadius={blurry} ref={img => this.blur1 = img} style={{
width: width,
height: height,
}} source={require('../../assets/images/big-numbers.jpg')} />
</View>;
What I've tried... (all with the same laggy choppy result) 1) Animating directly into the blurRadius prop 2) Using a ref with setNativeProps({blurRadius:x}) 3) Using setState({blurRadius:x}) - this was at least smooth but slowed down every animation a lot.
What I am trying next... Running these 3 solutions in release mode. I also will consider doing this directly in Objective-C, but really want to avoid that...
Would the react-native-blur package help? I'm guessing not...
Please help! Thank you!
I finally gave in and just put a BlurView from react-native-blur over what I wanted to blur. I then just animated the opacity and everything is smooth as can be.
The only problem is, it does not look as nice as blurring the image directly through blurRadius, but so far it's the only way I've been able to get this to work for now.
Please, if you have another solution, let me know!
UPDATE
Finally, I got this working without having to animate opacity on a react-native-blur BlurView! And it looks 10x better!
I am using the solution by #umitanuki from https://github.com/react-native-community/react-native-blur/issues/152
Here's how I did it...
Go to https://github.com/AlpacaDB/react-native-blur/tree/animate/ios
Set your BlurView.m in Xcode to exactly what the BlurView.m there has.
Use onScroll and setState to change the blurAmount property on a BlurView (react-native-blur).
This accomplishes the same effect as blurRadius but runs perfectly smooth when animating.
EXTRA
When using this solution I found one problem... When exiting the app and coming back to it (without fully quitting it), the blur goes away until you scroll again or do something to update it.
To solve this I went back into BlurView.m and added an observer for when the app becomes active...
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(appBecameActive)
name:UIApplicationDidBecomeActiveNotification
object:nil];
-(void) appBecameActive {
[self updateBlurEffect];
}
I then ran updateBlurEffect when the app became active. Unfortunately there was no way to solve this within react-native, but this works perfectly.
I am developing an app with react native. I have this UI element which is similar to that of Maps in iOS, in which you slide a panel from the bottom and inside it, there is a scrollable list.
For the slide-out panel, I am using a component called rn-sliding-up-panel. It has several props as event listeners. For example
<SlidingUpPanel
allowDragging={/*Boolean*/}
onDragStart={()=>{} /*When it is about to be dragged*/}
onDrag={()=>{} /*When it is being dragged*/}
onDragEnd={()={} /*When the user is no longer touching the screen*/}
></SlidingUpPanel>
Inside it, I have a <ScrollView> containing a <List> from react-native-elements. As far as I know, it has only one vent listener, being:
<ScrollView onScroll={()=>{}}></ScrollView>
My issue is that scrolling on the list actually causes the panel to close (it closes by sliding down). I found a work-around by adding a state, and modfiying it onScroll:
state = {
dragPanel: true,
}
/*===========================================*/
<SlidingUpPanel allowDragging={this.state.dragPanel}>
<ScrollView onScroll={()={ this.setState({dragPanel: false}) }}></ScrollView>
</SlidingUpPanel>
However, I cannot find a way to restore the dragging, and it doesn't fire up as efficiently.
TL;DR
Is there an eficient way to implement a ScrollView inside a SlidingUpPanel without the events of each overlapping? Maybe using something similar to function(e){e.preventDefault();}?
To properly disable / restore outer scroll dragging, do
_onGrant() {
this.setState({ dragPanel: false });
return true;
}
_onRelease() {
this.setState({ dragPanel: true });
}
constructor(props) {
super(props);
this._onGrant = this._onGrant.bind(this);
this._onRelease = this._onRelease.bind(this);
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder: this._onGrant,
onPanResponderRelease: this._onRelease,
onPanResponderTerminate: this._onRelease,
});
}
render() {
<SlidingUpPanel allowDragging={this.state.dragPanel}>
<ScrollView
{...this._panResponder.panHandlers}
/>
</SlidingUpPanel>
}
From what I had been searching for a long time, preventDefault() is a pure web-javascript thing, I think there are no preventDefault in react-native.
From document section Handling Touches, react-native just use javascript to simulate Objc (iOS) & Java (Android) events.
Set the minimumDistanceThreshold property to something around 50. Maybe 30 for small screens and 50-60 for bigger ones. Do it like so:
<SlidingUpPanel minimumDistanceThreshold={isSmallScreen ? 30 : 50}>
<ScrollView style={{flex: 1}}>
</ScrollView>
</SlidingUpPanel>
It might be late for an answer but use your scroll view as absolute positioned and position it accordingly.
Think of the scroll view as a pop-up dialog that appears in front of the backdrop behind it. Upon clicking the backdrop, the pop-up dismisses. Apply similar logic to the issue by letting scroll view in front of the slide up panel.
look this video of current scrolling behavior. i want, as soon as finger is lifted from screen, to disable scrolling.
Edit: the video shows a user scrolling through a list. When the finger is lifted from the screen, you see the scrolling continues up or down for a short time.
I think what you need is:
<ScrollView
bounces={false}
/>
This way when you scroll finished, the scroll animation will not continue and immediately stopped.
What I believe you want is basically to stop the scroll when momentum beings. For that, you can use the onMomentumScrollBegin callback on your ScrollView and call .scrollToEnd({animated: false}) on it.
Here's how that would look:
<ScrollView
ref={(ref) => this.scrollView = ref}
scrollEnabled={this.state.scroll}
onMomentumScrollBegin={
() => this.scrollView.scrollToEnd({animated: false})
}
>
...CONTENT HERE...
</ScrollView>
i think u mean stop scrolling once user lifts his finger
try :
<ScrollView
...
decelerationRate = {0}
/>
check docs