Как преобразовать один путь SVG в другой с анимацией в React Native? - PullRequest
2 голосов
/ 20 января 2020

Я хочу изменить путь SVG или форму с кривой на прямоугольную angular всякий раз, когда пользователь нажимает на ввод текста. Как это сделать с помощью анимации или перехода от кривой к прямоугольной angular форме? Так что кривая go вверх, пока не станет прямой. Я могу сделать это, но без анимации. как изображения ниже:

enter image description here

enter image description here

Вот мой код 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>
  );
};

1 Ответ

1 голос
/ 22 января 2020

Хорошо, у меня есть способ сделать это в ванили 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, чтобы сделать ее более простой (но такой же формы) - я использовал более простую версию кривой Безье, квадратичную c one - https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Bezier_Curves

Другой тип кривой Безье, квадратичная c кривая, называемая Q, на самом деле является более простой кривой, чем cubi c one

Тогда это позволяет мне описать кривую довольно просто с помощью массива:

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'];

Это означает, что я могу взять часть этой разницы и добавить ее до, и мы будем медленно двигаться к желаемому конечному результату (af ter).

Остальное - просто установить setInterval для запуска функции каждые 10 миллисекунд для медленного перехода формы к конечному результату. Я не уверен, что вы бы использовали тот же setInterval et c. в ReactNative?

Это приводит к перемещению между изогнутым и плоским при нажатии кнопки «вперед» и между плоским и изогнутым при нажатии кнопки «назад». В вашем примере вы вместо этого запускаете связанные функции для какого-либо события onFocus et c.?

Пример:

enter image description here

(Примечание: черные линии - это просто пути, и есть только рекомендации, показывающие, как происходит этот переход)

ОБНОВЛЕНИЕ С ИСПОЛЬЗОВАНИЕМ SMIL

Существует также возможность использовать SMIL (язык синхронизации синхронизированных мультимедиа - http://www.w3.org/TR/REC-smil) - я не уверен на 100%, работает ли это в React-native, но я бы предположил, что так.

Демонстрация: https://codepen.io/Alexander9111/pen/MWYRzNp

Тогда мы можем упростить наш код. Нам просто нужно сначала добавить один тег animate element к нашему 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="..." пути. Это необходимо, если мы начнем переход до того, как закончится другой, чтобы гарантировать, что мы не получим скачкообразную анимацию, перезапускающую свою позицию и т. Д. c. Затем, наконец, просто прикрепите эти функции к событиям щелчка / фокуса ввода / кнопок, и все готово!

...