Unfold

Open-source web component

Unfold

An iPhone Duo you can put on any web page. It opens, folds, turns and stands up like the real one, and shows your own screens.

Drag to turn it, click to flip it, or drop a screenshot on it.Drag sideways to turn it, tap to flip it.

Every way it stands.

Seven postures, one attribute each. Every phone here turns and flips too.

Closed

Landscape

Landscape ajar

Portrait

Portrait ajar

Laptop

Tent

The back

Measured, not guessed.

The body follows Apple's published dimensions. Everything else is measured from Apple's own 3D model of the phone and its spec drawings, down to the speaker holes.

Open
164.6 × 117.8 × 5.2 mm
Closed
84.1 × 117.8 × 11.3 mmTwo 5.2 mm halves with a 0.9 mm gap, and the hinge cover standing 1.8 mm out of the side.
Camera
A 55.8 × 21.2 mm plateau, 3.6 mm proudTwo lenses in 16.3 mm polished rings that rise another 1.9 mm, with the mic and flash at the inner end.
Band
Rounded titanium, 0.7 mm round the inner screenIt rolls 1.3 mm into the back glass and the cover glass, with antenna lines, speaker holes and USB-C where they really are.
Buttons
Volume on top, Touch ID and Camera Control on the sideVolume 21 to 45 mm from the corner, Touch ID 34 to 52 mm and Camera Control 77 to 94 mm down from the top.
Hinge
No line at the foldThe screen wakes dark and soft as it opens and clears from the side that was hidden, like the real one.
Finishes
Star White and Night Sky

Add it to a page.

It is one ES module with no build step and no dependencies. Load it from here, or download unfold.js and host it yourself. The source is on GitHub.

Load the script

<script type="module" src="https://unfold.foo/unfold.js"></script>

Add a phone

Give each screen a picture or a video. The inner screen spans both halves, and a portrait shot on it turns to stand upright when the phone does.

<unfold-device
  inner="open.png"
  cover="closed.png"
  posture="landscape-ajar"
  finish="night-sky"
  interactive>
</unfold-device>

Move it

Every method returns a promise, so moves can follow one another.

const phone = document.querySelector('unfold-device');

await phone.setPosture('closed');
await phone.setPosture('laptop');
await phone.animateTo({ yaw: 160, pitch: 14 }, { duration: 1200 });

phone.addEventListener('change', e => console.log(e.detail.fold));

Reference.

Attributes

NameValuesWhat it does
postureclosed, landscape, landscape-ajar, portrait, portrait-ajar, laptop, tentHow it is held. Changing it animates to the new posture. Default landscape.
innerURLImage or video on the big inside screen.
coverURLImage or video on the outside screen.
finishstar-white, night-skyThe colour of the frame, back and hinge.
fold0 to 180Angle between the two halves of the inner screen. 0 is shut, 180 is flat.
yawdegreesTurns it left or right.
pitch-90 to 90How far above it you look from. Below zero looks up at it.
rolldegreesTurns it on the page.
interactivepresent or notDrag to turn, click to flip, arrow keys to turn, F to fold.
clickflip, fold, noneWhat a click does when it is interactive. Default flip.
logoURLA logo in the middle of the back. None by default.
zoomnumberScales the phone within the element. Default 1.
shadownoneHides the shadow underneath.
labeltextWhat screen readers call it. Default "iPhone Duo".

Methods and properties

NameWhat it does
setPosture(name, options)Moves to a posture. Same as setting the attribute, but returns a promise.
open(options)Unfolds it flat.
close(options)Folds it shut.
toggle(options)Opens it if it is mostly closed, closes it otherwise.
flip(options)Turns it around to show the other side.
spin(turns, options)Spins it one full turn, or turns turns. Negative spins the other way.
animateTo(target, options)Animates any of fold, yaw, pitch and roll. Options are duration in ms, easing (a CSS easing name or cubic-bezier()) and delay. Resolves true when it finishes, or false if something else moved it first.
stop()Stops every animation where it is.
setScreen(name, src)Puts a URL, File or Blob on inner or cover. Handy for a file picker or a drop. null clears it.
fold, yaw, pitch, rollRead or set the pose right away, with no animation.
posture, stateThe last posture asked for, and the current pose as one object.

Animations respect reduced motion: they jump straight to the end unless you pass force: true.

Events

NameWhen
changeEvery time it moves. event.detail is the pose: { fold, yaw, pitch, roll, posture }.
activateOn a click, Enter or Space when it is interactive. event.detail.action says what it did.

Styling

NameWhat it does
width, heightThe element is a block, 16:10 by default. Size it however you like and the phone scales to fit, in every posture.
--unfold-metalColour of the frame and buttons. Overrides the finish.
--unfold-backColour of the back glass and camera plateau.
--unfold-hinge, --unfold-antennaColours of the hinge cover and the antenna lines.
--unfold-shadowHow dark the shadow is, 0 to 1. Default 0.3.
::part()stage, shadow, screen, back-glass and camera.

Your own finishes and postures

Both are plain objects. Add one, then use it by name.

import { addFinish, POSTURES } from 'https://unfold.foo/unfold.js';

addFinish('sage', { metal: '#a9b7a5', back: '#c3cdbf', hinge: '#97a393', antenna: 'rgba(60, 70, 58, .4)' });
POSTURES.reading = { fold: 120, roll: 0, anchor: 0.5, lean: 0, yaw: -20, pitch: 10 };
<unfold-device finish="sage" posture="reading"></unfold-device>

Notes.