CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-native-animatable

Easy to use declarative transitions and animations for React Native

Pending
Overview
Eval results
Files

imperative-animation.mddocs/

Imperative Animation

Method-based animation control for complex programmatic animations and transitions. Execute animations directly via component methods for fine-grained control over timing and sequencing.

Capabilities

Animate Method

Execute any animation imperatively with optional timing parameters.

/**
 * Execute an animation imperatively
 * @param animation - Animation name or custom definition
 * @param duration - Optional duration override in milliseconds
 * @param iterationDelay - Optional delay between iterations in milliseconds
 * @returns Promise that resolves when animation completes or is cancelled
 */
animate(animation: Animation | CustomAnimation, duration?: number, iterationDelay?: number): Promise<{ finished: boolean }>;

Usage Examples:

class AnimatedComponent extends Component {
  handleViewRef = ref => this.view = ref;
  
  startAnimation = async () => {
    try {
      // Execute animation and wait for completion
      const endState = await this.view.animate('bounceIn', 800);
      
      if (endState.finished) {
        console.log('Animation completed successfully');
        this.startNextAnimation();
      } else {
        console.log('Animation was cancelled');
      }
    } catch (error) {
      console.log('Animation error:', error);
    }
  };
  
  customAnimation = () => {
    // Use custom animation object
    const customBounce = {
      0: { scale: 1, translateY: 0 },
      0.5: { scale: 1.2, translateY: -50 },
      1: { scale: 1, translateY: 0 }
    };
    
    this.view.animate(customBounce, 1000);
  };
  
  render() {
    return (
      <Animatable.View ref={this.handleViewRef}>
        <TouchableOpacity onPress={this.startAnimation}>
          <Text>Tap to animate</Text>
        </TouchableOpacity>
      </Animatable.View>
    );
  }
}

Built-in Animation Methods

All built-in animations are available as direct methods on animatable components.

interface AnimationMethods {
  // Attention seekers
  bounce(duration?: number): Promise<{ finished: boolean }>;
  flash(duration?: number): Promise<{ finished: boolean }>;
  jello(duration?: number): Promise<{ finished: boolean }>;
  pulse(duration?: number): Promise<{ finished: boolean }>;
  rotate(duration?: number): Promise<{ finished: boolean }>;
  rubberBand(duration?: number): Promise<{ finished: boolean }>;
  shake(duration?: number): Promise<{ finished: boolean }>;
  swing(duration?: number): Promise<{ finished: boolean }>;
  tada(duration?: number): Promise<{ finished: boolean }>;
  wobble(duration?: number): Promise<{ finished: boolean }>;
  
  // Bouncing entrances
  bounceIn(duration?: number): Promise<{ finished: boolean }>;
  bounceInDown(duration?: number): Promise<{ finished: boolean }>;
  bounceInUp(duration?: number): Promise<{ finished: boolean }>;
  bounceInLeft(duration?: number): Promise<{ finished: boolean }>;
  bounceInRight(duration?: number): Promise<{ finished: boolean }>;
  
  // ... all other built-in animations
}

Usage Examples:

class InteractiveComponent extends Component {
  handleViewRef = ref => this.view = ref;
  
  // Different methods for different interactions
  onPress = () => this.view.pulse(200);
  onLongPress = () => this.view.shake(400);
  onSuccess = () => this.view.bounce(600);
  onError = () => this.view.flash(300);
  
  sequentialAnimations = async () => {
    // Chain animations sequentially
    await this.view.slideInLeft(500);
    await this.view.pulse(300);
    await this.view.slideOutRight(500);
  };
  
  render() {
    return (
      <TouchableOpacity 
        onPress={this.onPress}
        onLongPress={this.onLongPress}
      >
        <Animatable.View ref={this.handleViewRef}>
          <Text>Interactive Element</Text>
        </Animatable.View>
      </TouchableOpacity>
    );
  }
}

Stop Animation

Stop any currently running animation immediately.

/**
 * Stop any currently running animation
 */
stopAnimation(): void;

Usage Examples:

class ControlledAnimation extends Component {
  handleViewRef = ref => this.view = ref;
  
  startInfiniteAnimation = () => {
    // Start infinite animation
    this.view.animate('pulse', 1000);
    
    // Set up auto-stop after 5 seconds
    this.stopTimer = setTimeout(() => {
      this.view.stopAnimation();
    }, 5000);
  };
  
  stopAnimation = () => {
    // Stop animation immediately
    this.view.stopAnimation();
    if (this.stopTimer) {
      clearTimeout(this.stopTimer);
    }
  };
  
