简体   繁体   中英

How to transform one svg path to another with animation in React Native?

I want to change the SVG path or the shape from curve to rectangular whenever the user click on the text input. How to do it with animation or transition from curve to rectangular shape? So that the curve go up until it becomes a line. I am able to do it but without animation. like the images below:

在此处输入图片说明

在此处输入图片说明

Here is my JSX code:

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>
  );
};

Ok, so I have a way to do it in vanilla JavaScript - Demo: https://codepen.io/Alexander9111/pen/povqpaG

I have never used React Native before, so I can't give you the exact code for that, but I figure the concept is the most important thing?

My HTML looks like this:

<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>

And JavaScript like this:

// 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);
});

In summary, I changed your svg path shape a little bit to make it simpler (but its the same shape) - I used the simpler version of the Bezier curve, the quadratic one - https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Bezier_Curves

The other type of Bezier curve, the quadratic curve called with Q, is actually a simpler curve than the cubic one

Then this allows me to describe the curve quite simply with an array:

const before = ['M', [0,0], 'V', [200], 'Q', [395, 370], ' ', [WIDTH, 120], 'V', [0], 'Z'];

Which over time transitions to:

const after = ['M', [0,0], 'V', [200], 'Q', [180, 200], ' ', [WIDTH, 200], 'V', [0], 'Z'];

The difference between the two is:

const diff = ['M', [0,0], 'V', [0], 'Q', [-145, -170], ' ', [0, 80], 'V', [0], 'Z'];

Which means I can take a fraction of this diff and add it to before and we will move slowly to the end result desired (after).

The rest is just setting up a setInterval in order to fire the function every 10 milliseconds to slowly transition the shape to the final outcome. I am not sure if you would use the same setInterval etc. in ReactNative?

This results in the movement between curved and flat when you click the "forwards" button and between flat and curved when you click the "backwards" button. In your example, you would instead fire the associated functions on some kind of onFocus event etc.?

Example:

在此处输入图片说明

(Note: the black lines are just paths that are there are just guidelines to show how this transition is happening)

UPDATE USING SMIL

There is also the option to use SMIL (Synchronized Multimedia Integration Language - http://www.w3.org/TR/REC-smil ) - I am not 100% certain if this works in React-native but I would assume so.

Demo: https://codepen.io/Alexander9111/pen/MWYRzNp

Then we can simplify our code. We just require first to add one animate element tag to our HTML, which describes the animation:

<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>

And then the JavaScript is simply this:

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);

We only really need to capture the from="..." and to="..." attributes so that we can flip them around when going in the reverse direction as well as capturing the current d="..." attribute of the path. This is needed if we start the transition before the other is finished to ensure we don't get a jumpy animation restarting its position etc. Then finally just attach these functions to the click/focus events of the input/buttons and we're done!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM