> For the complete documentation index, see [llms.txt](https://sygnal.gitbook.io/sygnal-webflow-components/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sygnal.gitbook.io/sygnal-webflow-components/cc/sygnal-media/rive/rive-js-api.md).

# Rive JS API

{% hint style="warning" %}
**NOT YET IMPLEMENTED.**&#x20;
{% endhint %}

Setup;&#x20;

1. **A global registry** to track Rive instances by ID
2. **Ref forwarding** in components to expose Rive instance methods
3. **A manager class** that provides the external API
4. **Props for ID** so components can be registered

### What It Would Look Like

#### 1. Global Rive Manager (similar to MediaPlayerManager)

```typescript
// RiveManager.ts
class RiveManager {
  private instances: Map<string, any> = new Map();

  register(id: string, riveInstance: any) {
    this.instances.set(id, riveInstance);
  }

  unregister(id: string) {
    this.instances.delete(id);
  }

  // Artboard control
  setArtboard(id: string, artboardName: string): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    
    try {
      instance.artboard = artboardName;
      return true;
    } catch (error) {
      console.error(`Failed to set artboard for ${id}:`, error);
      return false;
    }
  }

  // State machine control
  setStateMachine(id: string, stateMachineName: string): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    
    try {
      instance.stateMachineName = stateMachineName;
      return true;
    } catch (error) {
      console.error(`Failed to set state machine for ${id}:`, error);
      return false;
    }
  }

  // Input control (the powerful one)
  setInput(id: string, inputName: string, value: number | boolean): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    
    try {
      const input = instance.stateMachineInputs(instance.stateMachineName)
        ?.find((i: any) => i.name === inputName);
      
      if (!input) {
        console.warn(`Input "${inputName}" not found`);
        return false;
      }
      
      input.value = value;
      return true;
    } catch (error) {
      console.error(`Failed to set input for ${id}:`, error);
      return false;
    }
  }

  // Trigger control
  fireTrigger(id: string, triggerName: string): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    
    try {
      const trigger = instance.stateMachineInputs(instance.stateMachineName)
        ?.find((i: any) => i.name === triggerName && i.type === 'trigger');
      
      if (!trigger) return false;
      
      trigger.fire();
      return true;
    } catch (error) {
      console.error(`Failed to fire trigger for ${id}:`, error);
      return false;
    }
  }

  // Playback control
  play(id: string): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    instance.play();
    return true;
  }

  pause(id: string): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    instance.pause();
    return true;
  }

  reset(id: string): boolean {
    const instance = this.instances.get(id);
    if (!instance) return false;
    instance.reset();
    return true;
  }

  // Utility
  getInstanceIds(): string[] {
    return Array.from(this.instances.keys());
  }

  getInstance(id: string): any | null {
    return this.instances.get(id) || null;
  }
}

// Global singleton
export const riveManager = new RiveManager();

// Add to window for external access
declare global {
  interface Window {
    riveManager: RiveManager;
  }
}

if (typeof window !== 'undefined') {
  window.riveManager = riveManager;
}
```

#### 2. Updated Component (RiveWebGL2.tsx)

```typescript
export interface RiveWebGL2Props {
  // ... existing props
  id?: string; // ADD THIS - optional ID for external control
}

export const RiveWebGL2: React.FC<RiveWebGL2Props> = ({
  riveUrl,
  id, // ADD THIS
  // ... other props
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const riveInstanceRef = useRef<any>(null);

  useEffect(() => {
    // ... existing canvas setup

    const loadRive = async () => {
      try {
        const RiveModule = await import('@rive-app/webgl2');
        const { Rive: RiveRuntime, Layout, Fit, Alignment } = RiveModule;

        const riveInstance = new RiveRuntime({
          // ... existing config
        });

        riveInstanceRef.current = riveInstance;

        // Register with manager if ID provided
        if (id) {
          riveManager.register(id, riveInstance);
          console.log(`Rive instance "${id}" registered`);
        }

        // ... rest of setup
      } catch (error) {
        // ... error handling
      }
    };

    loadRive();

    return () => {
      // Unregister on cleanup
      if (id && riveInstanceRef.current) {
        riveManager.unregister(id);
      }
      
      // ... existing cleanup
    };
  }, [urlString, id, /* other deps */]);

  // ... rest of component
};
```

#### 3. Webflow Props Declaration

```typescript
// RiveWebGL2.webflow.tsx
export default declareComponent(RiveWebGL2, {
    name: 'Rive WebGL2',
    description: 'Interactive Rive animation player...',
    group: 'Media',
    props: {
        // ... existing props
        
        id: props.ID({
            name: "Component ID",
            tooltip: "Unique ID for external control via JavaScript API (optional)",
            group: "Advanced"
        }),
    },
});
```

### Usage Examples

#### In Webflow Designer:

1. Add RiveWebGL2 component
2. Set ID to `"character-animation"`
3. Configure artboard, state machine, etc.

#### External JavaScript Control:

```javascript
// In a Webflow embed or custom code

// Change artboard dynamically
window.riveManager.setArtboard('character-animation', 'Walk');

// Switch state machine
window.riveManager.setStateMachine('character-animation', 'Movement');

// Set numeric input (e.g., speed)
window.riveManager.setInput('character-animation', 'speed', 2.5);

// Set boolean input (e.g., isJumping)
window.riveManager.setInput('character-animation', 'isJumping', true);

// Fire a trigger
window.riveManager.fireTrigger('character-animation', 'explode');

// Playback control
window.riveManager.play('character-animation');
window.riveManager.pause('character-animation');
window.riveManager.reset('character-animation');

// Get instance for direct access
const instance = window.riveManager.getInstance('character-animation');
console.log(instance.bounds); // Access Rive instance directly
```

#### Event-Driven Example:

```javascript
// Button click changes character
document.getElementById('hero-button').addEventListener('click', () => {
  window.riveManager.setArtboard('character', 'Hero');
  window.riveManager.fireTrigger('character', 'transform');
});

// Scroll-based animation
window.addEventListener('scroll', () => {
  const scrollPercent = window.scrollY / document.body.scrollHeight;
  window.riveManager.setInput('progress-bar', 'progress', scrollPercent * 100);
});

// Form interaction
document.getElementById('email-input').addEventListener('focus', () => {
  window.riveManager.setInput('form-animation', 'isFocused', true);
});
```

### Real-World Use Cases

**1. Interactive Character Selection**

```javascript
// User hovers over character portraits
['warrior', 'mage', 'rogue'].forEach(character => {
  document.getElementById(`${character}-card`).addEventListener('mouseenter', () => {
    window.riveManager.setArtboard('character-preview', character);
    window.riveManager.fireTrigger('character-preview', 'appear');
  });
});
```

**2. Progress/Loading States**

```javascript
// Update loading bar
let progress = 0;
const interval = setInterval(() => {
  progress += 10;
  window.riveManager.setInput('loader', 'percentage', progress);
  
  if (progress >= 100) {
    clearInterval(interval);
    window.riveManager.fireTrigger('loader', 'complete');
  }
}, 200);
```

**3. Form Validation Feedback**

```javascript
// Email validation
const emailInput = document.getElementById('email');
emailInput.addEventListener('blur', () => {
  const isValid = emailInput.validity.valid;
  window.riveManager.setInput('form-feedback', 'isValid', isValid);
  window.riveManager.fireTrigger('form-feedback', isValid ? 'success' : 'error');
});
```

### Advanced: Type-Safe API

For even better DX, you could add TypeScript interfaces:

```typescript
interface RiveInputs {
  [animationId: string]: {
    [inputName: string]: number | boolean;
  };
}

// Usage with autocomplete
const inputs: RiveInputs = {
  'character': {
    'speed': 2.5,
    'isRunning': true
  }
};
```

### Key Considerations

**1. ID Uniqueness**: Just like your audio players, ensure IDs are unique across the page

&#x20;

**2. Timing**: The API only works after components mount. Consider:

```javascript
// Wait for components to register
window.addEventListener('load', () => {
  console.log('Available Rive instances:', window.riveManager.getInstanceIds());
});
```

**3. Error Handling**: The manager returns `boolean` for success/failure - check these!

&#x20;

**4. Performance**: Direct Rive API access is synchronous and fast

&#x20;

**5. Webflow Compatibility**: Works perfectly in published Webflow sites via custom code embeds

&#x20;

Would you like me to implement this Rive manager system for you? It would follow the same pattern as your existing MediaPlayerManager.

<br>