  componentWillUnmount() {
    // Always stop animations when component unmounts
    this.view?.stopAnimation();
    if (this.stopTimer) {
      clearTimeout(this.stopTimer);
    }
  };
  
  render() {
    return (
      <View>
        <Animatable.View ref={this.handleViewRef}>
          <Text>Controlled Animation</Text>
        </Animatable.View>
        <Button title="Start" onPress={this.startInfiniteAnimation} />
        <Button title="Stop" onPress={this.stopAnimation} />
      </View>
    );
  }
}

Transition Method

Transition between specific style values with full control over from and to states.

/**
 * Transition between specific style values
 * @param fromValues - Starting style values
 * @param toValues - Ending style values
 * @param duration - Optional transition duration in milliseconds
 * @param easing - Optional easing function
 */
transition(fromValues: object, toValues: object, duration?: number, easing?: Easing): void;

Usage Examples:

class TransitionComponent extends Component {
  handleViewRef = ref => this.view = ref;
  
  colorTransition = () => {
    this.view.transition(
      { backgroundColor: 'red', scale: 1 },     // From
      { backgroundColor: 'blue', scale: 1.2 }, // To
      800,                                      // Duration
      'ease-in-out'                            // Easing
    );
  };
  
  layoutTransition = () => {
    this.view.transition(
      { 
        width: 100, 
        height: 100, 
        borderRadius: 0 
      },
      { 
        width: 200, 
        height: 50, 
        borderRadius: 25 
      },
      1000,
      'ease-out-back'
    );
  };
  
  multiPropertyTransition = () => {
    this.view.transition(
      {
        opacity: 1,
        translateX: 0,
        translateY: 0,
        rotate: '0deg',
        scale: 1
      },
      {
        opacity: 0.7,
        translateX: 100,
        translateY: 50,
        rotate: '45deg',
        scale: 1.5
      },
      1200
    );
  };
  
  render() {
    return (
      <View>
        <Animatable.View ref={this.handleViewRef} style={styles.box}>
          <Text>Transition Box</Text>
        </Animatable.View>
        <Button title="Color Transition" onPress={this.colorTransition} />
        <Button title="Layout Transition" onPress={this.layoutTransition} />
        <Button title="Multi-Property" onPress={this.multiPropertyTransition} />
      </View>
    );
  }
}

Transition To Method

Transition to specific style values from the current state automatically.

/**
 * Transition to specific style values from current state
 * @param toValues - Target style values
 * @param duration - Optional transition duration in milliseconds
 * @param easing - Optional easing function
 */
transitionTo(toValues: object, duration?: number, easing?: Easing): void;

Usage Examples:

class SmartTransition extends Component {
  handleViewRef = ref => this.view = ref;
  
  state = {
    expanded: false,
    highlighted: false
  };
  
  toggleExpanded = () => {
    const expanded = !this.state.expanded;
    this.setState({ expanded });
    
    // Transition to new size
    this.view.transitionTo({
      width: expanded ? 300 : 150,
      height: expanded ? 200 : 100,
      scale: expanded ? 1.1 : 1
    }, 400, 'ease-out');
  };
  
  toggleHighlight = () => {
    const highlighted = !this.state.highlighted;
    this.setState({ highlighted });
    
    // Transition to new appearance
    this.view.transitionTo({
      backgroundColor: highlighted ? 'yellow' : 'white',
      opacity: highlighted ? 1 : 0.8,
      borderWidth: highlighted ? 3 : 1
    }, 200);
  };
  
  animateToRandomPosition = () => {
    // Transition to random position
    this.view.transitionTo({
      translateX: Math.random() * 200 - 100,
      translateY: Math.random() * 200 - 100,
      rotate: `${Math.random() * 360}deg`
    }, 800, 'ease-in-out');
  };
  
  resetPosition = () => {
    // Return to original position
    this.view.transitionTo({
      translateX: 0,
      translateY: 0,
      rotate: '0deg',
      scale: 1,
      opacity: 1
    }, 600, 'ease-out-back');
  };
  
  render() {
    return (
      <View>
        <Animatable.View 
          ref={this.handleViewRef} 
          style={[
            styles.box,
            { 
              width: 150, 
              height: 100,
              backgroundColor: 'white',
              borderWidth: 1,
              borderColor: 'gray'
            }
          ]}
        >
          <Text>Smart Transition</Text>
        </Animatable.View>
        
        <Button title="Toggle Size" onPress={this.toggleExpanded} />
        <Button title="Toggle Highlight" onPress={this.toggleHighlight} />
        <Button title="Random Position" onPress={this.animateToRandomPosition} />
        <Button title="Reset" onPress={this.resetPosition} />
      </View>
    );
  }
}

