Custom layouts
Write your own place(d) and get any shape you like.
A layout is a function. It takes how far an item is from the center and returns where to put it. Every preset that ships is written against it.
type Layout = (d: number) => Placement
interface Placement {
x: number
y: number
z: number // pixels toward the viewer; also decides paint order
rotate: number // degrees in the plane of the screen
rotateX?: number // degrees tipped toward you
rotateY?: number // degrees turned toward you
scale: number
opacity: number
}d is signed and continuous, in item units. 0 is the centered item, 1 is the
next one along, -0.5 is halfway back toward the previous one. It is a float because the
track spends most of its time between two items.
A wave
Six lines of arithmetic.
const wave = (d) => ({
x: d * 88,
y: Math.sin(d * 0.8) * 40,
z: -Math.abs(d),
rotate: Math.cos(d * 0.8) * 10,
scale: 1 - Math.min(Math.abs(d) / 5, 1) * 0.35,
opacity: 1 - Math.min(Math.abs(d) / 5, 1) * 0.7,
})
createPicker(el, { items, renderItem, layout: wave, spacing: 88 })Pass spacing too
The layout decides where items go; spacing decides how far a finger has to
travel to move the track by one item. Loopem cannot infer one from the other, so
a custom layout should pass whatever matches its own step.
Packaging one as a preset
loopem/layouts exports functions that return a partial options object, not
a bare Layout, because a shape usually needs its own spacing, perspective
and overscan to render correctly. If you build something reusable, do the same:
export const wave = ({ step = 88, height = 40 } = {}) => ({
layout: (d) => ({ /* ... */ }),
spacing: step,
overscan: 3,
})