@grandgular/rive-angular 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -191,6 +191,221 @@ this.riveRef().setTextRunValueAtPath(
|
|
|
191
191
|
- **Uncontrolled**: Keys not in `textRuns` — managed imperatively via methods
|
|
192
192
|
- **Warning**: Calling `setTextRunValue()` on a controlled key logs a warning and the change will be overwritten on next input update
|
|
193
193
|
|
|
194
|
+
### Data Binding (ViewModel)
|
|
195
|
+
|
|
196
|
+
Rive's ViewModel system allows you to bind dynamic data (colors, numbers, strings, booleans, etc.) to your animations. ViewModels are created in the Rive editor and provide a structured way to control animation properties at runtime.
|
|
197
|
+
|
|
198
|
+
#### What is a ViewModel?
|
|
199
|
+
|
|
200
|
+
A ViewModel in Rive is a data structure that:
|
|
201
|
+
- Is created by designers in the Rive editor
|
|
202
|
+
- Contains typed properties (color, number, string, boolean, enum, trigger)
|
|
203
|
+
- Can be bound to animation elements (fills, strokes, transforms, etc.)
|
|
204
|
+
- Supports two-way data binding (changes in code affect animation, changes in animation can trigger events)
|
|
205
|
+
|
|
206
|
+
#### When to use Data Binding vs Text Runs?
|
|
207
|
+
|
|
208
|
+
- **Text Runs**: Simple text updates, no ViewModel setup required in editor
|
|
209
|
+
- **Data Binding**: Dynamic colors, numbers, complex data structures, two-way reactivity
|
|
210
|
+
|
|
211
|
+
#### Declarative (Controlled) Approach
|
|
212
|
+
|
|
213
|
+
Use the `dataBindings` input for reactive, template-driven data binding:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { Component, signal } from '@angular/core';
|
|
217
|
+
import { RiveCanvasComponent } from '@grandgular/rive-angular';
|
|
218
|
+
|
|
219
|
+
@Component({
|
|
220
|
+
selector: 'app-data-binding',
|
|
221
|
+
standalone: true,
|
|
222
|
+
imports: [RiveCanvasComponent],
|
|
223
|
+
template: `
|
|
224
|
+
<rive-canvas
|
|
225
|
+
src="assets/animation.riv"
|
|
226
|
+
[dataBindings]="{
|
|
227
|
+
backgroundColor: themeColor(),
|
|
228
|
+
score: playerScore(),
|
|
229
|
+
playerName: userName(),
|
|
230
|
+
isActive: isGameActive()
|
|
231
|
+
}"
|
|
232
|
+
(dataBindingChange)="onDataChange($event)"
|
|
233
|
+
/>
|
|
234
|
+
|
|
235
|
+
<button (click)="changeTheme()">Change Theme</button>
|
|
236
|
+
<button (click)="incrementScore()">+10 Points</button>
|
|
237
|
+
`
|
|
238
|
+
})
|
|
239
|
+
export class DataBindingComponent {
|
|
240
|
+
themeColor = signal('#FF5733');
|
|
241
|
+
playerScore = signal(0);
|
|
242
|
+
userName = signal('Player 1');
|
|
243
|
+
isGameActive = signal(true);
|
|
244
|
+
|
|
245
|
+
changeTheme() {
|
|
246
|
+
const colors = ['#FF5733', '#33FF57', '#3357FF', '#F333FF'];
|
|
247
|
+
const randomColor = colors[Math.floor(Math.random() * colors.length)];
|
|
248
|
+
this.themeColor.set(randomColor);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
incrementScore() {
|
|
252
|
+
this.playerScore.update(score => score + 10);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
onDataChange(event: DataBindingChangeEvent) {
|
|
256
|
+
console.log('Property changed from animation:', event);
|
|
257
|
+
// event.path: property path
|
|
258
|
+
// event.value: new value (for triggers, value is always true)
|
|
259
|
+
// event.propertyType: 'color' | 'number' | 'string' | 'boolean' | 'enum' | 'trigger'
|
|
260
|
+
|
|
261
|
+
if (event.propertyType === 'trigger') {
|
|
262
|
+
console.log(`Trigger "${event.path}" fired from animation`);
|
|
263
|
+
// Handle trigger event (e.g., show popup, play sound, etc.)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Imperative (Uncontrolled) Approach
|
|
270
|
+
|
|
271
|
+
Use methods for direct, programmatic control:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { Component, viewChild } from '@angular/core';
|
|
275
|
+
import { RiveCanvasComponent } from '@grandgular/rive-angular';
|
|
276
|
+
|
|
277
|
+
@Component({
|
|
278
|
+
selector: 'app-imperative',
|
|
279
|
+
standalone: true,
|
|
280
|
+
imports: [RiveCanvasComponent],
|
|
281
|
+
template: `
|
|
282
|
+
<rive-canvas src="assets/animation.riv" />
|
|
283
|
+
|
|
284
|
+
<button (click)="updateColor()">Update Color</button>
|
|
285
|
+
<button (click)="updateScore()">Update Score</button>
|
|
286
|
+
<button (click)="triggerAnimation()">Fire Trigger</button>
|
|
287
|
+
`
|
|
288
|
+
})
|
|
289
|
+
export class ImperativeComponent {
|
|
290
|
+
riveRef = viewChild.required(RiveCanvasComponent);
|
|
291
|
+
|
|
292
|
+
updateColor() {
|
|
293
|
+
// Set color using hex string
|
|
294
|
+
this.riveRef().setColor('backgroundColor', '#00FF00');
|
|
295
|
+
|
|
296
|
+
// Or using RGBA components
|
|
297
|
+
this.riveRef().setColorRgba('backgroundColor', 0, 255, 0, 255);
|
|
298
|
+
|
|
299
|
+
// Or change only opacity
|
|
300
|
+
this.riveRef().setColorOpacity('backgroundColor', 0.5);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
updateScore() {
|
|
304
|
+
// Set any data binding value (auto-detects type)
|
|
305
|
+
this.riveRef().setDataBinding('score', 100);
|
|
306
|
+
this.riveRef().setDataBinding('playerName', 'Winner');
|
|
307
|
+
this.riveRef().setDataBinding('isActive', false);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
triggerAnimation() {
|
|
311
|
+
// Fire a trigger property
|
|
312
|
+
this.riveRef().fireViewModelTrigger('onComplete');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
readValues() {
|
|
316
|
+
// Read current values
|
|
317
|
+
const color = this.riveRef().getColor('backgroundColor');
|
|
318
|
+
// color: { r: 0, g: 255, b: 0, a: 255 }
|
|
319
|
+
|
|
320
|
+
const score = this.riveRef().getDataBinding('score');
|
|
321
|
+
// score: 100 (auto-detected as number)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### Color Utilities
|
|
327
|
+
|
|
328
|
+
The library exports color conversion utilities for advanced use cases:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { parseRiveColor, riveColorToArgb, riveColorToHex } from '@grandgular/rive-angular';
|
|
332
|
+
|
|
333
|
+
// Parse various color formats
|
|
334
|
+
const color1 = parseRiveColor('#FF5733'); // { r: 255, g: 87, b: 51, a: 255 }
|
|
335
|
+
const color2 = parseRiveColor('#FF573380'); // { r: 255, g: 87, b: 51, a: 128 }
|
|
336
|
+
const color3 = parseRiveColor(0x80FF5733); // { r: 255, g: 87, b: 51, a: 128 }
|
|
337
|
+
const color4 = parseRiveColor({ r: 255, g: 0, b: 0, a: 255 });
|
|
338
|
+
|
|
339
|
+
// Convert to ARGB integer
|
|
340
|
+
const argb = riveColorToArgb({ r: 255, g: 0, b: 0, a: 255 }); // 0xFFFF0000
|
|
341
|
+
|
|
342
|
+
// Convert to hex string
|
|
343
|
+
const hex = riveColorToHex({ r: 255, g: 0, b: 0, a: 255 }); // '#FF0000FF'
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### Selecting a ViewModel
|
|
347
|
+
|
|
348
|
+
If your `.riv` file contains multiple ViewModels, specify which one to use:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
<rive-canvas
|
|
352
|
+
src="assets/animation.riv"
|
|
353
|
+
viewModelName="GameViewModel"
|
|
354
|
+
[dataBindings]="{ score: 42 }"
|
|
355
|
+
/>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
If `viewModelName` is not provided, the default ViewModel for the artboard is used.
|
|
359
|
+
|
|
360
|
+
#### Controlled vs Uncontrolled
|
|
361
|
+
|
|
362
|
+
Same semantics as Text Runs:
|
|
363
|
+
|
|
364
|
+
- **Controlled**: Keys in `dataBindings` input — managed by Angular, input is source of truth
|
|
365
|
+
- **Uncontrolled**: Keys not in `dataBindings` — managed imperatively via methods
|
|
366
|
+
- **Warning**: Calling `setDataBinding()` or `setColor()` on a controlled key logs a warning and the change will be overwritten on next input update
|
|
367
|
+
|
|
368
|
+
#### Validation and Error Handling
|
|
369
|
+
|
|
370
|
+
Imperative methods (`setDataBinding`, `setColor`, `setColorOpacity`, `fireViewModelTrigger`) emit validation errors via the `loadError` output when:
|
|
371
|
+
|
|
372
|
+
- Property path doesn't exist in the ViewModel (`RIVE_402`)
|
|
373
|
+
- Value type doesn't match property type (`RIVE_403`)
|
|
374
|
+
- Color format is invalid (hex string, ARGB integer, or RiveColor object expected)
|
|
375
|
+
- Opacity value is out of range (must be between 0.0 and 1.0)
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
<rive-canvas
|
|
379
|
+
src="assets/animation.riv"
|
|
380
|
+
(loadError)="handleError($event)"
|
|
381
|
+
/>
|
|
382
|
+
|
|
383
|
+
handleError(error: Error) {
|
|
384
|
+
if (error instanceof RiveValidationError) {
|
|
385
|
+
console.error('Validation error:', error.code, error.message);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Advanced: Direct ViewModel Access
|
|
391
|
+
|
|
392
|
+
For advanced scenarios, access the ViewModel instance directly:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
riveRef = viewChild.required(RiveCanvasComponent);
|
|
396
|
+
|
|
397
|
+
advancedUsage() {
|
|
398
|
+
const vmi = this.riveRef().viewModelInstance();
|
|
399
|
+
if (vmi) {
|
|
400
|
+
// Direct access to ViewModel SDK methods
|
|
401
|
+
const colorProp = vmi.color('backgroundColor');
|
|
402
|
+
if (colorProp) {
|
|
403
|
+
colorProp.rgba(255, 0, 0, 255);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
194
409
|
### Preloading files with RiveFileService
|
|
195
410
|
|
|
196
411
|
For better performance, you can preload and cache .riv files:
|
|
@@ -304,6 +519,9 @@ In this case, `onError` receives a `RiveValidationError` with code `RIVE_201`, a
|
|
|
304
519
|
| `RIVE_205` | Validation | Text run not found |
|
|
305
520
|
| `RIVE_301` | Config | No animation source provided |
|
|
306
521
|
| `RIVE_302` | Config | Invalid canvas element |
|
|
522
|
+
| `RIVE_401` | Data Binding | ViewModel not found |
|
|
523
|
+
| `RIVE_402` | Data Binding | Property not found in ViewModel |
|
|
524
|
+
| `RIVE_403` | Data Binding | Type mismatch (value doesn't match property type) |
|
|
307
525
|
|
|
308
526
|
## API Reference
|
|
309
527
|
|