javascript - 如何在 React Native 中使用动画将一个 svg 路径转换为另一个?
问题描述
每当用户单击文本输入时,我想将 SVG 路径或形状从曲线更改为矩形。如何用动画或从曲线到矩形的过渡来做到这一点?使曲线向上直到它变成一条线。我可以做到,但没有动画。像下面的图片:
这是我的 JSX 代码:
return (
<KeyboardAvoidingView style={{ flex: 1 }} behavior="padding" enabled>
<View style={styles.container}>
<Svg height={280} width={WIDTH}>
<Path
d= { numInputTouched? "M0 0 V200 C0 200, 400 400, 0" + WIDTH + ", 120 V0 ":"M0 0 V200 H" + WIDTH + ", V0"}
//"M0 0 V200 H" + WIDTH + ", V0"
// d={"M0 0 V200 C0 200, 400 400, 0" + WIDTH + ", 120 V0"}
fill={Colors.primary}
stroke={Colors.primary}
/>
<View style={styles.titleContaier}>
<Text style={styles.title}>
اشتراك / تسجيل الدخول{"\n"}
<Text style={styles.subTitle}>ادخل رقم موبايلك</Text>
</Text>
</View>
</Svg>
<Text style={styles.SMSText}>ستصلك رسالة قصيرة تحتوي على رمز</Text>
<View style={{}}>
<View style={styles.numberInputContainer}>
<View style={styles.numberInput}>
<View style={{}}>
<Text style={styles.numberTextFixed}>07</Text>
</View>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[1]}
onKeyPress={textHandler(1)}
keyboardType="number-pad"
onChangeText={text => setText1(text)}
onTouchStart={() => setNumInputTouched(true)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[2]}
onKeyPress={textHandler(2)}
onChangeText={text => setText2(text)}
keyboardType="number-pad"
value={text2}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[3]}
onKeyPress={textHandler(3)}
keyboardType="number-pad"
onChangeText={text => setText3(text)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[4]}
onKeyPress={textHandler(4)}
keyboardType="number-pad"
onChangeText={text => setText4(text)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[5]}
onKeyPress={textHandler(5)}
keyboardType="number-pad"
onChangeText={text => setText5(text)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[6]}
onKeyPress={textHandler(6)}
keyboardType="number-pad"
onChangeText={text => setText6(text)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[7]}
onKeyPress={textHandler(7)}
keyboardType="number-pad"
onChangeText={text => setText7(text)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[8]}
onKeyPress={textHandler(8)}
keyboardType="number-pad"
onChangeText={text => setText8(text)}
/>
<TextInput
style={styles.numberText}
placeholder="X"
maxLength={1}
ref={textInput[9]}
onKeyPress={textHandler(9)}
keyboardType="number-pad"
onChangeText={text => setText9(text)}
/>
</View>
</View>
<View>
<TouchableOpacity
style={{ alignItems: "flex-end", width: "85%", marginTop: 50 }}
>
<View style={styles.buttonContainer}>
<Ionicons
name="ios-arrow-round-forward"
size={45}
color="white"
style={styles.icon}
/>
</View>
</TouchableOpacity>
</View>
</View>
</View>
</KeyboardAvoidingView>
);
};
解决方案
好的,所以我有办法在香草 JavaScript 中做到这一点 - 演示:https ://codepen.io/Alexander9111/pen/povqpaG
我以前从未使用过 React Native,所以我不能给你确切的代码,但我认为这个概念是最重要的?
我的 HTML 如下所示:
<button id="forwards">Forwards</button>
<button id="backwards">Backwards</button>
<Svg height="280" width="400">
<Path id="target" d="M0 0 V200 C0 200, 400 400, 0400, 120 V0 " fill="#85ffda" stroke="#85ffda"/>
<g transform="translate(0,00)">
<path d="M0 0 V200 Q 395 370 400 120 V0 Z" stroke-width="1" stroke="black" fill="transparent"/>
<path d="M0 0 V200 Q 300 300 400 150 V0 Z" stroke="black" fill="transparent"/>
<path d="M0 0 V200 Q 250 250 400 175 V0 Z" stroke="black" fill="transparent"/>
<path d="M0 0 V200 Q 250 200 400 200 V0 Z" stroke="black" fill="transparent"/>
</g>
</svg>
像这样的JavaScript:
// d= { numInputTouched? "M0 0 V200 C0 200, 400 400, 0" + WIDTH + ", 120 V0 ":"M0 0 V200 H" + WIDTH + ", V0"}
// //"M0 0 V200 H" + WIDTH + ", V0"
// // d={"M0 0 V200 C0 200, 400 400, 0" + WIDTH + ", 120 V0"}
const WIDTH = 400;
const svg = document.querySelector("svg");
svg.setAttribute('width', WIDTH);
const before = ['M', [0,0], 'V', [200], 'Q', [395, 370], ' ', [WIDTH, 120], 'V', [0], 'Z'];
const after = ['M', [0,0], 'V', [200], 'Q', [180, 200], ' ', [WIDTH, 200], 'V', [0], 'Z'];
const diff = ['M', [0,0], 'V', [0], 'Q', [-145, -170], ' ', [0, 80], 'V', [0], 'Z'];
console.log(before.join(' '))
// const target = document.getElementById("target");
// target.setAttribute('d', "M0 0 V200 H" + WIDTH + ", V0");
// target.setAttribute('d', "M0 0 V200 C0 200, 400 400, 0" + WIDTH + ", 120 V0 ");
target.setAttribute('d', before.join(' '));
var current = ['M', [0,0], 'V', [200], 'Q', [395, 370], ' ', [400, 120], 'V', [0], 'Z'];
function transition(i) {
current[1][0] = before[1][0] + Math.round((i/100)*diff[1][0],0);
current[1][1] = before[1][1] + Math.round((i/100)*diff[1][1],0);
current[3][0] = before[3][0] + Math.round((i/100)*diff[3][0],0);
current[5][0] = before[5][0] + Math.round((i/100)*diff[5][0],0);
current[5][1] = before[5][1] + Math.round((i/100)*diff[5][1],0);
current[7][0] = before[7][0] + Math.round((i/100)*diff[7][0],0);
current[7][1] = before[7][1] + Math.round((i/100)*diff[7][1],0);
target.setAttribute('d', current.join(' '));
}
console.log(current.join(' '));
var progress = 0;
var timeInterval; //to be started at a later time
function myTimerForward() {
console.log(progress);
if (progress == 100) {
clearInterval(timeInterval);
} else {
progress += 1;
transition(progress);
}
}
function myTimerBackward() {
console.log(progress);
if (progress == 0) {
clearInterval(timeInterval);
} else {
progress -= 1;
transition(progress);
}
}
document.querySelector("#forwards").addEventListener('click', function() {
timeInterval = setInterval(myTimerForward, 10);
});
document.querySelector("#backwards").addEventListener('click', function() {
timeInterval = setInterval(myTimerBackward, 10);
});
总之,我稍微更改了您的 svg 路径形状以使其更简单(但形状相同) - 我使用了贝塞尔曲线的更简单版本,即二次曲线 - https://developer.mozilla.org/en- US/docs/Web/SVG/Tutorial/Paths#Bezier_Curves
另一种贝塞尔曲线,用 Q 称为二次曲线,实际上是比三次曲线更简单的曲线
然后这允许我用一个数组非常简单地描述曲线:
const before = ['M', [0,0], 'V', [200], 'Q', [395, 370], ' ', [WIDTH, 120], 'V', [0], 'Z'];
随着时间的推移过渡到:
const after = ['M', [0,0], 'V', [200], 'Q', [180, 200], ' ', [WIDTH, 200], 'V', [0], 'Z'];
两者的区别在于:
const diff = ['M', [0,0], 'V', [0], 'Q', [-145, -170], ' ', [0, 80], 'V', [0], 'Z'];
这意味着我可以提取这个差异的一小部分并将其添加到之前,我们将缓慢地移动到所需的最终结果(之后)。
剩下的只是设置一个 setInterval 以便每 10 毫秒触发一次函数,以缓慢地将形状转换为最终结果。我不确定你是否会在 ReactNative 中使用相同的 setInterval 等?
当您单击“前进”按钮时,这会导致在弯曲和平坦之间移动,当您单击“后退”按钮时,会导致在平坦和弯曲之间移动。在您的示例中,您会改为在某种 onFocus 事件等上触发相关函数?
例子:
(注意:黑线只是路径,它们只是显示这种过渡如何发生的指导方针)
使用 SMIL 更新
还可以选择使用 SMIL(同步多媒体集成语言 - http://www.w3.org/TR/REC-smil) - 我不能 100% 确定这是否适用于 React-native,但我会假设是这样。
演示:https ://codepen.io/Alexander9111/pen/MWYRzNp
然后我们可以简化我们的代码。我们只需要首先在我们的 HTML 中添加一个动画元素标签,它描述了动画:
<Svg height="400" width="400">
<Path id="target" d="M0 0 V200 Q 395 370 400 120 V0 Z" fill="#85ffda" stroke="#85ffda">
<animate
attributeName="d"
from="M0 0 V200 Q 395 370 400 120 V0 Z"
to="M0 0 V200 Q 180 200 400 200 V0 Z"
dur="0.5s" repeatCount="1" begin="indefinite" fill="freeze" />
</path>
...
</svg>
然后 JavaScript 就是这样的:
const target = document.querySelector("#target");
const animate_el = document.querySelector("#target > animate");
const from = animate_el.getAttribute("from");
const to = animate_el.getAttribute("to");
var d;
function forwards(){
d = target.getAttribute("d");
animate_el.setAttribute("from", d);
animate_el.setAttribute("to", to);
animate_el.beginElement();
}
function backwards(){
d = target.getAttribute("d");
animate_el.setAttribute("from", d);
animate_el.setAttribute("to", from);
animate_el.beginElement();
}
document.querySelector("#forwards").addEventListener('click', forwards);
document.querySelector("#backwards").addEventListener('click', backwards);
document.querySelector("#input_div > input").addEventListener('focus', forwards);
document.querySelector("#input_div > input").addEventListener('blur', backwards);
我们只需要捕获from="..."
和to="..."
属性,以便在反向行驶时可以翻转它们以及捕获d="..."
路径的当前属性。如果我们在另一个完成之前开始过渡,这是需要的,以确保我们不会得到一个重新开始其位置的跳跃动画等。最后只需将这些函数附加到输入/按钮的单击/焦点事件,我们就完成了!
推荐阅读
- spring - 在 Spring Cloud Gateway 中自定义请求
- jquery - 通过经典asp页面获取数据时如何将数据处理到我的数据表中
- sql - SQL 语句耗时较长
- pandas - 将带参数的函数应用于熊猫数据框
- r - 使用 purrr 抓取多个页面时出错
- laravel - Laravel 立即保存具有关系的模型
- mysql - 如何组合三个表中的列并根据月份将它们显示在一行中?
- django - 访问 'localhost:8080/signin' 给我错误代码:SSL_ERROR_RX_RECORD_TOO_LONG
- ios - 如何对 dispatch_group_leave 崩溃进行故障排除
- python - python 太快了,gnuplot 无法完成它的工作