इस पेज का अनुवाद मशीन द्वारा किया गया है और इसमें त्रुटियाँ हो सकती हैं। मूल अंग्रेज़ी पढ़ें

दस्तावेज़

Animation

NSH में हर एनिमेशन UI thread पर चलता है। आप उसे Rust या TSX से एक बार बयान करते हैं, और प्लेटफ़ॉर्म उसे फ़्रेम-दर-फ़्रेम चलाता है (iOS पर Core Animation और display link, Android पर UI-thread animator), आपके कोड को वापस बुलाए बिना। व्यस्त engine thread किसी एनिमेशन को अटकने नहीं दे सकता, और एनिमेशन के चलने के दौरान आपके कोड की कोई लागत नहीं आती।

guide-animation.tsx
guide-animation.tsxLive
import { mount, signal, Text, View } from "nsh";
import { reduceMotion } from "nsh";
import { Animated, createAnimatedValue, Easing, LayoutAnimation } from "nsh";
import { button, buttonText, desktop, focusRing, Inspector, Panel, size, Stage, ui, wide } from "./palette";

const TRACK = 250;
// Read when they render, so they follow the theme.
const palette = () => [ui().down, ui().warn, ui().accent, ui().up, ui().info];
const SWATCHES = 5;

function Chip(props: { label: string; onPress: () => void }) {
  return (
    <View onPress={props.onPress} {...button()}>
      <Text style={buttonText()}>{props.label}</Text>
    </View>
  );
}

function App() {
  // 1. A style transition: change the style, the platform animates it.
  const on = signal(false);

  // 2. Animated values: timing and spring, driven on the UI thread.
  const timed = createAnimatedValue(0);
  const sprung = createAnimatedValue(0);
  let there = false;
  const run = () => {
    there = !there;
    timed.timing(there ? TRACK : 0, { duration: 700, easing: Easing.inOut(Easing.cubic) });
    sprung.spring(there ? TRACK : 0, { stiffness: 180, damping: 9 });
  };

  // 3. A composite: a staggered hop, then all of them spin; runs natively end to end.
  const ys = Array.from({ length: SWATCHES }, () => createAnimatedValue(0));
  const spin = createAnimatedValue(0);
  const status = signal("idle");
  const show = Animated.sequence([
    Animated.stagger(
      70,
      ys.map((y) =>
        Animated.sequence([
          Animated.timing(y, { toValue: -30, duration: 200, easing: Easing.out(Easing.quad) }),
          Animated.spring(y, { toValue: 0, stiffness: 300, damping: 12 }),
        ]),
      ),
    ),
    Animated.timing(spin, { toValue: 1, duration: 600, easing: Easing.inOut(Easing.cubic) }),
  ]);
  const play = () => {
    status.set("running…");
    show.reset();
    show.start(({ finished }) => status.set(finished ? "finished" : "stopped"));
  };

  // 4. Layout animation: the next commit's frame changes animate.
  const stacked = signal(false);

  const heading = (title: string) => <Text style={{ fontWeight: "600", color: ui().text }}>{title}</Text>;
  const transition = () => (
      <View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
        <View
          onPress={() => on.set(!on())}
          style={{
            width: 64,
            height: 64,
            borderRadius: on() ? 32 : 12,
            backgroundColor: on() ? ui().info : ui().accent,
            opacity: on() ? 0.6 : 1,
            rotate: on() ? "45deg" : "0deg",
            transitionProperty: "all",
            transitionDuration: reduceMotion() ? 0 : 400,
            transitionTimingFunction: "ease-in-out",
          }}
          hoverStyle={{ opacity: 0.88 }}
          focusStyle={focusRing()}
          activeStyle={{ opacity: 0.7 }}
        />
        <View class="size-10 rounded-full border-4 animate-spin motion-reduce:animate-none" style={{ borderColor: ui().border, borderTopColor: ui().accent }} />
        <View class="size-10 rounded-full animate-pulse motion-reduce:animate-none" style={{ backgroundColor: ui().up }} />
        <Text style={{ flex: 1, color: ui().muted, fontSize: 13 }}>
          {desktop ? "Click the square." : "Tap the square."} animate-spin / animate-pulse run natively.
        </Text>
      </View>
  );
  const tracks = () => (
      <>
        <View style={{ width: 20, height: 20, borderRadius: 10, backgroundColor: ui().accent, translate: [timed, 0] }} />
        <View style={{ width: 20, height: 20, borderRadius: 4, backgroundColor: ui().info, translate: [sprung, 0] }} />
        <View
          style={{
            height: 8,
            borderRadius: 4,
            // One value, interpolated into a color and a width fraction.
            backgroundColor: timed.interpolate({ inputRange: [0, TRACK], outputRange: [ui().border, ui().up] }),
            scale: [timed.interpolate({ inputRange: [0, TRACK], outputRange: [0.1, 1] }), 1],
            transformOrigin: "left",
            width: wide() ? TRACK + 20 : undefined,
          }}
        />
      </>
  );
  const move = () => <Chip label="Move" onPress={run} />;
  const swatches = () => (
      <View style={{ flexDirection: "row", justifyContent: "center", alignItems: "flex-end", gap: 10, height: 64 }}>
        <Each of={palette()}>
          {(color, i) => (
            <View
              style={{
                width: 32,
                height: 32,
                borderRadius: 8,
                backgroundColor: color(),
                translate: [0, ys[i()]],
                rotate: spin.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "180deg"] }),
              }}
            />
          )}
        </Each>
      </View>
  );
  const playback = () => (
      <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
        <Chip label="Play" onPress={play} />
        <Chip label="Stop" onPress={() => show.stop()} />
        <Text style={{ color: ui().muted, fontSize: desktop ? 13 : undefined }}>{status()}</Text>
      </View>
  );
  const bars = () => (
      <View style={{ flexDirection: stacked() ? "column" : "row", gap: 8 }}>
        <Each of={palette()}>
          {(color) => <View style={{ height: stacked() ? 18 : 40, flex: stacked() ? undefined : 1, borderRadius: 6, backgroundColor: color() }} />}
        </Each>
      </View>
  );
  const stack = () => (
      <Chip
        label={stacked() ? "Back to a row" : "Stack them"}
        onPress={() => {
          LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
          stacked.set(!stacked());
        }}
      />
  );

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 16, paddingTop: 24, gap: 14 }}>
      {() =>
        wide() ? (
          <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
            <Stage width={520} gap={14}>
              {heading("Transition (style changes animate)")}
              {transition()}
              {heading("timing and spring")}
              <View style={{ gap: 8 }}>{tracks()}</View>
              {heading("Animated.sequence / stagger")}
              {swatches()}
              {heading("LayoutAnimation")}
              {bars()}
            </Stage>
            <Inspector>
              <Panel title="timing · spring">
                <View style={{ paddingTop: 6 }}>{move()}</View>
              </Panel>
              <Panel title="Animated.sequence · stagger">
                <View style={{ paddingTop: 6 }}>{playback()}</View>
              </Panel>
              <Panel title="LayoutAnimation.configureNext">
                <View style={{ paddingTop: 6 }}>{stack()}</View>
              </Panel>
            </Inspector>
          </View>
        ) : (
          <>
            {heading("Transition (style changes animate)")}
            {transition()}
            {heading("timing and spring")}
            <View style={{ gap: 8 }}>
              {tracks()}
              {move()}
            </View>
            {heading("Animated.sequence / stagger")}
            {swatches()}
            {playback()}
            {heading("LayoutAnimation")}
            {bars()}
            {stack()}
          </>
        )
      }
    </View>
  );
}

