05Object 05 · Reveals
Iris
A lens’s iris diaphragm, as a reveal. Curved blades pivot on their pins, close to a pinhole and open onto what lies beneath, the engraved f-stop ring turning with them.
- Version
- 0.1.0
- Size
- 26.8 KB gzipped
- Access
- With a Pass
- Needs
- iris.mjs + iris.css
- Tests
- 25 tests · 11 states
Use it
Reveal a hero image, a portfolio thumbnail or a page with a mechanism people recognise from a camera, not a fade: the content stays real, accessible DOM under the blades, and the opening is driven by the ring, hover, a press, the scroll, or a slow breathing loop for a loader.
Copy the iris folder from its package next to your page, link its stylesheet and construct it on an element. Every option has a default; the lab’s Code tab writes the same example at any settings, and for React, Vue, Svelte or a custom element (Using a framework).
<link rel="stylesheet" href="iris/iris.css">
<div id="iris" style="width: min(92vw, 560px); aspect-ratio: 1; border-radius: 0"></div>
<script type="module">
import Iris from './iris/iris.mjs';
const iris = new Iris(document.getElementById('iris'), {
blades: 9,
curvature: 0.6,
finish: 'graphite',
});
// iris.update({ … }); change any option in place
// iris.destroy(); and take it away again
</script>Options
| Option | Type | Default | What it does |
|---|---|---|---|
| blades | number, 5 to 16 | 9 | Blades |
| curvature | number, 0 to 1 | 0.6 | 0 is straight blades and a sharp polygon; 1 is as round as a blade that still hides under the lip. |
| finish | graphite · ink · steel · brass | 'graphite' | Finish |
| fit | lens · cover | 'lens' | Lens: the whole object, content seen only through it. Cover: the blades fill the frame and wide open clears it (a reveal). |
| ring | boolean | true | Off leaves a thin lip. Lens fit only. |
| knurl | boolean | true | Knurled grip |
| scale | boolean | true | Engraved f-stop scale |
| driver | dial · hover · press · scroll · breathe · manual | 'dial' | dial: turn the ring (or arrow keys, a stop at a time). hover and press open it. scroll follows the page. breathe is a loader. manual is open() and close() only. |
| intro | boolean | true | For dial, hover, press and manual. |
| rest | number, 0 to 1 | 0.35 | Where it settles: 1 is wide open (f/1.4), 0.35 about f/4, 0 shut. Hover and press open from here. |
| stiffness | number, 60 to 400 | 180 | Spring stiffness |
| light | number, 0 to 345 | 315 | 0 is from above, clockwise. |
| readout | boolean | true | Show f-number |
| sound | boolean | false | Synthesised, no audio file. Starts only after you touch it. |
| still | open · rest | 'open' | open: fully open, because the content is the information. rest: the rest opening, for a loader. |
| label | text | 'Aperture' | The accessible name of the ring slider or the toggle button. |
Methods
new Iris(element, options)- Builds it inside the element with these options.
opening- The current opening (0 shut … 1 wide open), as drawn.
aim- Where the opening is heading.
stateopen(v = 1)- Open to
v(0…1, default fully open) through the spring. close()- Shut the iris.
toggle()- Between the rest opening and wide open.
replay()- Shut, then open again to whatever the driver asks for.
update(opts = {})- Change options in place.
destroy()- Stops it and cleans up after it.
States
- At restSettled at the rest opening (f/4 by default); the loop is asleep and costs nothing.
- Opening when first seenShut until the lens is a third in view, then a short beat and the blades open to rest through the spring.
- Turning the ringDrag round the ring or use the arrow keys: the blades follow the hand with no lag and click into the nearest f-stop on release; the slider's value text is the f-number. In the cover fit a sideways drag scrubs.
- Hover and pressHover (or focus inside) opens it wide and leaving returns it to rest; as a toggle it has its own small button (aria-expanded) and a click on anything not interactive in the object toggles too, so links in the content still work.
- Scroll-linkedShut as it enters from the bottom of the viewport, wide open by the time it is a little above the middle; the spring gives the blades weight.
- BreathingThe loader: stops down to a pinhole and opens back to rest every 3.6 s, and only while it can be seen.
- ShutThe blades meet at the centre in a pinwheel; the content under them stays in the accessibility tree.
- Off screen or hiddenThe loop pauses; a tab returning with a long frame advances the spring one clamped step.
- TouchA vertical swipe anywhere over the object scrolls the page (pan-y); the knurled ring is the grab zone and takes a drag in any direction; hover becomes a tap.
- Reduced motionNo animation: the first paint is fully open (or the rest opening, by choice), and every later change snaps.
- No WebGLPainted with Canvas 2D from the same linkage: blades stacked sector by sector in the exact shingle order, each lit from the lamp with a brushed grain, cast shadow and lit or shaded edge; the housing's turned sheen from the same formula. Same motion; also used if the WebGL context is lost.
How it works
Made of: Pivot linkage, Shingle stack, Sdf shading, Spring opening, Shared webgl context, Polar engraving texture, Sleeping loop, Canvas 2d fallback, Sector ordered painting, Touch grab zone.
Original. Modelled from how a real diaphragm is built (rigid blades on fixed pivots turned together), not from a catalogue reference, so references is honestly empty. One shared WebGL context renders every instance, with a Canvas 2D painter of the same geometry when WebGL is missing or lost; no image or font ships (the preview's photograph is painted procedurally, the exported example's is inline SVG). PRESETS in controls.mjs: Graphite prime (default), Brass field camera, Six-blade hexagon, Steel cine lens, Cover reveal.