From 5c0b9cd613873ee565b4ec40d71042c3850a502c Mon Sep 17 00:00:00 2001 From: Zach Plata Date: Tue, 19 Apr 2022 12:23:56 -0700 Subject: [PATCH] Breaking: Spread non-style props onto canvas element instead of containing div --- README.md | 78 +++++++++++++++++++++++++---------------- src/components/Rive.tsx | 2 +- src/hooks/useRive.tsx | 32 ++++++++++------- src/types.ts | 2 +- test/Rive.test.tsx | 33 +++++++++++++++++ test/useRive.test.tsx | 28 +++++++++++++++ 6 files changed, 130 insertions(+), 45 deletions(-) create mode 100644 test/Rive.test.tsx diff --git a/README.md b/README.md index 2d6890a..086e34e 100644 --- a/README.md +++ b/README.md @@ -22,34 +22,6 @@ npm i --save rive-react _Note: This library is using React hooks so the minimum version required for both react and react-dom is 16.8.0._ -### Migrating from version 0.0.x to 1.x.x - -Starting in v 1.0.0, we've migrated from wrapping around the `@rive-app/canvas` runtime (which uses the `CanvasRendereringContext2D` renderer) to the `@rive-app/webgl` runtime (which uses the WebGL renderer). The high-level API doesn't require any change to upgrade, but there are some notes to consider about the backing renderer. - -The backing `WebGL` runtime allows for best performance across all devices, as well as support for some features that are not supported in the `canvas` renderer runtime. To allow the `react` runtime to support some of the newer features in Rive, we needed to switch the `rive-react` backing runtime to `@rive-app/webgl`. - -One note about this switch is that some browsers may limit the number of concurrent WebGL contexts. For example, Chrome may only support up to 16 contexts concurrently. We pass a property called `useOffscreenRenderer` set to true to the backing runtime when instantiating Rive by default, which helps to manage the lifecycle of the `canvas` with a single offscreen `WebGL` context, even if there are many Rive animations on the screen (i.e 16+). If you need a single `WebGL` context per Rive animation/instance, pass in the `useOffscreenRenderer` property set to `false` in the `useRive` options, or as a prop in the default export component from this runtime. See below for an example: - -```js -const {rive, RiveComponent} = useRive({ - src: 'foo.riv', -}, { - // Default (you don't need to set this) - useOffscreenRenderer: true, - // To override and use one context per Rive instance, uncomment and use the line below - // useOffscreenRenderer: false, -}); - -// or you can override the flag in JSX via props -return ( - -); -``` - -### Migrating from version 1.x.x to 2.x.x - -In most cases, you may be able to migrate safely. We are mainly enabling `rive-react` to work with both backing renderers `@rive-app/webgl` and `@rive-app/canvas`, such that you can use either `@rive-app/react-canvas` or `@rive-app/react-webgl` as the dependency in your React applications. Another change that is mostly internal is that by default, `rive-react` will now use `@rive-app/canvas` (as opposed to `@rive-app/webgl`) to wrap around, as it currently yields the fastest performance across devices. Therefore, **we recommend installing `@rive-app/react-canvas` in your applicaions**. However, if you need a WebGL backing renderer, you may want to use `@rive-app/react-webgl`. - ## Usage ### Component @@ -72,7 +44,17 @@ export default Example; - `artboard`: _(optional)_ Name to display. - `animations`: _(optional)_ Name or list of names of animtions to play. - `layout`: _(optional)_ Layout object to define how animations are displayed on the canvas. See [Rive.js](https://github.com/rive-app/rive-wasm#layout) for more details. -- _All attributes and eventHandlers that can be passed to a `div` element can also be passed to the `Rive` component and used in the same manner._ +- _All attributes and eventHandlers that can be passed to a `canvas` element can also be passed to the `Rive` component and used in the same manner._ + +#### Styles and Classes + +When rendering out a Rive component, in the DOM, it will show as a `
` element that contains the `` element that powers the Rive animations. The purpose of the `
` element is to help control the sizing of the component. By default, the container has the following styles set on the `style` attribute: +```css +width: 100%; +height: 100%; +``` + +If you decide to pass in a `className` to the Rive component, you will override these attributes, and you will need to either set these style attributes in your CSS associated with that `className`, or set your own sizing preferences. ### useRive Hook @@ -106,7 +88,7 @@ export default Example; #### Return Values -- `RiveComponent`: A Component that can be used to display your .riv file. This component accepts the same attributes and event handlers as a `div` element. +- `RiveComponent`: A Component that can be used to display your .riv file. This component accepts the same attributes and event handlers as a `canvas` element. - `rive`: A Rive.js `Rive` object. This will return as null until the .riv file has fully loaded. - `canvas`: HTMLCanvasElement object, on which the .riv file is rendering. - `setCanvasRef`: A callback ref that can be passed to your own canvas element, if you wish to have control over the rendering of the Canvas element. @@ -182,3 +164,39 @@ A Rive.js `stateMachineInput` object. ## Examples The [examples](examples) shows a number of different ways to use Rive React. See the instructions for each example to run locally. + + +## Migration notes + +### Migrating from version 0.0.x to 1.x.x + +Starting in v 1.0.0, we've migrated from wrapping around the `@rive-app/canvas` runtime (which uses the `CanvasRendereringContext2D` renderer) to the `@rive-app/webgl` runtime (which uses the WebGL renderer). The high-level API doesn't require any change to upgrade, but there are some notes to consider about the backing renderer. + +The backing `WebGL` runtime allows for best performance across all devices, as well as support for some features that are not supported in the `canvas` renderer runtime. To allow the `react` runtime to support some of the newer features in Rive, we needed to switch the `rive-react` backing runtime to `@rive-app/webgl`. + +One note about this switch is that some browsers may limit the number of concurrent WebGL contexts. For example, Chrome may only support up to 16 contexts concurrently. We pass a property called `useOffscreenRenderer` set to true to the backing runtime when instantiating Rive by default, which helps to manage the lifecycle of the `canvas` with a single offscreen `WebGL` context, even if there are many Rive animations on the screen (i.e 16+). If you need a single `WebGL` context per Rive animation/instance, pass in the `useOffscreenRenderer` property set to `false` in the `useRive` options, or as a prop in the default export component from this runtime. See below for an example: + +```js +const {rive, RiveComponent} = useRive({ + src: 'foo.riv', +}, { + // Default (you don't need to set this) + useOffscreenRenderer: true, + // To override and use one context per Rive instance, uncomment and use the line below + // useOffscreenRenderer: false, +}); + +// or you can override the flag in JSX via props +return ( + +); +``` + +### Migrating from version 1.x.x to 2.x.x + +#### Package split + +In most cases, you may be able to migrate safely. We are mainly enabling the React runtime to work with both backing renderers `@rive-app/webgl` and `@rive-app/canvas`, such that you can use either `@rive-app/react-canvas` or `@rive-app/react-webgl` as the dependency in your React applications. Another change that is mostly internal is that by default, `rive-react` will now use `@rive-app/canvas` (as opposed to `@rive-app/webgl`) to wrap around, as it currently yields the fastest performance across devices. Therefore, **we recommend installing `@rive-app/react-canvas` in your applicaions**. However, if you need a WebGL backing renderer, you may want to use `@rive-app/react-webgl`. + +#### Classes, styles, and component props +Starting in v2.0, we introduce one breaking change where any non-style props set on the `RiveComponent` (i.e `aria-*`, `role`, etc.) will be set on the inner `` element. Previously, all extra props would be set onto the containing `
` element. Both the `className` and `style` props will continue to be set on the `
` element that wraps the canvas, as this dictates the sizing of the Rive component. diff --git a/src/components/Rive.tsx b/src/components/Rive.tsx index db4b568..5991d30 100644 --- a/src/components/Rive.tsx +++ b/src/components/Rive.tsx @@ -17,7 +17,7 @@ const Rive = ({ layout, useOffscreenRenderer = true, ...rest -}: RiveProps & ComponentProps<'div'>) => { +}: RiveProps & ComponentProps<'canvas'>) => { const params = { src, artboard, diff --git a/src/hooks/useRive.tsx b/src/hooks/useRive.tsx index 99fa5d4..7b9c5f0 100644 --- a/src/hooks/useRive.tsx +++ b/src/hooks/useRive.tsx @@ -23,20 +23,23 @@ type RiveComponentProps = { function RiveComponent({ setContainerRef, setCanvasRef, + className = '', + style, ...rest -}: RiveComponentProps & ComponentProps<'div'>) { +}: RiveComponentProps & ComponentProps<'canvas'>) { const containerStyle = { width: '100%', height: '100%', + ...style, }; return (
- +
); } @@ -235,15 +238,18 @@ export default function useRive( } }, [animations, rive]); - const Component = useCallback((props: ComponentProps<'div'>): JSX.Element => { - return ( - - ); - }, []); + const Component = useCallback( + (props: ComponentProps<'canvas'>): JSX.Element => { + return ( + + ); + }, + [] + ); return { canvas: canvasRef.current, diff --git a/src/types.ts b/src/types.ts index c9345e2..bec2477 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,5 +29,5 @@ export type RiveState = { setCanvasRef: RefCallback; setContainerRef: RefCallback; rive: Rive | null; - RiveComponent: (props: ComponentProps<'div'>) => JSX.Element; + RiveComponent: (props: ComponentProps<'canvas'>) => JSX.Element; }; diff --git a/test/Rive.test.tsx b/test/Rive.test.tsx new file mode 100644 index 0000000..5700127 --- /dev/null +++ b/test/Rive.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import RiveComponent from '../src/components/Rive'; +import {render} from '@testing-library/react' + +jest.mock('@rive-app/webgl', () => ({ + Rive: jest.fn().mockImplementation(() => ({ + on: jest.fn(), + stop: jest.fn(), + })), + Layout: jest.fn(), + Fit: { + Cover: 'cover', + }, + Alignment: { + Center: 'center', + }, + EventType: { + Load: 'load', + }, + StateMachineInputType: { + Number: 1, + Boolean: 2, + Trigger: 3, + }, +})); + +describe('Rive Component', () => { + it('renders the component as a canvas and a div wrapper', () => { + const {container, getByLabelText} = render(); + expect(container.firstChild).toHaveClass('container-styles'); + expect(getByLabelText('Foo label').tagName).toEqual('CANVAS'); + }); +}); diff --git a/test/useRive.test.tsx b/test/useRive.test.tsx index ea1f2e0..e84de92 100644 --- a/test/useRive.test.tsx +++ b/test/useRive.test.tsx @@ -1,8 +1,10 @@ +import React from 'react'; import { mocked } from 'jest-mock'; import { renderHook, act } from '@testing-library/react-hooks'; import useRive from '../src/hooks/useRive'; import * as rive from '@rive-app/canvas'; +import { render } from '@testing-library/react'; jest.mock('@rive-app/canvas', () => ({ Rive: jest.fn().mockImplementation(() => ({ @@ -308,4 +310,30 @@ describe('useRive', () => { expect(stopMock).toBeCalledWith(['light']); expect(playMock).toBeCalledWith('dark'); }); + + it('does not set styles if className is passed in for the canvas container', async () => { + const params = { + src: 'file-src', + }; + + const riveMock = { + on: (_: string, cb: () => void) => cb(), + stop: jest.fn(), + stopRendering: jest.fn(), + }; + + // @ts-ignore + mocked(rive.Rive).mockImplementation(() => riveMock); + + const canvasSpy = document.createElement('canvas'); + const { result } = renderHook(() => useRive(params)); + + await act(async () => { + result.current.setCanvasRef(canvasSpy); + }); + + const {RiveComponent: RiveTestComponent} = result.current; + const {container} = render(); + expect(container.firstChild).not.toHaveStyle('width: 50%'); + }); });