mount(App);

छह टूल हैं, सबसे तैयार से लेकर सबसे लचीले तक:

टूलकिसके लिए
Motionऐसा view जो आता है, जाता है, ध्यान खींचता है या लूप में चलता है: तैयार motions (fadeInUp, shake, pulse, …) या आपके अपने keyframes, उसकी opacity और transform पर।
Transitionsऐसा style बदलाव जो झटके से बदलने के बजाय एनिमेट होना चाहिए: कोई रंग, opacity, transform।
Keyframe animationsस्पिनर, धड़कता हुआ प्लेसहोल्डर, उछलता हुआ संकेत।
Keyframesकोई भी एनिमेटेड मान keyframes के ज़रिए, जैसे CSS उन्हें चलाता है: offsets, हर खंड के लिए easing, iterations और direction।
Animated valuesसमय के साथ किसी मान से चलने वाली कोई भी चीज़: springs, gestures, scroll position, sequences।
Layout animationsअपडेट के बाद आकार और स्थिति के बदलाव: items का जुड़ना, हटना, क्रम बदलना, पैनल का फैलना।

#Motion

Motion किसी view की opacity और transform को keyframes के ज़रिए, native रूप से चलाता है। उसे उस component में बनाएँ जो view का मालिक है (Rust में motion(), TSX में nsh से createMotion()), उसे view से bind करें, और उस पर कोई तैयार motion या अपना motion चलाएँ। enter view को वहाँ से शुरू करता है जहाँ motion शुरू होता है और उसे तुरंत चला देता है, ताकि view अपने पहले फ़्रेम से ही आए।

#[component]
pub fn App() -> impl IntoElement {
    let card = motion().entering(MotionPreset::FadeInUp);
    view().flex_1().items_center().justify_center().child(
        view()
            .size(px(120.0))
            .rounded(px(16.0))
            .bg(rgb(0x0969da))
            .bind_motion(&card)
            .on_press(move || {
                card.play(MotionPreset::Shake);
            }),
    )
}

Motion में हर channel के लिए एक animated value होता है: opacity (0..1), translateX और translateY (points), rotate, rotateX और rotateY (डिग्री), scale, scaleX और scaleY। ये विश्राम अवस्था से शुरू होते हैं (opacity और scales 1, बाकी 0) और इसी क्रम में लागू होते हैं: पहले translations, फिर rotations, फिर scales, view के transform origin के सापेक्ष। Motion का अपना कोई रूप नहीं होता: वह उसी को चलाता है जिसे आपने style किया है।

Motion केवल opacity और transform को चलाता है। m.style को spread करने से view की opacity और transform सेट हो जाती हैं; अपनी opacity रखने के लिए केवल steps को bind करें (transform: m.transform)। रंग किसी animated value और interpolate से चलते हैं।

TSXRustविवरण
createMotion(options?)motion()ऐसा motion जिसका हर channel विश्राम अवस्था में है।
createMotion({ initial: { opacity: 0 } })m.value(MotionChannel::Opacity).set(0.0)जहाँ से channels शुरू होते हैं; बाकी विश्राम अवस्था से शुरू होते हैं।
createMotion({ enter, delay, duration, ... })motion().entering(spec)वहीं से शुरू होता है जहाँ enter शुरू होता है और उसे तुरंत चलाता है; अन्य विकल्प उसकी timing बदलते हैं। enter उन channels को सेट करता है जिन्हें वह चलाता है, initial के ऊपर।
style={m.style}, m.transform.bind_motion(&m)channels को bind करता है: m.style है { opacity, transform }, उसका transform सात steps की एक सूची है (scale को scaleX और scaleY में मिला दिया जाता है)। ये view के translate, rotate और scale के बाद लागू होते हैं; इनसे पहले का perspective 3D flip को गहराई देता है: transform: [{ perspective: 800 }, ...m.transform]। Rust में motion का transform view के अपने transform के बाद लागू होता है।
m.opacity, m.translateX, …m.value(MotionChannel::Opacity), …किसी एक channel का animated value, जिसे interpolate किया जा सकता है या हाथ से चलाया जा सकता है।
m.play(motion, options?)m.play(spec)कोई preset या अपना motion चलाता है; नया motion चल रहे motion को बीच में रोक देता है। पूरा होने पर { finished }, true के साथ resolve होता है (Rust में AnimationEnd)।
m.animation(motion, options?)m.animation(spec)वही motion एक composite animation के रूप में, बिना शुरू किए, ताकि दूसरों के साथ क्रम में, stagger करके या लूप में चलाया जा सके।
play(motion, { ignoreReduceMotion: true })m.play_ignoring_reduce_motion(spec)इसे तब भी चलाता है जब उपयोगकर्ता कम motion माँगता है।
m.stop()m.stop()उसे जहाँ है वहीं रोक देता है।
m.reset()m.reset()उसे रोकता है और हर channel को विश्राम अवस्था में लौटा देता है।

#Presets

तैयार motions डेटा हैं: nsh से motions (motions.fadeInUp वही है जो play("fadeInUp") चलाता है) और Rust में MotionPreset (MotionPreset::FadeInUp.spec())। दूरियाँ points में हैं, controls और cards के हिसाब से; बड़े views के लिए motion के मानों को scale करें या अपने keyframes लिखें। swing और अन्य rotations view के transform origin के सापेक्ष घूमते हैं: झूलते साइन को transformOrigin: "top" दें (.origin_top())।

Enter: ये छिपे हुए या दूर से शुरू होते हैं और विश्राम अवस्था में खत्म होते हैं।

