react-native-model-viewer-webview is a small adapter around three pieces:
- React Native renders a
react-native-webviewinstance. - The package generates a complete HTML document containing Google's
<model-viewer>web component. - The WebView sends structured status messages back to React Native through
window.ReactNativeWebView.postMessage.
It does not implement a renderer. The renderer is the platform WebView running
<model-viewer>.
flowchart LR
RN["React Native screen"] --> Component["ModelViewerWebView"]
Component --> Source["resolveModelSourceUri"]
Component --> HTML["buildModelViewerHtml"]
HTML --> WebView["react-native-webview"]
WebView --> ModelViewer["<model-viewer>"]
ModelViewer --> Events["load/error events"]
Events --> Bridge["postMessage JSON status"]
Bridge --> Component
Native 3D stacks are better when 3D is the product. This package exists for the opposite case: 3D is a preview inside a normal app screen.
Using WebView gives us:
- the same high-level
<model-viewer>API used on the web - simple camera controls and auto-rotate
- GLB/glTF support delegated to a maintained web component
- a small React Native API surface
- compatibility with Expo and bare React Native apps that already support
react-native-webview
It also means:
- startup cost is higher than a native view
- WebView memory matters in long lists
- WebView gesture behavior may need screen-level tuning
- rendering quality and bugs depend on the platform WebView
resolveModelSourceUri accepts:
- string URLs, including
https:,data:, andfile: - React Native static asset module numbers from
require(...) - asset-like objects with
localUriand/oruri
For objects, localUri wins over uri because local files are preferred after
an Expo asset has been downloaded.
For module numbers, the package calls Image.resolveAssetSource. React Native
does not have a dedicated generic asset resolver with equivalent availability,
so Image.resolveAssetSource is used as the practical stable option.
buildModelViewerHtml creates a full HTML document with:
- viewport metadata
- a
<model-viewer>script tag - WebView status bridge helpers
- a full-size
<model-viewer>element - generated model-viewer attributes
Attribute values are HTML-escaped. Attribute names must match a conservative safe-name pattern before they are emitted. Basic CSS color values are sanitized to avoid injecting arbitrary CSS into generated HTML.
By default, the HTML inlines the vendored runtime:
@google/model-viewer 4.2.0
This avoids a CDN request at runtime, so a bundled/local model asset can render
without network access. It also makes each package version deterministic: the
same npm package version always embeds the same <model-viewer> runtime.
The vendored runtime is generated into vendor/model-viewer/runtime.js from
@google/model-viewer's dist/model-viewer.min.js. Source hashes and license
metadata are recorded in vendor/model-viewer/SOURCE.md.
Apps that want to use a different runtime can provide either:
modelViewerScriptUrl, such as a CDN URL orfile:URL to another scriptmodelViewerScript, an inline module script string
Inline script content escapes closing </script> sequences to avoid breaking
the generated document.
The generated HTML posts JSON messages:
{ "type": "dom-ready", "message": "Model viewer DOM ready" }Important event types:
dom-readymodel-loadedmodel-errorpage-error
ModelViewerWebView parses these messages and exposes:
onStatusfor every statusonModelLoadedformodel-loadedonModelErrorfor statuses ending inerror
Raw or malformed WebView messages are converted into page-error statuses.
- It does not implement native rendering.
- It does not expose a scene graph.
- It does not manage physics, shaders, or multiple model scenes.
- It does not guarantee AR support.
- It does not guarantee identical rendering across Android and iOS WebViews.
Those are native renderer concerns. Use Filament, React Three Fiber Native, or Expo GLView for those workloads.