{"id":19614315,"url":"https://github.com/fpapado/rive-offscreen-canvas","last_synced_at":"2026-06-06T20:31:41.473Z","repository":{"id":229601763,"uuid":"775351243","full_name":"fpapado/rive-offscreen-canvas","owner":"fpapado","description":"A demo/proof of concept of using Rive backed by an OffScreenCanvas.","archived":false,"fork":false,"pushed_at":"2024-03-25T08:52:51.000Z","size":83,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-09T10:29:12.930Z","etag":null,"topics":["animation","offscreencanvas","rive"],"latest_commit_sha":null,"homepage":"https://rive-offscreen-canvas-demo.netlify.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fpapado.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-21T08:22:26.000Z","updated_at":"2024-08-22T18:47:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"9ae62740-7855-45cc-9866-583b273c0508","html_url":"https://github.com/fpapado/rive-offscreen-canvas","commit_stats":null,"previous_names":["fpapado/rive-offscreen-canvas"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Frive-offscreen-canvas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Frive-offscreen-canvas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Frive-offscreen-canvas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fpapado%2Frive-offscreen-canvas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fpapado","download_url":"https://codeload.github.com/fpapado/rive-offscreen-canvas/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240906647,"owners_count":19876680,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","offscreencanvas","rive"],"created_at":"2024-11-11T10:51:07.270Z","updated_at":"2025-02-26T17:42:50.291Z","avatar_url":"https://github.com/fpapado.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rive with OffscreenCanvas\n\nThis is a demo/proof of concept of using Rive backed by an [OffScreenCanvas](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas).\nThis allows animations to proceed without jank, even if the main thread ends up being blocked.\n\n## Running this demo\n\nYou will need node and pnpm.\n\n```shell\n# Install dependencies\npnpm install\n\n# Start the dev server\npnpm start\n```\n\n## What problem does this solve?\n\nRive is already very performant on the main thread, and its usage of `canvas` (compared to `svg`) means that much work can happen without taxing the DOM.\n\nIf the usage of the event loop is well-optimised, then Rive-backed animations can proceed happily, without the more complicated setup that this demo shows.\n\nHowever, contemporary frontend applications are still largely on the main thread. This means that, during heavy operations, Rive-backed animations can stutter.\nAn alternative to this problem, is moving Rive-backed animations off the main thread, so they can keep running in the usual performant way.\n\n## The core idea\n\nAs mentioned, Rive renders on a `canvas`, backed either by a `CanvasRenderingContext2D`, `WebGLRenderingContext`, `WebGL2RenderingContext`.\n\nA canvas can be transferred off the main thread via [HTMLCanvasElement.transferControlToOffscreen](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/transferControlToOffscreen), where the usual context operations can be performed.\n\nRive can be used within a web worker, with custom messaging to receive a canvas from the main thread, and operate on it.\n\nThis setup can be wrapped in a React component, or anything else that might be framework-specific.\n\nIn this case, the demo uses React, and tracks some of the API surface from `@rive-app/canvas` and `@rive-app/react-canvas`.\nThe main relevant components are `OffscreenRive` and `riveWorker`.\nThe worker loading is handled via [Vite's web worker feature](https://v3.vitejs.dev/guide/features.html#web-workers), but similar solutions exists for other bundlers and frameworks (or even natively).\n\n## Open issues / questions\n\n### Source issues\n\nWhile Rive's types accept `canvas: HTMLCanvasElement | OffscreenCanvas`, the implementation does occasionally reach out to either `document`, `document.createElement`, or comparisons to `instanceof HTMLCanvasElement`.\nThese features are not available in a worker scope, meaning that they will throw in most cases.\n\nThe `patches` directory contains some [pnpm-backed patches](https://pnpm.io/cli/patch) related to all this.\n\nSome relevant parts:\n\n#### Mesh feature-detection via `document.createElement`\n\nIf a mesh is used, then Rive feature-detects support for some features by creating an `HTMLCanvasElement`. This can be avoided by doing feature detection on `OffscreenCanvas` instead, or doing it conditionally.\n\n#### `resizeDrawingSurfaceToCanvas()` and canvas DOM access\n\nRive's [resizeDrawingSurfaceToCanvas()](https://help.rive.app/runtimes/overview/web-js/rive-parameters#resizedrawingsurfacetocanvas) uses an `instanceof HTMLCanvasElement` comparison and `this.canvas.getBoundingClientRect()`, neither of which exist in workers or on `OffscreenCanvas`.\n\nThis method is recommended to be called in the `onLoad` callback, to avoid blurry images on high-dpi screens, and might be called automatically in future Rive versions.\n\nAt the moment, there is the `customDevicePixelRatio` option, but Rive needs to also read the canvas `width` and `height`.\n\nTo solve this, `resizeDrawingSurfaceToCanvas` could defer to the caller for how to figure out the device pixel ratio, as well as width and height.\nIt could take `width` and `weight` as parameters in addition to `customDevicePixelRatio`, and skip the auto-detection if those are specified.\n\nThis approach seems compatible with this note from the Rive docs:\n\n\u003e In a future major version of this runtime, this API may be called internally on initialization by default, with an option to opt-out if you have specific width and height properties you want to set on the canvas\n\n### Other document access (e.g. attaching event listeners)\n\nThere is no standard way to attach listeners to the DOM/main thread `canvas`, from a web worker.\nA general solution would have to account for this, and either provide such a way by default (e.g. by proxying), provide an extension point (e.g. an open-ended `onAddEventListener` to defer attachment to the user) or to exclude these use-cases as out of scope.\n\n### Working with web workers\n\nAt the moment, the demo `OffscreenRive` uses a single `riveWorker`, which tracks Rive instances based on an id.\n\nThe assumption here is that Rive is performant enough to run many instances on the same worker without blocking each other, and that the main issue is the main thread being overworked due to unrelated reasons.\n\nIf this assumption does not hold, then some worker pooling method could help scale this approach (at the cost of additional complexity).\n\n### Distribution\n\nAt the moment, this is just an inline demo, and is not distributed as a library.\nIt can be used as a reference for your own explorations.\n\nWhile making a framework- and use-case specific library would be workable (modulo some source issues above), I have the gut feeling that there is an intermediate, framework-agnostic layer missing.\n\nThis imaginary layer could be `*-canvas-worker`, and would provide a stable API for communicating via `OffscreenCanvas` and a Rive-in-web-worker instance.\nThis layer might use [`comlink`](https://github.com/GoogleChromeLabs/comlink), or some other [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) facade, to make it a bit nicer to work with.\nThis layer might even tackle other issues, such as worker pooling, in a general way.\n\nHaving this layer distributed by Rive would mean that the API surface is tracked as part of regular Rive updates.\nMaking a truly generic library that tracks an external API surface is hard work!\n\nTo the best of my understanding, distributing libraries with web workers is a bit annoying, because of all the different ways of setting them up and consuming them.\nSuch a library would require extensive documentation, or concrete references for how to use web workers in popular frameworks.\nThis task would be similar to the task of distributing a Wasm library, which Rive already tackles well :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffpapado%2Frive-offscreen-canvas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffpapado%2Frive-offscreen-canvas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffpapado%2Frive-offscreen-canvas/lists"}