Compare commits

..

7 Commits

16 changed files with 350 additions and 421 deletions

6
.gitignore vendored
View File

@@ -1,10 +1,8 @@
node_modules
dist/types
package.json
dist
.DS_Store
.env
.idea
.vscode
examples/**/package-lock.json
package-lock.json
npm
package-lock.json

View File

@@ -4,9 +4,120 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v3.0.18](https://github.com/rive-app/rive-react/compare/v3.0.1...v3.0.18)
#### [v3.0.19](https://github.com/rive-app/rive-react/compare/v3.0.18...v3.0.19)
- fix tests that were automatically calling the rive load callback to be more controlled [`16d836c`](https://github.com/rive-app/rive-react/commit/16d836c95928e4294b565ecb444d517653c4988b)
#### [v3.0.18](https://github.com/rive-app/rive-react/compare/v3.0.17...v3.0.18)
> 14 July 2022
- update canvas dimensions to use clientWidth and Height as opposed to BoundingClient, to avoid getting scaled information [`fd1c00a`](https://github.com/rive-app/rive-react/commit/fd1c00a995374634ec8552d20a0f7094fcb04e25)
- chore: release 3.0.18 [`d010a55`](https://github.com/rive-app/rive-react/commit/d010a55cc0c065c353dd5424a6fef8a58f416c61)
#### [v3.0.17](https://github.com/rive-app/rive-react/compare/v3.0.16...v3.0.17)
> 14 July 2022
- add resizeObserver to replace window listeners for all but IE [`e966316`](https://github.com/rive-app/rive-react/commit/e966316971d88a7242651a0b1fa3a1eaff48d276)
- refactored the IE check into `useSize` [`5be9d2f`](https://github.com/rive-app/rive-react/commit/5be9d2f8741224ed7cd291898b1abe88668b3fed)
- Fix useEffects so they're not in conditional statements [`ec61a68`](https://github.com/rive-app/rive-react/commit/ec61a6835d9ca6158538f5d1ac5b6b861c58ac57)
#### [v3.0.16](https://github.com/rive-app/rive-react/compare/v3.0.15...v3.0.16)
> 12 July 2022
- Docs: Condense down README and add CONTRIBUTING guide [`0863835`](https://github.com/rive-app/rive-react/commit/08638359bb817213fb861950a20cae7e7b27111f)
- staged work [`7dbade4`](https://github.com/rive-app/rive-react/commit/7dbade4589ca0524b58f9abbdcc38afa3e3b1866)
- chore: release 3.0.16 [`ae6efc1`](https://github.com/rive-app/rive-react/commit/ae6efc14d46c33b90fe89ee067347296daf865e7)
#### [v3.0.15](https://github.com/rive-app/rive-react/compare/v3.0.14...v3.0.15)
> 28 June 2022
- chore: release 3.0.15 [`8175c4a`](https://github.com/rive-app/rive-react/commit/8175c4a4d406ac80703a6df346f3b5562d2e9311)
- Patch: Bump js runtime dependencies for nested artboard display patch [`795ee53`](https://github.com/rive-app/rive-react/commit/795ee533405ec98457db074d11730849e1be5c88)
#### [v3.0.14](https://github.com/rive-app/rive-react/compare/v3.0.12...v3.0.14)
> 28 June 2022
- Deploying to main from @ 3477afdef166251f35f1778a3143ff6c6efecc58 🚀 [`7aee5cf`](https://github.com/rive-app/rive-react/commit/7aee5cfab4eaca1fc9369742639507a770c4f756)
- Fix: Intake JS runtime patches for starting animation frames [`3477afd`](https://github.com/rive-app/rive-react/commit/3477afdef166251f35f1778a3143ff6c6efecc58)
- chore: release 3.0.14 [`04353db`](https://github.com/rive-app/rive-react/commit/04353db43266f6dcf40f4ef7f3be23afa13c2e0d)
#### [v3.0.12](https://github.com/rive-app/rive-react/compare/v3.0.11...v3.0.12)
> 22 June 2022
- chore: release 3.0.12 [`8b43a82`](https://github.com/rive-app/rive-react/commit/8b43a82c5f56cbb5b1fe7dacfa7ca8457fc6d413)
- Fix: Bump cpp to get nested artboard opacity fix and fill rule patch [`bd49e6a`](https://github.com/rive-app/rive-react/commit/bd49e6a4ee66c68005b60a670700ef69b5322656)
- Bump @rive-app/canvas to take the fillRule bug fix [`1dbb9cd`](https://github.com/rive-app/rive-react/commit/1dbb9cd38d41393b9f354cdf81e88c702aa3ae64)
#### [v3.0.11](https://github.com/rive-app/rive-react/compare/v3.0.10...v3.0.11)
> 22 June 2022
- chore: release 3.0.11 [`aee7407`](https://github.com/rive-app/rive-react/commit/aee7407f7921c515f3c1d9aabf87387baddb4064)
- Docs: Code snippets update to use new React package structure [`b48de9d`](https://github.com/rive-app/rive-react/commit/b48de9db8496be35f29bea87273a7a9fceefdafc)
#### [v3.0.10](https://github.com/rive-app/rive-react/compare/v3.0.8...v3.0.10)
> 20 June 2022
- Deploying to main from @ 5ad5a957a6e8f10abedc23f46033d4792e29dfe5 🚀 [`802648e`](https://github.com/rive-app/rive-react/commit/802648eda8fa0e5a0a35c66af06e476eac59fe9e)
- chore: release 3.0.10 [`6772f16`](https://github.com/rive-app/rive-react/commit/6772f166b7f3e4747ae508a54e2533bb3ea49878)
- Maint: Update docs for storybook link [`5ad5a95`](https://github.com/rive-app/rive-react/commit/5ad5a957a6e8f10abedc23f46033d4792e29dfe5)
#### [v3.0.8](https://github.com/rive-app/rive-react/compare/v3.0.7...v3.0.8)
> 9 June 2022
- Maint: Add GH workflow for deploying storybook to Github Pages [`38625a0`](https://github.com/rive-app/rive-react/commit/38625a00c313192d0edbe1c3a855bea1ec56bd2b)
- chore: release 3.0.8 [`414d6f8`](https://github.com/rive-app/rive-react/commit/414d6f895ac2184876dec90959c17c2b22f6843f)
#### [v3.0.7](https://github.com/rive-app/rive-react/compare/v3.0.6...v3.0.7)
> 8 June 2022
- Feat: Move existing examples into Storybook and add documentation [`ec230fa`](https://github.com/rive-app/rive-react/commit/ec230faa738202cedad14cc866e30c4c03efffd7)
- chore: release 3.0.7 [`bad688d`](https://github.com/rive-app/rive-react/commit/bad688dfa3841ec07e30fa07609a6cb7bb7c1688)
#### [v3.0.6](https://github.com/rive-app/rive-react/compare/v3.0.5...v3.0.6)
> 6 June 2022
- chore: release 3.0.6 [`90c6d1e`](https://github.com/rive-app/rive-react/commit/90c6d1edb1d4bef6250dd4a5101a7cfe04ff9ce9)
- Maint: Roll canvas and webgl dependencies forward to support nested state machines [`0480dc9`](https://github.com/rive-app/rive-react/commit/0480dc92c842265d601d08b60fb49392969cfd9e)
#### [v3.0.5](https://github.com/rive-app/rive-react/compare/v3.0.4...v3.0.5)
> 26 May 2022
- chore: release 3.0.5 [`de24fa5`](https://github.com/rive-app/rive-react/commit/de24fa564117d4acbe60b4cf734abd9e951b30f1)
- Feat: Add stateMachines param to the default Rive component [`84d9730`](https://github.com/rive-app/rive-react/commit/84d9730767a62c63e743d5a04bba5b3d480ea38d)
- Maint: Bump wasm for another listener patch [`805afd5`](https://github.com/rive-app/rive-react/commit/805afd5dff2888294926c32ec07f5e24db804d09)
#### [v3.0.4](https://github.com/rive-app/rive-react/compare/v3.0.3...v3.0.4)
> 23 May 2022
- chore: release 3.0.4 [`9abee34`](https://github.com/rive-app/rive-react/commit/9abee34d12641f845b93febf438df0f77f72153f)
- Maint: Bump rive-wasm dependency for listener patches [`12801b1`](https://github.com/rive-app/rive-react/commit/12801b10cc8980339e5856d71d96da3c612cb291)
#### [v3.0.3](https://github.com/rive-app/rive-react/compare/v3.0.2...v3.0.3)
> 17 May 2022
- Feat: Bump wasm and add examples to support touch feature [`3902948`](https://github.com/rive-app/rive-react/commit/3902948a2ef8af6955ef12124207edee29eb0be8)
- chore: release 3.0.3 [`da11387`](https://github.com/rive-app/rive-react/commit/da1138755861aadb9e7c6cb0028f2120d610a6c5)
#### [v3.0.2](https://github.com/rive-app/rive-react/compare/v3.0.1...v3.0.2)
> 17 May 2022
- chore: release 3.0.2 [`21a17ed`](https://github.com/rive-app/rive-react/commit/21a17ed40ee51263c666dde48b6c55e958eceeb8)
- Maint: Bump wasm dependencies [`f0e7092`](https://github.com/rive-app/rive-react/commit/f0e70924ec9849f45ecddda801ad63e1d87b1bdb)
#### [v3.0.1](https://github.com/rive-app/rive-react/compare/v3.0.0...v3.0.1)

350
dist/index.esm.js vendored
View File

@@ -1,350 +0,0 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Rive as Rive$1, EventType } from '@rive-app/canvas';
export * from '@rive-app/canvas';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function useWindowSize() {
var _a = useState({
width: 0,
height: 0,
}), windowSize = _a[0], setWindowSize = _a[1];
useEffect(function () {
if (typeof window !== 'undefined') {
var handleResize_1 = function () {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize_1);
handleResize_1();
return function () { return window.removeEventListener('resize', handleResize_1); };
}
}, []);
return windowSize;
}
// grabbed from: https://stackoverflow.com/questions/19999388/check-if-user-is-using-ie
// There is a shorter version, but that one ran into type issues with typescript.
function isIE() {
/**
* detect IEEdge
* returns version of IE/Edge or false, if browser is not a Microsoft browser
*/
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) {
// IE 10 or older => return version number
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
var trident = ua.indexOf('Trident/');
if (trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('Edge/');
if (edge > 0) {
// Edge => return version number
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
}
// other browser
return false;
}
function RiveComponent(_a) {
var setContainerRef = _a.setContainerRef, setCanvasRef = _a.setCanvasRef, _b = _a.className, className = _b === void 0 ? '' : _b, style = _a.style, rest = __rest(_a, ["setContainerRef", "setCanvasRef", "className", "style"]);
var containerStyle = __assign({ width: '100%', height: '100%' }, style);
return (React.createElement("div", __assign({ ref: setContainerRef, className: className }, (!className && { style: containerStyle })),
React.createElement("canvas", __assign({ ref: setCanvasRef, style: { verticalAlign: 'top' } }, rest))));
}
var defaultOptions = {
useDevicePixelRatio: true,
fitCanvasToArtboardHeight: false,
useOffscreenRenderer: true,
};
/**
* Returns options, with defaults set.
*
* @param opts
* @returns
*/
function getOptions(opts) {
return Object.assign({}, defaultOptions, opts);
}
/**
* Custom Hook for loading a Rive file.
*
* Waits until the load event has fired before returning it.
* We can then listen for changes to this animation in other hooks to detect
* when it has loaded.
*
* @param riveParams - Object containing parameters accepted by the Rive object
* in the rive-js runtime, with the exception of Canvas as that is attached
* via the ref callback `setCanvasRef`.
*
* @param opts - Optional list of options that are specific for this hook.
* @returns {RiveAnimationState}
*/
function useRive(riveParams, opts) {
if (opts === void 0) { opts = {}; }
var canvasRef = useRef(null);
var containerRef = useRef(null);
var _a = useState(null), rive = _a[0], setRive = _a[1];
var _b = useState({
height: 0,
width: 0,
}), dimensions = _b[0], setDimensions = _b[1];
// Listen to changes in the window sizes and update the bounds when changes
// occur.
var windowSize = useWindowSize();
// when the container dimensions change, we need to re evaluate our dimensions.
var _c = useState({
height: 0,
width: 0,
}), containerDimensions = _c[0], setContainerDimensions = _c[1];
var isParamsLoaded = Boolean(riveParams);
var options = getOptions(opts);
/**
* Gets the intended dimensions of the canvas element.
*
* The intended dimensions are those of the container element, unless the
* option `fitCanvasToArtboardHeight` is true, then they are adjusted to
* the height of the artboard.
*
* @returns Dimensions object.
*/
function getCanvasDimensions() {
var _a, _b;
var _c = (_b = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) !== null && _b !== void 0 ? _b : new DOMRect(0, 0, 0, 0), width = _c.width, height = _c.height;
if (rive && options.fitCanvasToArtboardHeight) {
var _d = rive.bounds, maxY = _d.maxY, maxX = _d.maxX;
return { width: width, height: width * (maxY / maxX) };
}
return { width: width, height: height };
}
/**
* Updates the width and height of the canvas.
*/
function updateBounds() {
if (!containerRef.current) {
return;
}
var _a = getCanvasDimensions(), width = _a.width, height = _a.height;
var boundsChanged = width !== dimensions.width || height !== dimensions.height;
if (canvasRef.current && rive && boundsChanged) {
if (options.fitCanvasToArtboardHeight) {
containerRef.current.style.height = height + 'px';
}
if (options.useDevicePixelRatio) {
var dpr = window.devicePixelRatio || 1;
canvasRef.current.width = dpr * width;
canvasRef.current.height = dpr * height;
canvasRef.current.style.width = width + 'px';
canvasRef.current.style.height = height + 'px';
}
else {
canvasRef.current.width = width;
canvasRef.current.height = height;
}
setDimensions({ width: width, height: height });
// Updating the canvas width or height will clear the canvas, so call
// startRendering() to redraw the current frame as the animation might
// be paused and not advancing.
rive.startRendering();
}
// Always resize to Canvas
if (rive) {
rive.resizeToCanvas();
}
}
/**
* Listen to changes on the windowSize and the rive file being loaded
* and update the canvas bounds as needed.
*
* ie does not support ResizeObservers, so we fallback to the window listener there
*/
useEffect(function () {
if (isIE() && rive) {
updateBounds();
}
}, [rive, windowSize]);
var observer = useRef(new ResizeObserver(function (entries) {
setContainerDimensions(entries[entries.length - 1].contentRect);
}));
useEffect(function () {
if (!isIE() && rive) {
updateBounds();
}
}, [rive, containerDimensions]);
useEffect(function () {
if (!isIE() && containerRef.current) {
observer.current.observe(containerRef.current);
}
return function () {
observer.current.disconnect();
};
}, [containerRef.current, observer]);
/**
* Ref callback called when the canvas element mounts and unmounts.
*/
var setCanvasRef = useCallback(function (canvas) {
if (canvas && riveParams) {
var useOffscreenRenderer = options.useOffscreenRenderer;
var r_1 = new Rive$1(__assign(__assign({ useOffscreenRenderer: useOffscreenRenderer }, riveParams), { canvas: canvas }));
r_1.on(EventType.Load, function () { return setRive(r_1); });
}
else if (canvas === null && canvasRef.current) {
canvasRef.current.height = 0;
canvasRef.current.width = 0;
}
canvasRef.current = canvas;
}, [isParamsLoaded]);
/**
* Ref callback called when the container element mounts
*/
var setContainerRef = useCallback(function (container) {
containerRef.current = container;
}, []);
/**
* Set up IntersectionObserver to stop rendering if the animation is not in
* view.
*/
useEffect(function () {
var observer = new IntersectionObserver(function (_a) {
var entry = _a[0];
entry.isIntersecting
? rive && rive.startRendering()
: rive && rive.stopRendering();
});
if (canvasRef.current) {
observer.observe(canvasRef.current);
}
return function () {
observer.disconnect();
};
}, [rive]);
/**
* On unmount, stop rive from rendering.
*/
useEffect(function () {
return function () {
if (rive) {
rive.stop();
setRive(null);
}
};
}, [rive]);
/**
* Listen for changes in the animations params
*/
var animations = riveParams === null || riveParams === void 0 ? void 0 : riveParams.animations;
useEffect(function () {
if (rive && animations) {
if (rive.isPlaying) {
rive.stop(rive.animationNames);
rive.play(animations);
}
else if (rive.isPaused) {
rive.stop(rive.animationNames);
rive.pause(animations);
}
}
}, [animations, rive]);
var Component = useCallback(function (props) {
return (React.createElement(RiveComponent, __assign({ setContainerRef: setContainerRef, setCanvasRef: setCanvasRef }, props)));
}, []);
return {
canvas: canvasRef.current,
setCanvasRef: setCanvasRef,
setContainerRef: setContainerRef,
rive: rive,
RiveComponent: Component,
};
}
var Rive = function (_a) {
var src = _a.src, artboard = _a.artboard, animations = _a.animations, stateMachines = _a.stateMachines, layout = _a.layout, _b = _a.useOffscreenRenderer, useOffscreenRenderer = _b === void 0 ? true : _b, rest = __rest(_a, ["src", "artboard", "animations", "stateMachines", "layout", "useOffscreenRenderer"]);
var params = {
src: src,
artboard: artboard,
animations: animations,
layout: layout,
stateMachines: stateMachines,
autoplay: true,
};
var options = {
useOffscreenRenderer: useOffscreenRenderer,
};
var RiveComponent = useRive(params, options).RiveComponent;
return React.createElement(RiveComponent, __assign({}, rest));
};
/**
* Custom hook for fetching a stateMachine input from a rive file.
*
* @param rive - Rive instance
* @param stateMachineName - Name of the state machine
* @param inputName - Name of the input
* @returns
*/
function useStateMachineInput(rive, stateMachineName, inputName, initialValue) {
var _a = useState(null), input = _a[0], setInput = _a[1];
useEffect(function () {
if (!rive || !stateMachineName || !inputName) {
setInput(null);
}
if (rive && stateMachineName && inputName) {
var inputs = rive.stateMachineInputs(stateMachineName);
if (inputs) {
var selectedInput = inputs.find(function (input) { return input.name === inputName; });
if (initialValue !== undefined && selectedInput) {
selectedInput.value = initialValue;
}
setInput(selectedInput || null);
}
}
else {
setInput(null);
}
}, [rive]);
return input;
}
export { Rive as default, useRive, useStateMachineInput };

1
dist/index.js vendored
View File

@@ -1 +0,0 @@
Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("@rive-app/canvas");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e),i=function(){return i=Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var i in t=arguments[n])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e},i.apply(this,arguments)};function a(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]])}return n}var u=function(){function e(){}return e.prototype.observe=function(){},e.prototype.unobserve=function(){},e.prototype.disconnect=function(){},e}(),o=globalThis.ResizeObserver||u,s=void 0!==globalThis.ResizeObserver;function c(e){var t=e.setContainerRef,n=e.setCanvasRef,u=e.className,o=void 0===u?"":u,s=e.style,c=a(e,["setContainerRef","setCanvasRef","className","style"]),f=i({width:"100%",height:"100%"},s);return r.default.createElement("div",i({ref:t,className:o},!o&&{style:f}),r.default.createElement("canvas",i({ref:n,style:{verticalAlign:"top"}},c)))}var f={useDevicePixelRatio:!0,fitCanvasToArtboardHeight:!1,useOffscreenRenderer:!0};function l(n,a){void 0===a&&(a={});var u=e.useRef(null),l=e.useRef(null),d=e.useState(null),v=d[0],h=d[1],p=e.useState({height:0,width:0}),g=p[0],w=p[1],y=function(t){var n=e.useState({width:0,height:0}),r=n[0],i=n[1];e.useEffect(function(){if("undefined"!=typeof window){var e=function(){i({width:window.innerWidth,height:window.innerHeight})};return s||(window.addEventListener("resize",e),e()),function(){return window.removeEventListener("resize",e)}}},[]);var a=e.useRef(new o(function(e){i({width:e[e.length-1].contentRect.width,height:e[e.length-1].contentRect.height})}));return e.useEffect(function(){var e=a.current;return t.current&&e.observe(t.current),function(){e.disconnect()}},[t,a]),r}(l),b=Boolean(n),R=function(e){return Object.assign({},f,e)}(a);function O(){if(l.current){var e=function(){var e,t,n,r,i=null!==(t=null===(e=l.current)||void 0===e?void 0:e.clientWidth)&&void 0!==t?t:0,a=null!==(r=null===(n=l.current)||void 0===n?void 0:n.clientHeight)&&void 0!==r?r:0;if(v&&R.fitCanvasToArtboardHeight){var u=v.bounds;return{width:i,height:i*(u.maxY/u.maxX)}}return{width:i,height:a}}(),t=e.width,n=e.height,r=t!==g.width||n!==g.height;if(u.current&&v&&r){if(R.fitCanvasToArtboardHeight&&(l.current.style.height=n+"px"),R.useDevicePixelRatio){var i=window.devicePixelRatio||1;u.current.width=i*t,u.current.height=i*n,u.current.style.width=t+"px",u.current.style.height=n+"px"}else u.current.width=t,u.current.height=n;w({width:t,height:n}),v.startRendering()}v&&v.resizeToCanvas()}}e.useEffect(function(){v&&O()},[v,y]);var m=e.useCallback(function(e){if(e&&n&&b){var r=R.useOffscreenRenderer,a=new t.Rive(i(i({useOffscreenRenderer:r},n),{canvas:e}));a.on(t.EventType.Load,function(){return h(a)})}else null===e&&u.current&&(u.current.height=0,u.current.width=0);u.current=e},[b]),C=e.useCallback(function(e){l.current=e},[]);e.useEffect(function(){var e=new IntersectionObserver(function(e){e[0].isIntersecting?v&&v.startRendering():v&&v.stopRendering()});return u.current&&e.observe(u.current),function(){e.disconnect()}},[v]),e.useEffect(function(){return function(){v&&(v.stop(),h(null))}},[v]);var x=null==n?void 0:n.animations;e.useEffect(function(){v&&x&&(v.isPlaying?(v.stop(v.animationNames),v.play(x)):v.isPaused&&(v.stop(v.animationNames),v.pause(x)))},[x,v]);var E=e.useCallback(function(e){return r.default.createElement(c,i({setContainerRef:C,setCanvasRef:m},e))},[]);return{canvas:u.current,setCanvasRef:m,setContainerRef:C,rive:v,RiveComponent:E}}exports.default=function(e){var t=e.src,n=e.artboard,u=e.animations,o=e.stateMachines,s=e.layout,c=e.useOffscreenRenderer,f=void 0===c||c,d=a(e,["src","artboard","animations","stateMachines","layout","useOffscreenRenderer"]),v=l({src:t,artboard:n,animations:u,layout:s,stateMachines:o,autoplay:!0},{useOffscreenRenderer:f}).RiveComponent;return r.default.createElement(v,i({},d))},exports.useRive=l,exports.useStateMachineInput=function(t,n,r,i){var a=e.useState(null),u=a[0],o=a[1];return e.useEffect(function(){if(t&&n&&r||o(null),t&&n&&r){var e=t.stateMachineInputs(n);if(e){var a=e.find(function(e){return e.name===r});void 0!==i&&a&&(a.value=i),o(a||null)}}else o(null)},[t]),u},Object.keys(t).forEach(function(e){"default"===e||exports.hasOwnProperty(e)||Object.defineProperty(exports,e,{enumerable:!0,get:function(){return t[e]}})});

View File

@@ -0,0 +1,32 @@
# To run
This is a basic showcase of a resize issue we have, to test this locally with rive-react changes
1. run `npm start`
2. run `npm run build` for the rive-react project
If you want to also test local wasm changes:
1. update `rive-react`/package.json to a locally checked out version of rive-wasm's canvas_single
`"@rive-app/canvas": "../rive-wasm/js/npm/canvas_single",`
2. cd to `rive-wasm/js` & run `npm run dev` (keep this going as it will watch for changes)
3. run `npm start`
4. run `npm run build` for the rive-react project
# Resize issue:
update parameters in `utils.ts` to see this for yourself.
Resizing from window.resize
- bottom animation moves slower than rest
- cannot deal with the animations container resizing, unless its linked to the window resizing
Resizing from ResizeObserver
- all animations move together super smooth when resizing
- top two animations flicker to white (blank canvas) when resizing
Resizing form ResizeObserver - throttled (current default)
- all animations move "together"
- resizing looks pretty smooth, but everything lags behind a bit.
# Other issues
its also very slow resizing when dev tools is open, its fine when closed though.

View File

@@ -0,0 +1,36 @@
{
"name": "basic-with-hook",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.13.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"react": "file:../../node_modules/react",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"rive-react": "file:../..",
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "SKIP_PREFLIGHT_CHECK=true react-scripts start"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>Rive React - Basic with Hook</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -0,0 +1,30 @@
import { useRive } from 'rive-react';
function App() {
const params = {
src: 'poison-loader.riv',
autoplay: true,
};
const { RiveComponent: RiveComponentBasic } = useRive(params);
const { RiveComponent: RiveComponentBasic2 } = useRive(params);
const { RiveComponent: RiveComponentBasic3 } = useRive(params);
return (
<>
<div style={{ width: '100%' }}>
<div style={{ height: '300px', width: '100%' }}>
<RiveComponentBasic />
</div>
<div style={{ height: '300px', width: '100%' }}>
<RiveComponentBasic2 />
</div>
<div style={{ height: '300px', width: '100%' }}>
<RiveComponentBasic3 />
</div>
</div>
</>
);
}
export default App;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -1,6 +1,6 @@
{
"name": "rive-react",
"version": "3.0.18",
"version": "3.0.19",
"description": "React wrapper around the rive-js library",
"main": "dist/index.js",
"typings": "dist/types/index.d.ts",

View File

@@ -181,7 +181,13 @@ export default function useRive(
...riveParams,
canvas,
});
r.on(EventType.Load, () => setRive(r));
r.on(EventType.Load, () => {
// Check if the component/canvas is mounted before setting state to avoid setState
// on an unmounted component in some rare cases
if (canvasRef.current) {
setRive(r);
}
});
} else if (canvas === null && canvasRef.current) {
canvasRef.current.height = 0;
canvasRef.current.width = 0;

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { Rive, StateMachineInput } from '@rive-app/canvas';
import { EventType, Rive, StateMachineInput } from '@rive-app/canvas';
/**
* Custom hook for fetching a stateMachine input from a rive file.
@@ -18,21 +18,33 @@ export default function useStateMachineInput(
const [input, setInput] = useState<StateMachineInput | null>(null);
useEffect(() => {
if (!rive || !stateMachineName || !inputName) {
setInput(null);
}
if (rive && stateMachineName && inputName) {
const inputs = rive.stateMachineInputs(stateMachineName);
if (inputs) {
const selectedInput = inputs.find((input) => input.name === inputName);
if (initialValue !== undefined && selectedInput) {
selectedInput.value = initialValue;
}
setInput(selectedInput || null);
function setStateMachineInput() {
if (!rive || !stateMachineName || !inputName) {
setInput(null);
}
} else {
setInput(null);
if (rive && stateMachineName && inputName) {
const inputs = rive.stateMachineInputs(stateMachineName);
if (inputs) {
const selectedInput = inputs.find(
(input) => input.name === inputName
);
if (initialValue !== undefined && selectedInput) {
selectedInput.value = initialValue;
}
setInput(selectedInput || null);
}
} else {
setInput(null);
}
}
setStateMachineInput();
if (rive) {
rive.on(EventType.Play, () => {
// Check if the component/canvas is mounted before setting state to avoid setState
// on an unmounted component in some rare cases
setStateMachineInput();
});
}
}, [rive]);

View File

@@ -8,8 +8,20 @@ class FakeResizeObserver {
disconnect() {}
}
function throttle(f: Function, delay: number) {
let timer = 0;
return function (this: Function, ...args: any) {
clearTimeout(timer);
timer = setTimeout(() => f.apply(this, args), delay) as unknown as number;
};
}
const MyResizeObserver = globalThis.ResizeObserver || FakeResizeObserver;
const hasResizeObserver = globalThis.ResizeObserver !== undefined;
const preferResizeObserver = true;
const throttleResizeObserver = true;
const useResizeObserver = hasResizeObserver && preferResizeObserver;
const useWindowListener = !useResizeObserver;
export function useSize(
containerRef: React.MutableRefObject<HTMLElement | null>
@@ -29,32 +41,48 @@ export function useSize(
});
};
if (!hasResizeObserver) {
if (useWindowListener) {
// only pay attention to window size changes when we do not have the resizeObserver (IE only)
window.addEventListener('resize', handleResize);
handleResize();
window.addEventListener('resize', handleResize);
}
return () => window.removeEventListener('resize', handleResize);
}
}, []);
const observer = useRef(
new MyResizeObserver((entries) => {
setSize({
width: entries[entries.length - 1].contentRect.width,
height: entries[entries.length - 1].contentRect.height,
});
})
new MyResizeObserver(
throttleResizeObserver
? throttle((entries: any) => {
if (useResizeObserver) {
setSize({
width: entries[entries.length - 1].contentRect.width,
height: entries[entries.length - 1].contentRect.height,
});
}
}, 16)
: (entries: any) => {
if (useResizeObserver) {
setSize({
width: entries[entries.length - 1].contentRect.width,
height: entries[entries.length - 1].contentRect.height,
});
}
}
)
);
useEffect(() => {
const current = observer.current;
if (containerRef.current) {
if (containerRef.current && useResizeObserver) {
current.observe(containerRef.current);
}
return () => {
current.disconnect();
if (containerRef.current && useResizeObserver) {
current.unobserve(containerRef.current);
}
};
}, [containerRef, observer]);

View File

@@ -29,6 +29,23 @@ jest.mock('@rive-app/canvas', () => ({
}));
describe('useRive', () => {
let controlledRiveloadCb: () => void;
let baseRiveMock: Partial<rive.Rive>;
beforeEach(() => {
baseRiveMock = {
on: (_: rive.EventType, cb: rive.EventCallback) =>
((controlledRiveloadCb as rive.EventCallback) = cb),
stop: jest.fn(),
stopRendering: jest.fn(),
startRendering: jest.fn(),
};
});
afterEach(() => {
controlledRiveloadCb = () => {};
});
it('returns rive as null if no params are passed', () => {
const { result } = renderHook(() => useRive());
expect(result.current.rive).toBe(null);
@@ -40,23 +57,17 @@ describe('useRive', () => {
src: 'file-src',
};
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
stopRendering: jest.fn(),
};
// @ts-ignore
mocked(rive.Rive).mockImplementation(() => riveMock);
mocked(rive.Rive).mockImplementation(() => baseRiveMock);
const canvasSpy = document.createElement('canvas');
const { result } = renderHook(() => useRive(params));
await act(async () => {
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
expect(result.current.rive).toBe(riveMock);
expect(result.current.rive).toBe(baseRiveMock);
expect(result.current.canvas).toBe(canvasSpy);
});
@@ -68,9 +79,7 @@ describe('useRive', () => {
const resizeToCanvasMock = jest.fn();
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
stopRendering: jest.fn(),
...baseRiveMock,
resizeToCanvas: resizeToCanvasMock,
};
@@ -84,6 +93,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
result.current.setContainerRef(containerSpy);
controlledRiveloadCb();
});
expect(result.current.rive).toBe(riveMock);
@@ -100,7 +110,7 @@ describe('useRive', () => {
const stopMock = jest.fn();
const riveMock = {
on: (_: string, cb: () => void) => cb(),
...baseRiveMock,
stop: stopMock,
};
@@ -112,6 +122,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
unmount();
@@ -126,13 +137,8 @@ describe('useRive', () => {
global.devicePixelRatio = 2;
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
};
// @ts-ignore
mocked(rive.Rive).mockImplementation(() => riveMock);
mocked(rive.Rive).mockImplementation(() => baseRiveMock);
const canvasSpy = document.createElement('canvas');
const containerSpy = document.createElement('div');
@@ -144,6 +150,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
result.current.setContainerRef(containerSpy);
controlledRiveloadCb();
});
// Height and width should be 2* the width and height returned from containers
@@ -164,13 +171,8 @@ describe('useRive', () => {
useDevicePixelRatio: false,
};
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
};
// @ts-ignore
mocked(rive.Rive).mockImplementation(() => riveMock);
mocked(rive.Rive).mockImplementation(() => baseRiveMock);
const canvasSpy = document.createElement('canvas');
const containerSpy = document.createElement('div');
@@ -182,6 +184,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
result.current.setContainerRef(containerSpy);
controlledRiveloadCb();
});
// Height and width should be same as containers bounding rect
@@ -199,8 +202,7 @@ describe('useRive', () => {
};
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
...baseRiveMock,
bounds: {
maxX: 100,
maxY: 50,
@@ -220,6 +222,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setContainerRef(containerSpy);
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
// Height and width should be same as containers bounding rect
@@ -243,8 +246,7 @@ describe('useRive', () => {
}));
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
...baseRiveMock,
bounds: {
maxX: 100,
maxY: 50,
@@ -260,6 +262,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
expect(observeMock).toBeCalledWith(canvasSpy);
@@ -277,7 +280,7 @@ describe('useRive', () => {
const stopMock = jest.fn();
const riveMock = {
on: (_: string, cb: () => void) => cb(),
...baseRiveMock,
stop: stopMock,
play: playMock,
animationNames: ['light'],
@@ -295,6 +298,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
rerender({
@@ -317,7 +321,7 @@ describe('useRive', () => {
const stopMock = jest.fn();
const riveMock = {
on: (_: string, cb: () => void) => cb(),
...baseRiveMock,
stop: stopMock,
play: playMock,
pause: pauseMock,
@@ -337,6 +341,7 @@ describe('useRive', () => {
await act(async () => {
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
rerender({
@@ -354,20 +359,15 @@ describe('useRive', () => {
src: 'file-src',
};
const riveMock = {
on: (_: string, cb: () => void) => cb(),
stop: jest.fn(),
stopRendering: jest.fn(),
};
// @ts-ignore
mocked(rive.Rive).mockImplementation(() => riveMock);
mocked(rive.Rive).mockImplementation(() => baseRiveMock);
const canvasSpy = document.createElement('canvas');
const { result } = renderHook(() => useRive(params));
await act(async () => {
result.current.setCanvasRef(canvasSpy);
controlledRiveloadCb();
});
const { RiveComponent: RiveTestComponent } = result.current;