TSXRustअवधिविवरण
fadeInMotionPreset::FadeIn300 msधीरे-धीरे दिखाई देता है।
fadeInUpMotionPreset::FadeInUp450 msधीरे-धीरे दिखाई देता है, ऊपर उठकर अपनी जगह आता है।
fadeInDownMotionPreset::FadeInDown450 msधीरे-धीरे दिखाई देता है, नीचे उतरकर अपनी जगह आता है।
fadeInLeftMotionPreset::FadeInLeft450 msधीरे-धीरे दिखाई देता है, बाईं ओर से आता है।
fadeInRightMotionPreset::FadeInRight450 msधीरे-धीरे दिखाई देता है, दाईं ओर से आता है।
zoomInMotionPreset::ZoomIn350 msधीरे-धीरे दिखाई देता है, 90 % से बढ़ता है।
popInMotionPreset::PopIn450 msउभरकर आता है: आधे आकार से अपने आकार के पार तक बढ़ता है और फिर टिक जाता है।
bounceInMotionPreset::BounceIn650 msउछलकर आता है, दो बार आगे निकल जाता है।
slideInUpMotionPreset::SlideInUp400 msनीचे से ऊपर सरकता है, बिना fade के (sheets, toasts)।
slideInDownMotionPreset::SlideInDown400 msऊपर से नीचे सरकता है, बिना fade के।
flipInXMotionPreset::FlipInX600 msअपनी क्षैतिज धुरी पर पलटकर आता है।
flipInYMotionPreset::FlipInY600 msअपनी ऊर्ध्वाधर धुरी पर पलटकर आता है।

Exit: ये विश्राम अवस्था से शुरू होते हैं और छिपे हुए या दूर जाकर खत्म होते हैं।

TSXRustअवधिविवरण
fadeOutMotionPreset::FadeOut250 msधीरे-धीरे गायब होता है।
fadeOutUpMotionPreset::FadeOutUp300 msधीरे-धीरे गायब होता है, ऊपर उठकर दूर जाता है।
fadeOutDownMotionPreset::FadeOutDown300 msधीरे-धीरे गायब होता है, नीचे उतरकर दूर जाता है।
fadeOutLeftMotionPreset::FadeOutLeft300 msधीरे-धीरे गायब होता है, बाईं ओर जाता है।
fadeOutRightMotionPreset::FadeOutRight300 msधीरे-धीरे गायब होता है, दाईं ओर जाता है।
zoomOutMotionPreset::ZoomOut250 msधीरे-धीरे गायब होता है, सिकुड़कर 90 % हो जाता है।
popOutMotionPreset::PopOut300 msउभरकर जाता है: थोड़ा फूलता है, फिर सिकुड़कर गायब हो जाता है।
slideOutDownMotionPreset::SlideOutDown300 msनीचे सरककर दृश्य से बाहर जाता है, बिना fade के।
slideOutUpMotionPreset::SlideOutUp300 msऊपर सरककर दृश्य से बाहर जाता है, बिना fade के।

Attention: ये विश्राम अवस्था में शुरू और खत्म होते हैं।

TSXRustअवधिविवरण
shakeMotionPreset::Shake500 msबगल में हिलता है: गलत पासवर्ड, अमान्य फ़ील्ड।
headShakeMotionPreset::HeadShake800 msसिर हिलाता है: एक नरम “नहीं”।
wiggleMotionPreset::Wiggle600 msअपने केंद्र के सापेक्ष डोलता है: कुछ ध्यान देने लायक।
swingMotionPreset::Swing900 msअपने ऊपरी किनारे से साइन की तरह झूलता है।
pulseMotionPreset::Pulse500 msथोड़ा बढ़कर वापस आता है: हल्का ज़ोर।
heartbeatMotionPreset::Heartbeat900 msदिल की तरह दो बार धड़कता है: लाइक, पसंदीदा।
rubberBandMotionPreset::RubberBand800 msरबर की तरह दबता और खिंचता है।
jelloMotionPreset::Jello800 msजेली की तरह थरथराता है।
tadaMotionPreset::Tada900 msसिकुड़ता है, फिर डोलते हुए बढ़ता है: एक इनाम।
jumpMotionPreset::Jump700 msकूदता है और दबकर उतरता है।
flashMotionPreset::Flash800 msदो बार बुझता और जलता है।

Loop: ये रोके जाने तक दोहराते रहते हैं।

TSXRustअवधिविवरण
spinMotionPreset::Spin1000 msएक बार घूमता है, हमेशा।
floatMotionPreset::Float1500 msऊपर-नीचे तैरता है, हमेशा।
breatheMotionPreset::Breathe1800 msसाँस लेता है: थोड़ा बढ़ता और फीका पड़ता है, हमेशा।
swayMotionPreset::Sway1600 msअपने केंद्र के सापेक्ष झूमता है, हमेशा।
bobMotionPreset::Bob1000 msउछलती गेंद की तरह ऊपर-नीचे होता है, हमेशा।
glowMotionPreset::Glow2000 msचमकता और मंद होता है, हमेशा।

play किसी preset के ऊपर उसके timing विकल्प लेता है (m.play("shake", { duration: 300 })), और preset को अपने motion में spread किया जा सकता है (m.play({ ...motions.fadeInUp, duration: 600 }))। Rust में preset का spec बदलें: MotionPreset::FadeInUp.spec().timing(|t| t.delay(100.0))।

#अपने खुद के motions

अपना motion हर channel के लिए keyframes और एक timing से बनता है (keyframes के विकल्प)। TSX में हर channel के keyframes लिखें ({ opacity: [0, 1] }), या frames की एक सूची जैसी Web Animations API में लिखी जाती है ([{ opacity: 0, translateY: 24 }, { opacity: 1, translateY: 0 }]), जिसमें हर frame के पास अपना offset और easing हो। frames की सूची में, बिना offset वाला frame वहीं बैठता है जहाँ सूची में उसकी स्थिति उसे रखती है, और कोई channel उन्हीं frames का अनुसरण करता है जो उसका नाम लेते हैं। कोण strings हो सकते हैं ("45deg", "0.5turn")। Rust में MotionSpec बनाएँ।

let rise = MotionSpec::new(KeyframeTiming::new(400.0))
    .values(MotionChannel::Opacity, [0.0, 1.0])
    .track(
        MotionChannel::TranslateY,
        [Keyframe::at(0.0, 24.0).ease(Easing::EaseOut), Keyframe::at(1.0, 0.0)],
    );
card.play(rise);

let flip = MotionSpec::new(KeyframeTiming::new(600.0).infinite())
    .values(MotionChannel::RotateY, [0.0, 180.0, 360.0]);
card.play(flip);

#Entrances और staggered सूचियाँ

