screenframe3d
v0.2.1
Published
3D iPhone mockups for landing pages. Put an app screenshot on a real device with React, Vite, and three.js.
Downloads
704
Maintainers
Readme
Screenframe3D
Composite an app screenshot onto a real 3D phone. Live demo.
Phone GLBs are not included in the npm package (Sketchfab licenses). You download a model once, drop it in public/models/, and Screenframe does the rest. First-party devices are planned.
Install
npm install screenframe3d threeReact apps also need react / react-dom. Vite apps should add the plugin below so the HDRI (and later, posters) land automatically.
1. Get a device model from Sketchfab
device is a path. Screenframe loads /models/{device}.glb from your site.
| Device | device id | Put the file at | Sketchfab |
|---|---|---|---|
| iPhone 5s | apple/iphone-5s | public/models/apple/iphone-5s.glb | IPhone 5s by Eternal Realm (CC-BY-4.0) |
| iPhone 12 Pro | apple/iphone-12-pro | public/models/apple/iphone-12-pro.glb | iPhone 12 Pro by DatSketch (CC-BY-4.0) |
| iPhone 14 Pro | apple/iphone-14-pro | public/models/apple/iphone-14-pro.glb | No single listing is embedded in the file. Search downloadable iPhone 14 Pro models. You want a body_mat (or similar) whose emissive map includes a bright portrait screen. |
| iPhone 17 Pro | apple/iphone-17-pro | public/models/apple/iphone-17-pro.glb | Iphone 17 pro by Ibrahim.Bhl (CC-BY-4.0) |
Download steps
- Make a free Sketchfab account.
- Open the model. Confirm it is downloadable and that the license fits your project (CC-BY requires credit).
- Download 3D model → glTF (or GLB if offered).
- If you get a folder, use the
.glb(oftenscene.glb). If you only getscene.gltf+scene.bin+textures/, keep that folder structure or convert to a single.glb. - Rename / move it to the path in the table. The filename must match
deviceexactly:apple/iphone-12-pro→models/apple/iphone-12-pro.glb.
What Screenframe looks for on the glass
Any downloadable phone can work if Screenframe can find the display:
- A material or mesh named
screen,wallpaper,Screen_BG,display, orlcd, or - A material named
body_matwhose emissive map has a bright portrait rectangle (atlas).
The four rows above are the combinations this repo is tested against. Catalog in code: DEVICES from screenframe3d.
2. Environment map (glass and metal)
Reflections use /env/greenwich_park_1k.hdr by default (Greenwich Park, CC0, Poly Haven). Omit environment to keep that map.
Pass environment as a preset id or any HDR URL:
| environment | File | Source |
|---|---|---|
| greenwich-park (default) | /env/greenwich_park_1k.hdr | Greenwich Park |
| studio | /env/studio_small_08_1k.hdr | Studio Small 08 |
| sunset | /env/venice_sunset_1k.hdr | Venice Sunset |
The Vite plugin copies those HDRIs into public/env/ when they are missing. Without the plugin, copy them from node_modules/screenframe3d/assets/env/ or download the 1K HDRs from Poly Haven.
Key light is a close area light (so metal picks up a broad highlight, not a pin-prick). The HDRI rotates so its brightest region sits with that light. Omit light to keep direction (4, 6, 5), intensity 0.22, and color #fff2e0.
initDeviceShowcase({
container,
screenshot: '/screenshot.png',
device: 'apple/iphone-12-pro',
environment: 'studio',
light: {
direction: { x: 4, y: 6, z: 5 },
intensity: 0.22, // `amplitude` is an alias
envIntensity: 1.8, // `envStrength` is an alias
color: '#fff2e0',
},
});After load, setEnvironment(idOrUrl) and setLight({ ... }) update the live scene without recreating it.
3. Vite plugin (recommended)
import { defineConfig } from 'vite';
import { screenframe } from 'screenframe3d/vite';
export default defineConfig({
plugins: [screenframe()],
});That plugin:
- Copies bundled HDRIs into
public/env/ - Writes extracted device textures to
public/textures/on first run - Captures a still (poster) so the next load can show an image before WebGL is ready
It does not download Sketchfab models.
4. Use it
Vanilla
<div id="device"></div>
<script type="module">
import { initDeviceShowcase } from 'screenframe3d';
initDeviceShowcase({
container: document.getElementById('device'),
screenshot: '/screenshot.png',
device: 'apple/iphone-12-pro',
publicBase: import.meta.env.BASE_URL,
});
</script>React
import { DeviceShowcase } from 'screenframe3d/react';
<DeviceShowcase
screenshot="/screenshot.png"
device="apple/iphone-12-pro"
publicBase={import.meta.env.BASE_URL}
/>publicBase matters if the app is not served from / (GitHub Pages, a Vite base, etc.).
Options
Passed to initDeviceShowcase() or <DeviceShowcase />:
| Option | Description |
|---|---|
| container | DOM node for the canvas (vanilla only) |
| screenshot | URL of the image on the glass (not downscaled) |
| device | Path id, e.g. apple/iphone-12-pro → /models/apple/iphone-12-pro.glb |
| publicBase | Prefix for models, env, posters, textures |
| environment | Preset id (greenwich-park, studio, sunset) or HDR URL. Default Greenwich Park. |
| light | Direction, intensity (amplitude alias), color, and env strength (envIntensity / envStrength). HDRI follows the key light. |
| fov, zoom, baseTilt | Camera / rest pose |
| tiltEnabled, spring, scrollTilt | Pointer and scroll motion |
| poster | false / omit = no still. true / 'auto' = hashed /posters/...webp. Or pass a URL. |
| cachePoster, cacheDeviceTextures | Persist stills and maps during vite dev (needs the plugin) |
| fallbackCondition, fallbackImage | Opt-in static image instead of WebGL |
Development (this repo)
npm install
npm run dev # landing + playground (models already in public/)
npm run build # library → dist/Phone GLBs in public/models/ are for the demo only. They are not published to npm.
License
MIT for the library code.
Device models you download remain under their Sketchfab licenses; CC-BY models need attribution. Bundled HDRIs are CC0 (Poly Haven).
