Javascript 使用React本机动画存在渲染问题
我的动画有问题。我试图用两个不同的视图翻转卡片。当用户在两张不同的卡之间滚动时,我还试图创建一种滚动效果。当代码以下面的方式组合时,它会创建一个我无法消除的bug。我附上了一张图片,以直观地反映我的问题 谢谢你的帮助 : 我的生命周期方法:Javascript 使用React本机动画存在渲染问题,javascript,react-native,Javascript,React Native,我的动画有问题。我试图用两个不同的视图翻转卡片。当用户在两张不同的卡之间滚动时,我还试图创建一种滚动效果。当代码以下面的方式组合时,它会创建一个我无法消除的bug。我附上了一张图片,以直观地反映我的问题 谢谢你的帮助 : 我的生命周期方法: componentWillMount() { this.animatedValue = new Animated.Value(0); this.value = 0; this.animatedValue.addListener(({
componentWillMount() {
this.animatedValue = new Animated.Value(0);
this.value = 0;
this.animatedValue.addListener(({ value }) => {
this.value = value;
this.setState({ value });
});
this.frontInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg']
});
this.backInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
});
}
}
此动画用于生成翻转动画:
flipCard() {
if (this.value >= 90) {
this.setState({
isWaiting: true
});
Animated.spring(this.animatedValue, {
toValue: 0,
friction: 8,
tension: 10
}).start(() => {
this.setState({
isWaiting: false
});
});
} else {
this.setState({
isWaiting: true
});
Animated.spring(this.animatedValue, {
toValue: 180,
friction: 8,
tension: 10
}).start(() => {
this.setState({ isWaiting: false });
});
}
}
这是通过flipCard功能翻转的视图。如果您在其中一个视图中看到,则有一个名为transitionAnimation的函数。用于产生滚动效果的
<View style={styles.scrollPage}>
<View>
<Animated.View
style={[
frontAnimatedStyle,
styles.screen,
this.transitionAnimation(index)
]}
>
<Text style={styles.text}>{question.question}</Text>
</Animated.View>
<Animated.View
style={[
styles.screen,
backAnimatedStyle,
styles.back,
this.transitionAnimation(index)
]}
>
<Text style={styles.text}>{question.answer}</Text>
</Animated.View>
我的渲染功能:
render() {
const { flashcards } = this.state;
return (
<View style={styles.container}>
<View
style={{
alignItems: 'flex-end',
marginTop: 10
}}
>
<Progress.Circle
size={70}
showsText
progress={this.state.timer}
formatText={text => {
return (this.state.timer * 100).toFixed(0);
}}
/>
</View>
<Animated.ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: xOffset } } }],
{ useNativeDriver: true }
)}
horizontal
pagingEnabled
style={styles.scrollView}
>
{this.state.flashcards && this.renderCard()}
</Animated.ScrollView>
</View>
);
}
}
render(){
const{flashcards}=this.state;
返回(
{
返回(this.state.timer*100).toFixed(0);
}}
/>
我还创建了一个快餐店,在那里你可以看到这个问题。
您有很多问题:
主要问题是,您没有正确存储每张卡的状态(如果它是否翻转)。例如,您可以添加flippedCards
数组或设置为您的状态,并在每次翻转一张卡时进行更新,以便在动画结束时调用setState
后可以正确渲染,并正确渲染未翻转的其他卡
一次渲染所有卡牌并设置其动画(翻转和转换),但应仅渲染三张卡牌(当前卡牌和相邻卡牌),且应仅翻转当前卡牌
性能问题:在每个渲染上创建过渡样式和其他函数,这会使渲染速度非常慢
应该重构的其他代码
我修复了1和3个问题,并进行了一些重构。2由您决定:
import React, { Component } from 'react';
import { Animated, Dimensions, StyleSheet, Text, View, TouchableOpacity, TouchableWithoutFeedback } from 'react-native';
import { EvilIcons, MaterialIcons } from '@expo/vector-icons';
const SCREEN_WIDTH = Dimensions.get('window').width;
export default class App extends Component {
constructor(props) {
super(props);
const flashcards = ['konichiwa','hi','genki desu','how are you'];
this.state = {
flashcards,
flipped: flashcards.map(() => false),
flipping: false
};
this.flipValue = new Animated.Value(0);
this.frontAnimatedStyle = {
transform: [{
rotateY: this.flipValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg']
})
}]
};
this.backAnimatedStyle = {
transform: [{
rotateY: this.flipValue.interpolate({
inputRange: [0, 1],
outputRange: ['180deg', '360deg']
})
}]
};
let xOffset = new Animated.Value(0);
this.onScroll = Animated.event(
[{ nativeEvent: { contentOffset: { x: xOffset } } }],
{ useNativeDriver: false }
);
this.transitionAnimations = this.state.flashcards.map((card, index) => ({
transform: [
{ perspective: 800 },
{
scale: xOffset.interpolate({
inputRange: [
(index - 1) * SCREEN_WIDTH,
index * SCREEN_WIDTH,
(index + 1) * SCREEN_WIDTH
],
outputRange: [0.25, 1, 0.25]
})
},
{
rotateX: xOffset.interpolate({
inputRange: [
(index - 1) * SCREEN_WIDTH,
index * SCREEN_WIDTH,
(index + 1) * SCREEN_WIDTH
],
outputRange: ['45deg', '0deg', '45deg']
})
},
{
rotateY: xOffset.interpolate({
inputRange: [
(index - 1) * SCREEN_WIDTH,
index * SCREEN_WIDTH,
(index + 1) * SCREEN_WIDTH
],
outputRange: ['-45deg', '0deg', '45deg']
})
}
]
}));
}
render() {
return (
<View style={styles.container}>
<Animated.ScrollView
scrollEnabled={!this.state.flipping}
scrollEventThrottle={16}
onScroll={this.onScroll}
horizontal
pagingEnabled
style={styles.scrollView}>
{this.state.flashcards.map(this.renderCard)}
</Animated.ScrollView>
</View>
);
}
renderCard = (question, index) => {
const isFlipped = this.state.flipped[index];
return (
<TouchableWithoutFeedback key={index} onPress={() => this.flipCard(index)}>
<View>
<View style={styles.scrollPage}>
<View>
{(this.state.flipping || !isFlipped) && <Animated.View
style={[
this.state.flipping ? this.frontAnimatedStyle : this.transitionAnimations[index],
styles.screen
]}
>
<Text style={styles.text}>{this.state.flashcards[index]}</Text>
</Animated.View>}
{(this.state.flipping || isFlipped) && <Animated.View
style={[
styles.screen,
this.state.flipping ? this.backAnimatedStyle : this.transitionAnimations[index],
this.state.flipping && styles.back
]}
>
<Text style={styles.text}>{this.state.flashcards[index+1]}</Text>
</Animated.View>}
</View>
</View>
<View style={styles.iconStyle}>
<TouchableOpacity>
<EvilIcons name="check" size={80} color={'#5CAF25'} />
</TouchableOpacity>
<TouchableOpacity>
<MaterialIcons name="cancel" size={70} color={'#b71621'} />
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
);
}
flipCard = index => {
if (this.state.flipping) return;
let isFlipped = this.state.flipped[index];
let flipped = [...this.state.flipped];
flipped[index] = !isFlipped;
this.setState({
flipping: true,
flipped
});
this.flipValue.setValue(isFlipped ? 1: 0);
Animated.spring(this.flipValue, {
toValue: isFlipped ? 0 : 1,
friction: 8,
tension: 10
}).start(() => {
this.setState({ flipping: false });
});
}
}
const styles = StyleSheet.create({
container: {
backgroundColor:'red',
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between'
},
scrollView: {
flexDirection: 'row',
backgroundColor: 'black'
},
scrollPage: {
width: SCREEN_WIDTH,
padding: 20
},
screen: {
height: 400,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 25,
backgroundColor: 'white',
width: SCREEN_WIDTH - 20 * 2,
backfaceVisibility: 'hidden'
},
text: {
fontSize: 45,
fontWeight: 'bold'
},
iconStyle: {
flexDirection: 'row',
justifyContent: 'center'
},
back: {
position: 'absolute',
top: 0,
backfaceVisibility: 'hidden'
}
});
import React,{Component}来自'React';
从“react native”导入{动画、尺寸、样式表、文本、视图、TouchableOpacity、TouchableWithoutFeedback};
从“@expo/vector icons”导入{EvilIcons,MaterialIcons};
const SCREEN_WIDTH=尺寸.get('window').WIDTH;
导出默认类应用程序扩展组件{
建造师(道具){
超级(道具);
const flashcards=[‘konichiwa’、‘hi’、‘genki desu’、‘你好吗’];
此.state={
抽认卡,
翻转:flashcards.map(()=>false),
翻转:错误
};
this.flipValue=新的动画.Value(0);
this.frontAnimatedStyle={
转换:[{
rotateY:this.flipValue.interpolate({
输入范围:[0,1],
输出范围:['0deg','180deg']
})
}]
};
this.backAnimatedStyle={
转换:[{
rotateY:this.flipValue.interpolate({
输入范围:[0,1],
输出范围:['180deg','360deg']
})
}]
};
设xOffset=new Animated.Value(0);
this.onScroll=Animated.event(
[{nativeEvent:{contentOffset:{x:xOffset}}}}],
{useNativeDriver:false}
);
this.transitionAnimations=this.state.flashcards.map((卡片,索引)=>({
转换:[
{透视图:800},
{
比例:xOffset.interpolate({
输入范围:[
(索引-1)*屏幕宽度,
索引*屏幕宽度,
(索引+1)*屏幕宽度
],
输出范围:[0.25,1,0.25]
})
},
{
rotateX:xOffset.interpolate({
输入范围:[
(索引-1)*屏幕宽度,
索引*屏幕宽度,
(索引+1)*屏幕宽度
],
输出范围:['45度','0度','45度']
})
},
{
rotateY:xOffset.interpolate({
输入范围:[
(索引-1)*屏幕宽度,
索引*屏幕宽度,
(索引+1)*屏幕宽度
],
输出范围:['-45度','0度','45度']
})
}
]
}));
}
render(){
返回(
{this.state.flashcards.map(this.renderCard)}
);
}
renderCard=(问题、索引)=>{
const isfliped=this.state.fliped[index];
返回(
此.flipCard(索引)}>
{(this.state.fliping | | |!isfliped)&&
{this.state.flashcards[index]}
}
{(this.state.flipping | | isfliped)&
{this.state.flashcards[index+1]}
}
);
}
flipCard=索引=>{
如果(本状态翻转)返回;
让isFlipped=this.state.flipped[index];
让翻转=[…this.state.flipped];
翻转[索引]=!已翻转;
这是我的国家({
翻转:是的,
轻弹
});
this.flipValue.setValue(isfliped?1:0);
动画.spring(this.flipValue{
toValue:isFlipped?0:1,
摩擦:8,
张力:10
}).start(()=>{
this.setState({flipping:false});
});
}
}
const styles=StyleSheet.create({
容器:{
背景颜色:'红色',
弹性:1,
flexDirection:'列',
justifyContent:“间距”
},
滚动视图:{
flexDirection:'行',
背景颜色:“黑色”
},
滚动页面:{
宽度:屏幕宽度,
填充:20
},
屏幕:{
身高:400,
为内容辩护:“中心”,
对齐项目:“居中”,
边界半径:25,
背景颜色:“白色”,
宽度:屏幕宽度-20*2,
背面可见性:“隐藏”
},
正文:{
尺码:45,
fontWeight:“粗体”
},
iconStyle:{
flexDirection:'行',
为内容辩护:“中心”
},
背面:{
位置:'绝对',
排名:0,
背面可见性:“隐藏”
}
});
至少现在它工作正常。ma
import React, { Component } from 'react';
import { Animated, Dimensions, StyleSheet, Text, View, TouchableOpacity, TouchableWithoutFeedback } from 'react-native';
import { EvilIcons, MaterialIcons } from '@expo/vector-icons';
const SCREEN_WIDTH = Dimensions.get('window').width;
export default class App extends Component {
constructor(props) {
super(props);
const flashcards = ['konichiwa','hi','genki desu','how are you'];
this.state = {
flashcards,
flipped: flashcards.map(() => false),
flipping: false
};
this.flipValue = new Animated.Value(0);
this.frontAnimatedStyle = {
transform: [{
rotateY: this.flipValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg']
})
}]
};
this.backAnimatedStyle = {
transform: [{
rotateY: this.flipValue.interpolate({
inputRange: [0, 1],
outputRange: ['180deg', '360deg']
})
}]
};
let xOffset = new Animated.Value(0);
this.onScroll = Animated.event(
[{ nativeEvent: { contentOffset: { x: xOffset } } }],
{ useNativeDriver: false }
);
this.transitionAnimations = this.state.flashcards.map((card, index) => ({
transform: [
{ perspective: 800 },
{
scale: xOffset.interpolate({
inputRange: [
(index - 1) * SCREEN_WIDTH,
index * SCREEN_WIDTH,
(index + 1) * SCREEN_WIDTH
],
outputRange: [0.25, 1, 0.25]
})
},
{
rotateX: xOffset.interpolate({
inputRange: [
(index - 1) * SCREEN_WIDTH,
index * SCREEN_WIDTH,
(index + 1) * SCREEN_WIDTH
],
outputRange: ['45deg', '0deg', '45deg']
})
},
{
rotateY: xOffset.interpolate({
inputRange: [
(index - 1) * SCREEN_WIDTH,
index * SCREEN_WIDTH,
(index + 1) * SCREEN_WIDTH
],
outputRange: ['-45deg', '0deg', '45deg']
})
}
]
}));
}
render() {
return (
<View style={styles.container}>
<Animated.ScrollView
scrollEnabled={!this.state.flipping}
scrollEventThrottle={16}
onScroll={this.onScroll}
horizontal
pagingEnabled
style={styles.scrollView}>
{this.state.flashcards.map(this.renderCard)}
</Animated.ScrollView>
</View>
);
}
renderCard = (question, index) => {
const isFlipped = this.state.flipped[index];
return (
<TouchableWithoutFeedback key={index} onPress={() => this.flipCard(index)}>
<View>
<View style={styles.scrollPage}>
<View>
{(this.state.flipping || !isFlipped) && <Animated.View
style={[
this.state.flipping ? this.frontAnimatedStyle : this.transitionAnimations[index],
styles.screen
]}
>
<Text style={styles.text}>{this.state.flashcards[index]}</Text>
</Animated.View>}
{(this.state.flipping || isFlipped) && <Animated.View
style={[
styles.screen,
this.state.flipping ? this.backAnimatedStyle : this.transitionAnimations[index],
this.state.flipping && styles.back
]}
>
<Text style={styles.text}>{this.state.flashcards[index+1]}</Text>
</Animated.View>}
</View>
</View>
<View style={styles.iconStyle}>
<TouchableOpacity>
<EvilIcons name="check" size={80} color={'#5CAF25'} />
</TouchableOpacity>
<TouchableOpacity>
<MaterialIcons name="cancel" size={70} color={'#b71621'} />
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
);
}
flipCard = index => {
if (this.state.flipping) return;
let isFlipped = this.state.flipped[index];
let flipped = [...this.state.flipped];
flipped[index] = !isFlipped;
this.setState({
flipping: true,
flipped
});
this.flipValue.setValue(isFlipped ? 1: 0);
Animated.spring(this.flipValue, {
toValue: isFlipped ? 0 : 1,
friction: 8,
tension: 10
}).start(() => {
this.setState({ flipping: false });
});
}
}
const styles = StyleSheet.create({
container: {
backgroundColor:'red',
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between'
},
scrollView: {
flexDirection: 'row',
backgroundColor: 'black'
},
scrollPage: {
width: SCREEN_WIDTH,
padding: 20
},
screen: {
height: 400,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 25,
backgroundColor: 'white',
width: SCREEN_WIDTH - 20 * 2,
backfaceVisibility: 'hidden'
},
text: {
fontSize: 45,
fontWeight: 'bold'
},
iconStyle: {
flexDirection: 'row',
justifyContent: 'center'
},
back: {
position: 'absolute',
top: 0,
backfaceVisibility: 'hidden'
}
});