हर पंक्ति को एक ऐसा motion दें जो अंदर आता है, और ऐसी delay के साथ जो उसके index के साथ बढ़ती है: जो पंक्तियाँ एक साथ दिखती हैं वे एक के बाद एक आती हैं। delay की एक सीमा रखें, ताकि सूची में बहुत नीचे जोड़ी गई पंक्ति को इंतज़ार न करना पड़े।

each(items, |item: &Item| item.id, |item, index| {
    let delay = index.get_untracked().min(10) as f32 * 60.0;
    let row = motion().entering(MotionPreset::FadeInUp.spec().timing(|t| t.delay(delay)));
    view()
        .rounded_xl()
        .bg(ZINC.shade(100))
        .p_4()
        .bind_motion(&row)
        .child(text(move || item.get().title))
})

Motions आपस में जुड़ भी सकते हैं: m.animation(..) एक motion है composite animation के रूप में, बिना शुरू किया हुआ, इसलिए Animated.stagger उनके एक समूह को native रूप से चलाता है, गिनती पहले item से, आख़िरी से, केंद्र से या किसी भी index से। उन्हें एक छिपा हुआ initial दें (या उन्हें वहीं से शुरू करें जहाँ motion शुरू होता है) ताकि अपनी बारी से पहले कुछ न दिखे।

let cards: Vec<Motion> = (0..5)
    .map(|_| {
        let m = motion();
        m.value(MotionChannel::Opacity).set(0.0);
        m
    })
    .collect();
Animation::stagger_from(60.0, StaggerFrom::Center, cards.iter().map(|m| m.animation(MotionPreset::FadeInUp))).start();

#Exits

exit चलाएँ, उसके खत्म होने की प्रतीक्षा करें, फिर view को हटाएँ। layout animation आसपास के views को खाली जगह भरने देता है।

let remove = move || {
    spawn(async move {
        row.play(MotionPreset::FadeOutLeft).await;
        animate_layout(LayoutAnimation::EASE_IN_EASE_OUT);
        items.update(|items| items.retain(|i| i.id != id));
    });
};

Motions उपयोगकर्ता की reduced motion सेटिंग का अपने आप पालन करते हैं: जब उपयोगकर्ता कम motion माँगता है, तो motion बिना हिले तुरंत अपने अंतिम स्थान पर पहुँच जाता है।

#Transitions

बताएँ कि क्या एनिमेट करना है और कितनी देर के लिए (Rust में .transition_all().duration(400.0), TSX में transitionProperty और transitionDuration), और उन properties में बाद के बदलाव native रूप से स्क्रीन पर मौजूद मान से नए मान तक एनिमेट होंगे।

#[component]
pub fn App() -> impl IntoElement {
    let on = signal(false);
    view().flex_1().items_center().justify_center().child(
        view()
            .on_press(move || on.update(|on| *on = !*on))
            .size(px(100.0))
            .rounded(move || px(if on.get() { 50.0 } else { 16.0 }))
            .bg(move || if on.get() { rgb(0x8250df) } else { rgb(0x0969da) })
            .rotate(move || deg(if on.get() { 45.0 } else { 0.0 }))
            .scale(move || if on.get() { 1.2_f32 } else { 1.0 })
            .transition_all()
            .duration(400.0)
            .ease_in_out(),
    )
}
StyleRustप्रकारविवरण
transitionProperty.transition(), .transition_all(), .transition_colors(), .transition_opacity(), .transition_transform(), .transition_shadow()stringक्या एनिमेट होता है, अल्पविराम से अलग: "all", "colors", "opacity", "transform", "box-shadow", "border-radius", …
transitionDuration.duration(ms)number | "150ms" | "0.2s"मिलीसेकंड में संख्या के रूप में, या CSS time।
transitionDelay.delay(ms)number | "150ms" | "0.2s"शुरू होने से पहले।
transitionTimingFunction.ease_linear(), .ease_in(), .ease_out(), .ease_in_out(), .ease_bezier(..)"linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out" | "cubic-bezier(…)"वक्र।

जो properties transition करती हैं, समूह के अनुसार: रंग (background, border, outline, shadow और text के रंग, gradient stops), opacity, transform, shadows (box-shadow) और आकृति (border radius और width)। CSS नाम और उनके camel-case रूप दोनों काम करते हैं (background-color, backgroundColor, transform, scale, …)। Layout बदलाव layout animations से एनिमेट होते हैं।

Tailwind के साथ वही चीज़ है transition (रंग, opacity, shadows और transforms), transition-all, transition-colors, transition-opacity, transition-transform, transition-shadow, साथ में duration-*, delay-* और ease-*। pressed state (.active(..), active:) के साथ मिलाने पर यह ऐसे pressed states देता है जो एनिमेट होते हैं:

view()
    .on_press(save)
    .rounded_xl()
    .bg(BLUE.shade(600))
    .px_5()
    .py_3()
    .transition()
    .duration(150.0)
    .active(|s| s.scale(0.95).bg(BLUE.shade(800)))
    .child(text("Save").font_weight(FontWeight::SEMIBOLD).text_color(white()))

Transition उस मान से शुरू होता है जो इस समय स्क्रीन पर है, इसलिए बीच में लक्ष्य फिर बदलने पर वह सहजता से मुड़ जाता है।

#Keyframe animations

चार अंतर्निहित keyframe animations native रूप से चलते हैं, डिफ़ॉल्ट रूप से हमेशा: spin (दक्षिणावर्त एक चक्कर), ping (2× तक बढ़ता है और फीका पड़ जाता है), pulse (आधी opacity तक फीका होकर वापस आता है) और bounce (अपनी ऊँचाई के चौथाई हिस्से जितना उछलता है और उतरता है)।

view()
    .size_8()
    .rounded_full()
    .border(px(4.0))
    .border_color(ZINC.shade(300))
    .border_t_color(BLUE.shade(600))
    .animate_spin()
view().h_4().w_40().rounded(px(4.0)).bg(ZINC.shade(200)).animate_pulse()
StyleRustप्रकारविवरण
animationName.animate_spin(), .animate_ping(), .animate_pulse(), .animate_bounce(), .animate_none(), या .animation(KeyframeAnimation::new(Keyframes::Spin))"none" | "spin" | "ping" | "pulse" | "bounce"एनिमेशन।
animationDurationKeyframeAnimation::duration(ms)number | "1s" | …एक iteration।
animationDelayKeyframeAnimation::delay(ms)number | "200ms" | …शुरू होने से पहले।
animationTimingFunctionKeyframeAnimation::easing([x1, y1, x2, y2])transitionTimingFunction की तरहवक्र।
animationIterationCountKeyframeAnimation::iterations(n)number | "infinite"डिफ़ॉल्ट "infinite"।

Tailwind: animate-spin, animate-ping, animate-pulse, animate-bounce, animate-none।

