首页 > 解决方案 > 如何在 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>
  );
};

标签: javascriptreactjsreact-nativesvgjsx

解决方案


好的,所以我有办法在香草 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="..."路径的当前属性。如果我们在另一个完成之前开始过渡,这是需要的,以确保我们不会得到一个重新开始其位置的跳跃动画等。最后只需将这些函数附加到输入/按钮的单击/焦点事件,我们就完成了!


推荐阅读