Advanced Imperative Patterns

Animation Sequences

Create complex animation sequences with promises:

class SequenceAnimation extends Component {
  handleViewRef = ref => this.view = ref;
  
  complexSequence = async () => {
    try {
      // Step 1: Slide in
      await this.view.slideInLeft(500);
      
      // Step 2: Pulse 3 times
      for (let i = 0; i < 3; i++) {
        await this.view.pulse(300);
        if (i < 2) await this.wait(200); // Pause between pulses
      }
      
      // Step 3: Scale up and change color
      this.view.transitionTo({
        scale: 1.5,
        backgroundColor: 'gold'
      }, 400);
      
      await this.wait(400);
      
      // Step 4: Final bounce and slide out
      await this.view.bounce(600);
      await this.view.slideOutRight(500);
      
    } catch (error) {
      console.log('Sequence interrupted:', error);
    }
  };
  
  wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
  
  render() {
    return (
      <Animatable.View ref={this.handleViewRef}>
        <TouchableOpacity onPress={this.complexSequence}>
          <Text>Start Complex Sequence</Text>
        </TouchableOpacity>
      </Animatable.View>
    );
  }
}

Conditional Animation Chaining

class ConditionalAnimation extends Component {
  handleViewRef = ref => this.view = ref;
  
  smartAnimation = async () => {
    const startTime = Date.now();
    
    // Start with attention seeker
    const result = await this.view.shake(400);
    
    if (result.finished) {
      // If user hasn't interacted, continue with more animations
      if (Date.now() - startTime > 2000) {
        await this.view.bounce(600);
        await this.view.pulse(300);
      }
    } else {
      // Animation was cancelled, do cleanup
      this.resetState();
    }
  };
  
  interruptibleSequence = async () => {
    this.animationCancelled = false;
    
    const animations = ['fadeIn', 'pulse', 'shake', 'bounce'];
    
    for (const animation of animations) {
      if (this.animationCancelled) break;
      
      const result = await this.view.animate(animation, 600);
      if (!result.finished) break;
      
      await this.wait(300);
    }
  };
  
  cancelSequence = () => {
    this.animationCancelled = true;
    this.view.stopAnimation();
  };
}

Performance-Optimized Animations

class OptimizedAnimations extends Component {
  handleViewRef = ref => this.view = ref;
  
  // Use native driver for transform animations
  nativeTransforms = () => {
    // These work with native driver
    this.view.transitionTo({
      translateX: 100,
      translateY: 50,
      scale: 1.2,
      rotate: '45deg',
      opacity: 0.8
    }, 400);
  };
  
  // Use JS driver for layout/color animations  
  jsAnimations = () => {
    // These require JS driver
    this.view.transitionTo({
      backgroundColor: 'red',
      width: 200,
      height: 150,
      borderRadius: 20
    }, 400);
  };
  
  // Batch multiple transform animations
  batchedTransforms = () => {
    // More efficient than multiple separate calls
    this.view.transitionTo({
      translateX: 50,
      translateY: -30,
      scale: 1.1,
      rotate: '15deg'
    }, 300);
  };
}

Error Handling and Cleanup

class RobustAnimations extends Component {
  animationPromises = [];
  
  handleViewRef = ref => this.view = ref;
  
  safeAnimation = async () => {
    try {
      const animationPromise = this.view.bounce(800);
      this.animationPromises.push(animationPromise);
      
      const result = await animationPromise;
      
      // Remove completed promise
      this.animationPromises = this.animationPromises.filter(p => p !== animationPromise);
      
      if (result.finished) {
        this.onAnimationComplete();
      } else {
        this.onAnimationCancelled();
      }
      
    } catch (error) {
      console.error('Animation error:', error);
      this.onAnimationError(error);
    }
  };
  
  componentWillUnmount() {
    // Stop all animations and clear promises
    this.view?.stopAnimation();
    this.animationPromises = [];
  };
  
  onAnimationComplete = () => {
    console.log('Animation completed successfully');
  };
  
  onAnimationCancelled = () => {
    console.log('Animation was cancelled');
  };
  
  onAnimationError = (error) => {
    console.error('Animation failed:', error);
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-react-native-animatable

docs

animation-registry.md

builtin-animations.md

component-creation.md

custom-animations.md

declarative-animation.md

imperative-animation.md

index.md

prebuilt-components.md

tile.json