#Animated values

Animated value एक संख्या है जो UI thread पर रहती है। उसे बनाएँ (Rust में animated_value(..), TSX में nsh से createAnimatedValue), उसे styles से bind करें, और उसे एनिमेट करें; bind किए गए views हर फ़्रेम में बिना अपडेट के उसका अनुसरण करते हैं।

#[component]
pub fn App() -> impl IntoElement {
    let x = animated_value(0.0);
    let opacity = animated_value(1.0);
    let play = move || {
        spawn(async move {
            x.timing(240.0, 600.0, Easing::Curve(Curve::Cubic, EaseMode::Out)).await;
            opacity.timing(0.3, 200.0, Easing::EaseInOut).await;
            x.spring(0.0, Spring::default());
            opacity.timing_with(1.0, Timing::default());
        });
    };
    view()
        .flex_1()
        .justify_center()
        .p_6()
        .gap_6()
        .child(
            view()
                .size(px(60.0))
                .rounded(px(12.0))
                .bg(rgb(0x0969da))
                .bind_opacity(&opacity)
                .bind_translate_x(&x),
        )
        .child(view().on_press(play).child(text("Play").text_color(rgb(0x0969da))))
}

क्या bind किया जा सकता है: opacity (.bind_opacity(..)); backgroundColor, borderColor (हर तरफ़) और text का color (पूरा text), रंगों तक interpolation के ज़रिए (.bind_background(..), .bind_border_color(..), .bind_color(..)); translate ([x, y], हर axis के लिए एक मान), rotate (डिग्री) और scale (हर axis के लिए [x, y], या दोनों के लिए एक मान); और transform सूची के steps: translateX, translateY, scale, scaleX, scaleY, rotate, rotateX, rotateY और rotateZ (.bind_translate_x(..), .bind_translate_y(..), .bind_scale(..), .bind_rotate(..) डिग्री में, Rust में .bind_transform(kind, ..))। Shadows transitions से बदलते हैं। focus ring या हरे रंग में चमकने वाली कीमत, border या text के रंग से bind किया गया color interpolation है:

let flash = animated_value(0.0);
let color = flash.interpolate_colors(&[0.0, 1.0], &[rgb(0x1f2328), rgb(0x1a7f37)]);

text(move || price.get().to_string()).bind_color(&color)
// On a new price: flash.set(1.0); flash.timing(0.0, 600.0, Easing::EaseInOut);

Animated values canvas drawings को भी चलाते हैं, scroll offsets (scrollValue) और gestures (pan, pinch, rotation) प्राप्त करते हैं।

मान को उससे bind किए गए views और उसे बनाने वाला component जीवित रखते हैं, इसलिए आपके variables कैसे भी आएँ-जाएँ, binding जुड़ी रहती है। Animated values को उसी component में बनाएँ जो उनका मालिक है (module स्तर पर नहीं, जब हर instance को अपना चाहिए)।

#AnimatedValue मेथड

TSXRustविवरण
set(value)set(value)value पर सीधे पहुँच जाता है और चल रहे किसी भी animation को रोक देता है।
stop()stop()चल रहे animation को जहाँ है वहीं रोक देता है; TSX में यह { value } के साथ resolve होता है, यानी वहाँ के मान के साथ।
getValue()value()वर्तमान मान, जो UI thread पर पढ़ा जाता है जहाँ वह बदलता है (यह एक फ़्रेम के भीतर स्थिर हो जाता है): TSX में promise, Rust में await किया जा सकने वाला उत्तर।
setOffset(offset)set_offset(offset)मान जो दिखाता है उसमें offset जोड़ता है: bindings को value + offset दिखता है, जबकि set, animations और drags मान को ही बदलते हैं।
flattenOffset()flatten_offset()offset को मान में जोड़ देता है और offset को 0 कर देता है।
extractOffset()extract_offset()मान को offset में ले जाता है और मान को 0 कर देता है: इसके बाद drag 0 से शुरू होता है, पिछले drag ने जहाँ छोड़ा था उसके ऊपर।
timing(to, { duration?, easing?, delay? })timing(to, ms, easing), timing_with(to, Timing { .. })duration ms में to तक animate करता है (डिफ़ॉल्ट 300, easing "easeInOut")।
spring(to, { stiffness?, damping?, mass?, velocity? })spring(to, Spring { .. })spring की तरह to तक पहुँचता है (डिफ़ॉल्ट: stiffness 170, damping 26, mass 1)।
decay({ velocity?, deceleration?, clamp?, bounce? })decay(velocity, deceleration), decay_within(velocity, deceleration, (min, max), bounce)velocity (इकाई प्रति सेकंड) की गति से चलना शुरू करता है और धीमा होता जाता है (डिफ़ॉल्ट deceleration 0.998 प्रति ms)। [min, max] के clamp के साथ यह किसी सीमा पर रुक जाता है, या अपनी गति का bounce (0..1) हिस्सा बनाए रखते हुए उससे वापस लौट आता है।

हर animation एक उत्तर देता है जिसे आप await कर सकते हैं, कि वह पूरा हुआ या नहीं: true जब वह अंत तक चला, false जब किसी ने उसे रोक दिया (stop(), set(), या उसी मान पर नया animation)। यह TSX में Promise<{ finished: boolean }> है और Rust में AnimationEnd, जिसे कोई task bool के लिए await करता है। चलते हुए मान पर नया animation शुरू करने से पुराना animation सहजता से बाधित होता है: velocity के बिना दिया गया spring या decay मान की मौजूदा velocity से जारी रहता है।

// Spring settings: lower damping overshoots more, higher stiffness is faster.
x.spring(200.0, Spring { stiffness: 180.0, damping: 8.0, ..Spring::default() });   // wobbly
x.spring(200.0, Spring { stiffness: 400.0, damping: 30.0, ..Spring::default() });  // stiff, no overshoot

// A fling that coasts to a stop, from a pan's release velocity.
view().pan_values(Some(&x), None).on_pan_end(move |e: &PanEvent| {
    x.decay(Some(e.velocity_x), 0.998);
})

// A fling that stays on its track and bounces softly off its ends.
x.decay_within(Some(e.velocity_x), 0.998, (0.0, 300.0), 0.3);

// Where a value is now (it moves on the UI thread, so the answer is async).
let at = x.value().await;

#Keyframes

keyframes किसी मान को keyframes के ज़रिए वैसे ही चलाता है जैसे CSS keyframes चलते हैं: TSX में value.keyframes(frames, options), Rust में value.keyframes(frames, KeyframeTiming) (composite के एक step के रूप में Animated.keyframes(value, { keyframes, ... }) और Animation::keyframes(..))। keyframe एक संख्या है (या "90deg" जैसी angle string), जो बाकी के साथ समान दूरी पर फैलती है, या { offset, value, easing } (Rust में Keyframe::new(value), Keyframe::at(offset, value), .ease(easing))।

let y = animated_value(0.0);
y.keyframes(
    [Keyframe::new(0.0), Keyframe::at(0.3, -20.0).ease(Easing::EaseIn), Keyframe::new(0.0)],
    KeyframeTiming::new(600.0).easing(Easing::EaseOut).iterations(Some(3)),
);

// A clock hand that ticks once a second.
let hand = animated_value(0.0);
hand.keyframes(
    [Keyframe::new(0.0), Keyframe::new(360.0)],
    KeyframeTiming::new(60_000.0).easing(Easing::Steps(60, StepPosition::JumpEnd)).infinite(),
);
विकल्पRust (KeyframeTiming)डिफ़ॉल्टविवरण
durationKeyframeTiming::new(ms)300एक iteration, मिलीसेकंड में।
delay.delay(ms)0पहले iteration से पहले।
easing.easing(easing)CSS easeहर उस segment का curve जिसका अपना easing नहीं है।
iterations.iterations(Some(n)), .infinite()1कितने iterations, या "infinite"।
direction.direction(PlaybackDirection::Alternate)"normal""normal", "reverse", "alternate" या "alternate-reverse"।

ये CSS और Web Animations API की तरह चलते हैं:

  • easing हर segment को आकार देता है, पूरे को नहीं: keyframe का easing उस segment को आकार देता है जो उससे शुरू होता है, और timing का easing बाकी हर segment को।
  • बिना offset वाले keyframes अपने पड़ोसियों के बीच समान दूरी पर फैलते हैं; पहले का डिफ़ॉल्ट 0 और आख़िरी का 1 है।
  • हर iteration अपने पहले keyframe पर छलांग के साथ शुरू होता है। offset 0 पर keyframe न हो तो मान वहीं से चलता है जहाँ वह है।
  • delay के दौरान मान पहले से पहला keyframe धारण किए रहता है (CSS animation-fill-mode: backwards), इसलिए देर से शुरू होने वाली entrance विश्राम अवस्था वाले view के बजाय अपना पहला फ़्रेम दिखाती है। यह motions पर भी लागू होता है।
  • reverse और alternate के पीछे की ओर वाले iterations segments को उलटे क्रम में चलाते हैं, हर एक का curve उलटकर।
  • मान वहीं रहता है जहाँ आख़िरी iteration उसे छोड़ता है। उत्तर बताता है कि वे पूरे हुए या नहीं: पूरे होने पर true, किसी के रोकने पर false।

#Interpolation

interpolate किसी मान को piecewise linear segments के ज़रिए संख्याओं, रंगों या कोणों में map करता है, और एक नया node लौटाता है जिसे आप bind कर सकते हैं। एक मान कई properties चला सकता है। Rust में interpolate(&input, &output) संख्याओं में, interpolate_colors(&input, &colors) रंगों में map करता है, और interpolate_with(&input, output, Interpolation { .. }) extrapolation और easing लेता है; TSX में value.interpolate({ inputRange, outputRange }) ये सब लेता है।

let t = animated_value(0.0);

view()
    .bind_background(&t.interpolate_colors(&[0.0, 1.0, 2.0], &[rgb(0x54aeff), rgb(0x8250df), rgb(0xcf222e)]))
    .bind_opacity(&t.interpolate(&[0.0, 1.0], &[0.4, 1.0]))
    .bind_rotate(&t.interpolate(&[0.0, 1.0], &[0.0, 180.0]))
    .bind_scale(&t.interpolate_with(
        &[0.0, 1.0],
        AnimationOutput::Numbers(vec![0.6, 1.2]),
        Interpolation {
            easing: Easing::Curve(Curve::Back(1.70158), EaseMode::Out),
            ..Interpolation::default()
        },
    ))
विकल्पRustविवरण
inputRangeinput sliceबढ़ते क्रम में input stops।
outputRangeoutput slice, या AnimationOutputहर stop के लिए एक output: संख्याएँ, रंग ("#rrggbb", "rgba(…)", नामित) या कोण ("45deg", "1rad")।
extrapolateInterpolation::extrapolate(Extrapolate::Extend)input सीमा के बाहर: "clamp" (डिफ़ॉल्ट), "extend" (अंतिम segments को जारी रखता है) या "identity" (input को ज्यों का त्यों पास करता है)।
extrapolateLeft, extrapolateRightextrapolate_left, extrapolate_rightवही, एक तरफ़ के लिए।
easingeasingहर segment को आकार देता है।

#अंकगणित और diffClamp

Nodes अंकगणित से भी जुड़ सकते हैं, और यह भी natively चलता है: a.add(b), a.sub(b), a.mul(b), a.div(b), a.mod(b) (Rust में a.modulo(b); धनात्मक b के लिए कभी ऋणात्मक नहीं, जैसे 0..360 में कोण), जहाँ b एक node या संख्या है। TSX में Animated.add, subtract, multiply, divide और modulo functions के रूप में यही काम करते हैं।

diffClamp(min, max) (diff_clamp) node के बदलावों का अनुसरण करता है और min..max के भीतर रहता है। scroll offset पर लगाने पर यह ऐसा header बनाता है जो content के ऊपर scroll होते ही छिप जाता है और नीचे scroll होते ही वापस आ जाता है:

let offset = animated_value(0.0);
let header = offset.diff_clamp(0.0, 56.0);

scroll().flex_1().scroll_value(&offset)
view().absolute().top_0().left_0().right_0().h(px(56.0)).bind_translate_y(&header.mul(-1.0))

{ snap: true } (diff_clamp_with(min, max, DiffClamp::snapping())) के साथ, scroll रुकने पर आंशिक रूप से दिखता छूटा हुआ header उस गति को पूरा कर देता है: वह UI thread पर 200 ms में पूरी तरह दिखाई देने या पूरी तरह छिपने की स्थिति में आ जाता है, जो भी निकट हो। वह तभी छिपता है जब page कम से कम उसकी ऊँचाई जितना scroll हो चुका हो, इसलिए उससे कम scroll हुए page में पूरा header दिखता है। गति के दौरान फिर से scroll करने पर नियंत्रण वहीं से लिया जाता है जहाँ header है।

let header = offset.diff_clamp_with(0.0, 56.0, DiffClamp::snapping());

#संयुक्त (composite) animations

Animations steps से बनते हैं (Rust में Animation, TSX में nsh से Animated) और पूरा tree natively चलता है: हर step UI thread पर उसी क्षण शुरू होता है जब पिछला खत्म होता है, बिना किसी अंतराल के और बीच में आपके किसी code के बिना।

let ys: Vec<AnimatedValue> = (0..5).map(|_| animated_value(0.0)).collect();
let fade = animated_value(1.0);

let hop = Animation::stagger(
    80.0,
    ys.iter().map(|y| {
        Animation::sequence([
            Animation::timing(y, -30.0, Timing::new(200.0, Easing::Curve(Curve::Quad, EaseMode::Out))),
            Animation::spring(y, 0.0, Spring { stiffness: 300.0, damping: 12.0, ..Spring::default() }),
        ])
    }),
);

let breathe = Animation::sequence([
    Animation::timing(&fade, 0.3, Timing::new(400.0, Easing::EaseInOut)),
    Animation::timing(&fade, 1.0, Timing::new(400.0, Easing::EaseInOut)),
])
.looped(None);

let landed = hop.start();
spawn(async move {
    status.set(if landed.await { "landed" } else { "stopped" });
});
TSXRustविवरण
Animated.timing(value, { toValue, duration?, easing?, delay? })Animation::timing(&value, to, Timing { .. })timing step।
Animated.spring(value, { toValue, stiffness?, damping?, mass?, velocity? })Animation::spring(&value, to, Spring { .. })spring step।
Animated.decay(value, { velocity?, deceleration?, clamp?, bounce? })Animation::decay(&value, velocity, deceleration), Animation::decay_within(..)decay step।
Animated.sequence(list)Animation::sequence(list)एक के बाद एक।
Animated.parallel(list, { stopTogether? })Animation::parallel(list), .stop_together(..)सब एक साथ।
Animated.stagger(ms, list, { from? })Animation::stagger(ms, list), Animation::stagger_from(ms, StaggerFrom::Center, list)सब एक साथ, हर एक पिछले के ms बाद शुरू होता है, from से गिनकर: "first" (डिफ़ॉल्ट), "last", "center" (बीच से बाहर की ओर लहर) या एक index (StaggerFrom::First, Last, Center, Index(i))।
Animated.keyframes(value, { keyframes, ...timing })Animation::keyframes(&value, frames, KeyframeTiming)keyframes का एक step।
Animated.delay(ms)Animation::delay(ms)प्रतीक्षा करता है।
Animated.loop(animation, { iterations?, resetBeforeIteration? }).looped(iterations), .reset_before_iteration(..)दोहराता है (डिफ़ॉल्ट रूप से हमेशा; Rust में None)।

composite animation में start() होता है (TSX में callback और promise के साथ भी, दोनों को { finished } मिलता है; Rust में await किया जा सकने वाला bool; इसे फिर से कॉल करने पर यह फिर से चलता है), stop() (जहाँ है वहीं रुक जाता है) और reset() (रुक जाता है और अपने मानों को वहाँ लौटा देता है जहाँ वे शुरू होते समय थे)। callback पूरे tree के लिए एक बार चलता है।

चलता हुआ animation अपने मानों को जीवित नहीं रखता: जब उन्हें रखने वाला component हट जाता है (उसके views और variables), तो अनंत loop भी उनके साथ रुक जाता है, और उसका उत्तर होता है कि वह पूरा नहीं हुआ (false)।

#Easing वक्र

Easing curves डेटा हैं, जिनका मूल्यांकन natively होता है: TSX में nsh से Easing, Rust में Easing enum।

TSXRustविवरण
Easing.linear, Easing.easeEasing::Linear, Easing::Bezier(0.25, 0.1, 0.25, 1.0)स्थिर गति; CSS का ease।
Easing.quad, Easing.cubic, Easing.poly(n)Curve::Quad, Curve::Cubic, Curve::Poly(n)t², t³, tⁿ।
Easing.sin, Easing.circle, Easing.expCurve::Sin, Curve::Circle, Curve::ExpSinusoidal, वृत्ताकार, घातांकीय।
Easing.back(s?)Curve::Back(s)आगे बढ़ने से पहले पीछे खिंचता है (overshoot s, डिफ़ॉल्ट 1.70158)।
Easing.elastic(bounciness?)Curve::Elastic(bounciness)स्प्रिंग जैसा overshoot।
Easing.bounceCurve::Bounceअंत में उछलता है।
Easing.bezier(x1, y1, x2, y2)Easing::Bezier(x1, y1, x2, y2)CSS की तरह cubic Bézier।
Easing.steps(count, position?)Easing::Steps(count, StepPosition::JumpEnd)सरकने के बजाय count स्तरों के बीच छलांग लगाता है, जैसे CSS steps(): फ़्रेम-दर-फ़्रेम sprite, टिक-टिक करती सुई। position है "jump-end" (डिफ़ॉल्ट, साथ ही "end"), "jump-start" ("start"), "jump-none" या "jump-both"।
Easing.in(e), Easing.out(e), Easing.inOut(e)Easing::Curve(curve, EaseMode::In), EaseMode::Out, EaseMode::InOutये curves “in” curves हैं; out और inOut उन्हें पलट देते हैं।

TSX में जहाँ भी easing स्वीकार किया जाता है, वहाँ आप नाम ("linear", "ease", "easeIn", "easeOut", "easeInOut") या Bézier array [x1, y1, x2, y2] भी दे सकते हैं; Rust में Easing::EaseIn, Easing::EaseOut और Easing::EaseInOut CSS के curves हैं।

x.timing(1.0, 300.0, Easing::Curve(Curve::Back(1.70158), EaseMode::Out));
x.timing(1.0, 300.0, Easing::EaseOut);
x.timing(1.0, 300.0, Easing::Bezier(0.2, 0.8, 0.2, 1.0));

Easing.sample(easing, count) (Rust में sample_easing) 0 से 1 तक count समान दूरी वाले बिंदुओं पर curve लौटाता है, और Animated.sampleSpring(config, fps?) / Animated.sampleDecay(config, fps?) (sample_spring, sample_decay) वे मान लौटाते हैं जिनसे होकर spring या decay गुज़रता है, हर फ़्रेम पर एक (डिफ़ॉल्ट 60 प्रति सेकंड), config.from (0) से उसके खत्म होने तक। इनकी गणना उसी native code से होती है जिससे animations चलते हैं, इसलिए curve का chart ठीक वही दिखाता है जो animation करता है:

use nsh_core::element::{sample_easing, sample_spring};

let curve = sample_easing(Easing::Curve(Curve::Back(1.70158), EaseMode::Out), 50); // 50 progress values
let spring = sample_spring(0.0, 1.0, Spring { stiffness: 180.0, damping: 8.0, ..Spring::default() }, 60.0);

#LayoutAnimation

layout animation अगले update के फ़्रेम बदलावों को natively animate करता है: जो views हिलते या आकार बदलते हैं वे अपने नए फ़्रेम तक सरकते हैं, नए views fade (या scale) होकर आते हैं, हटाए गए views fade होकर चले जाते हैं। इसे state बदलने से ठीक पहले कॉल करें: Rust में animate_layout(..), TSX में LayoutAnimation.configureNext(config)।

use nsh_core::element::LayoutAnimation;

let items = signal(vec![1, 2, 3]);
let next = stored(4);

let add = move || {
    animate_layout(LayoutAnimation::SPRING);
    let n = next.with(|n| {
        *n += 1;
        *n - 1
    });
    items.update(|i| i.insert(0, n));
};
let remove = move || {
    animate_layout(LayoutAnimation::EASE_IN_EASE_OUT);
    items.update(|i| {
        i.remove(0);
    });
};
TSXRustविवरण
configureNext(config, onEnd?)animate_layout(config)अगले commit के बदलावों को animate करता है; TSX में पूरा होने पर onEnd चलता है।
create(duration, type?, property?)LayoutAnimation::all(LayoutTransition::new(curve, ms))तीनों चरणों के लिए एक ही curve वाला config।
Presets.easeInEaseOut, Presets.linear, Presets.springLayoutAnimation::EASE_IN_EASE_OUT, LINEAR, SPRINGतैयार configs।
easeInEaseOut(onEnd?), linear(onEnd?), spring(onEnd?)preset के साथ animate_layoutpreset के साथ configureNext।
Types, PropertiesLayoutCurve, LayoutPropertyनाम, configs बनाने के लिए।

config है { duration, create?, update?, delete? } (Rust में LayoutAnimation { create, update, delete }): जोड़े गए views के लिए create, हिलाए या आकार बदले गए views के लिए update, हटाए गए views के लिए delete। हर चरण { type?, property?, duration?, delay?, springDamping? } (LayoutTransition { curve, property, duration_ms, delay_ms }) है, जहाँ type है "easeInEaseOut", "spring", "linear", "easeIn", "easeOut" या "keyboard" (software keyboard का अपना curve, जो keyboard के हिलना शुरू करने के समय से गिना जाता है, ताकि जो उसके लिए जगह बनाता है वह उसके साथ चले; LayoutCurve::Keyboard), property वह है जिसे प्रकट और गायब होते views animate करते हैं ("opacity", "scaleXY", "scale"), और springDamping (0..1, डिफ़ॉल्ट 0.7; LayoutCurve::Spring { damping }) spring का overshoot तय करता है।

use nsh_core::element::{LayoutAnimation, LayoutCurve, LayoutProperty, LayoutTransition};

animate_layout(LayoutAnimation {
    create: Some(LayoutTransition::new(LayoutCurve::EaseOut, 300.0)),
    update: Some(LayoutTransition::new(LayoutCurve::Spring { damping: 0.8 }, 300.0)),
    delete: Some(LayoutTransition {
        property: LayoutProperty::Scale,
        ..LayoutTransition::new(LayoutCurve::EaseIn, 300.0)
    }),
});

layout animation स्क्रीन पर कहीं भी, update द्वारा बदले गए हर फ़्रेम को कवर करता है: जो list refresh होती है या जो live data उसी update में आता है, वह भी साथ में चलता है। Rust component में, केवल उसे animate करें जो किसी एक element के बदलाव से हिलता है, ElementRef::animate_layout(transition) के साथ: element का अपना फ़्रेम और उसके content का, और उन views के फ़्रेम जिन्हें उसका आकार आगे धकेलता है (उसके siblings और उनके, स्क्रीन तक)। update के बाकी फ़्रेम तुरंत बदल जाते हैं। nsh/base के Collapsible और AccordionPanel इसी तरह अपना content प्रकट करते हैं।

use nsh_core::element::{LayoutCurve, LayoutTransition};

let region = element_ref();
let open = signal(false);
let toggle = move || {
    region.animate_layout(LayoutTransition::new(LayoutCurve::Spring { damping: 1.0 }, 265.0));
    open.update(|o| *o = !*o);
};
view()
    .element_ref(&region)
    .overflow_hidden()
    .style_with(move |s| if open.get() { s } else { s.h(px(0.0)) })

#क्या कहाँ चलता है

कहाँ चलता हैप्रति फ़्रेम आपका code
Transitions, keyframe animationsUI threadकोई नहीं
Motions, keyframes, timing / spring / decay, compositesUI threadकोई नहीं; आपके code को उनके खत्म होने की सूचना मिलती है
interpolate, अंकगणित, diffClampUI threadकोई नहीं
scrollValue, pan / pinch / rotationUI threadकोई नहीं; आपके code को शुरू और अंत की सूचना मिलती है
Layout animationsUI threadकोई नहीं
requestAnimationFrame loops (Rust में request_animation_frame), onScroll handlers, PanResponderआपका code, फिर एक updateहर फ़्रेम

requestAnimationFrame (TSX में एक global, जो display का अनुसरण करता है) का सहारा केवल उसी के लिए लें जिसे पहले से वर्णित नहीं किया जा सकता, जैसे आपके code में physics simulation। जो कुछ भी animated value हो सकता है, उसे animated value ही होना चाहिए।

#कम गति (Reduced motion)

जब user कम गति माँगता है (iOS में “Reduce Motion”, Android में “Remove animations”), तो Rust में reduce_motion() और TSX में nsh से reduceMotion() true होते हैं, और motion-reduce: / motion-safe: Tailwind variants लागू होते हैं।

Motions इस सेटिंग का अपने आप पालन करते हैं: play और enter बिना हिले तुरंत वहीं पहुँच जाते हैं जहाँ motion खत्म होता, और उत्तर यह होता है कि वह पूरा हुआ (true), ठीक वैसे ही जैसे चले हुए motion के लिए। exit फिर भी छिपे हुए पर खत्म होता है, entrance फिर भी विश्राम अवस्था में खत्म होता है। ऐसे motion के लिए जिसमें user के लिए ज़रूरी अर्थ है, जैसे progress spinner, ignoreReduceMotion: true (Rust में play_ignoring_reduce_motion) दें।

Transitions, keyframe animations, animated values, keyframes और composites जैसा लिखा है वैसा ही चलते हैं; इनका उपयोग करके कोई शांत विकल्प चुनें:

let open = move || {
    if reduce_motion() {
        panel.set(0.0);
    } else {
        panel.spring(0.0, Spring::default());
    }
};

view().style_when(|| !reduce_motion(), |s| s.animate_bounce())

देखें Accessibility।

#संबंधित