Skip to content

Data Attribute API ​

Data attributes (data-foldpage-*) control 3D features that HTML and CSS can't express.

Custom elements: An element with a Fold data attribute is rendered even if its tag isn't a standard HTML element — for example web components or Angular, React and Vue components. See Custom Elements.


Data Attribute Index ​

Depth & Extrusion ↓ ​

Lighting & Materials ↓ ​

Interaction ↓ ​

  • data-foldpage-passthrough — Let clicks/taps pass through this element's background to whatever is behind it ("true" = pass through)

Transforms ↓ ​

Views ↓ ​

View Layouts ↓ ​

Position Override ↓ ​

List Markers ↓ ​

Images, SVG & 3D Models ↓ ​

Camera Offset ↓ ​

Environment (HDRI) ↓ ​

Particle System ↓ ​

Animation ↓ ​


Data Attributes Details ​

Depth & Extrusion ​

data-foldpage-depth ​

Controls 3D depth. Behavior depends on the element category — backgrounds recede, images and text push forward:

  • Background elements — any element with a visible CSS background (fill or gradient) or box-shadow. Controls extrusion depth of the single background panel. Positive values recess the panel backward; negative values extrude it forward. Unaffected by data-foldpage-depth-interval — there's only one panel, not a layer stack.
  • <svg> elements — controls extrusion depth and forces geo mode. In geo mode, individual SVG child nodes (e.g. <rect>, <circle>, <path>) can also carry their own data-foldpage-depth to set per-shape depth, overriding the parent SVG's value.
  • <img> elements with no CSS box-shadow — creates a depth-repeat stack (see data-foldpage-depth-interval below) and pushes the entire stack, including the primary image, forward toward the camera by the full clamped stack depth. The image bulges out of the page rather than sinking into it.
  • <img> elements with a CSS box-shadow — the box-shadow layers drive their own per-copy styling and direction (inset reverses it), so the forward push is skipped and depth-repeat behaves exactly as authored. See Image Capabilities → Depth Repetition.
  • Text elements with no visible background and no CSS text-shadow — synthesizes shadow-style text layers (see data-foldpage-depth-interval below) and pushes the entire stack, including the primary text, forward toward the camera, the same as the image case. If the element already has a CSS text-shadow, data-foldpage-depth is ignored for text — the authored shadow keeps its own position/color/blur and is not pushed. If the element has a visible background or box-shadow, data-foldpage-depth drives that background's extrusion instead (first bullet above) and text is unaffected.
html
<!-- Recessed panel — positive value; Fold.page auto-negates for backgrounds -->
<section data-foldpage-depth="30"
         style="background-color: rgba(100,100,200,0.5); border-radius:20px;">
  <h3>Card</h3>
</section>

<!-- Image with no box-shadow: 5 depth-repeated copies, whole stack pushed forward -->
<img src="card.png" data-foldpage-depth="9" data-foldpage-depth-interval="2">

<!-- Plain paragraph, no background, no CSS text-shadow: 5 synthetic shadow layers
     (50% opacity, inherits the text color), whole stack pushed forward -->
<p data-foldpage-depth="9" data-foldpage-depth-interval="2">Bulging text</p>

The value is in CSS pixels for all element types. Fold.page converts to Three.js world units.

Layer cap: Depth-repeat layer counts (images and text) are clamped to a maximum of 20 layers, regardless of how large data-foldpage-depth is relative to the interval. The forward push amount is calculated from the clamped layer count, not the raw attribute value — see data-foldpage-depth-interval below.


data-foldpage-depth-interval ​

Controls the Z-spacing between each layer in a depth-repeat stack. Applies to <img> elements and text elements used with data-foldpage-depth — has no effect on backgrounds or SVG extrusion, which are a single panel/mesh with no layers to space out. It also spaces real CSS text-shadow layers, not just synthetic depth-driven ones (see Text Shadows).

The number of layers rendered is calculated as: round(depth / interval), clamped to a maximum of 20 layers.

html
<!-- depth 9 / interval 2 = round(4.5) = 5 depth copies, pushed forward as a stack -->
<img src="card.png"
     data-foldpage-depth="9"
     data-foldpage-depth-interval="2">

<!-- Same math applies to text with no background and no CSS text-shadow -->
<p data-foldpage-depth="9" data-foldpage-depth-interval="2">Bulging text</p>

<!-- Styled depth copies via box-shadow — forward push is skipped for this path -->
<img src="card.png"
     data-foldpage-depth="9"
     data-foldpage-depth-interval="2"
     style="box-shadow: 0 2px 4px rgba(0,0,200,0.4), 0 4px 8px rgba(0,100,255,0.3), 0 6px 12px rgba(0,50,255,0.2);">
ValueEffect
(absent)Default spacing: 2 CSS px between layers
1Tightly packed layers
7Widely spaced layers

For images, each box-shadow layer styles the corresponding depth copy — color tint, XY offset, spread, and opacity. The last box-shadow styles all remaining layers beyond the individually-specified ones. See Image Capabilities — Depth Repetition for full detail. Text has no equivalent per-layer styling attribute — synthetic text depth layers are always 50% opacity, inheriting the element's text color.


Lighting & Materials ​

data-foldpage-lit ​

Enables physically-based lighting on an element and all elements inside it.

html
<!-- Entire section rendered with lit standard material -->
<section data-foldpage-lit="standard" class="foldpage-bg" ...>
  <h2>Heading inherits lit material</h2>
  <p>Paragraph text too</p>
  <img src="photo.jpg">
</section>

<!-- Just a glossy image -->
<img src="logo.svg" data-foldpage-lit="glossy">

Material presets:

PresetDescriptionroughnessmetalness
(no value) or standardBalanced PBR0.50.0
reflectiveChrome / mirror0.00.5
roughDiffuse matte1.00.0
glossyShiny plastic0.150.0
metalBrushed metal0.20.6
glowEmissive — glows with its own color, independent of scene lighting1.00.0
vividLambertian + 50% self-emission — colors stay close to CSS values while responding to lights1.00.0

Without data-foldpage-lit, elements are unlit: they show their exact CSS colors and ignore lights and HDRI.

data-foldpage-lit-edge ​

Applies a separate material preset to the border geometry only of a .foldpage-bg element. Does not affect the background surface or child elements.

html
<section class="foldpage-bg" data-foldpage-lit="standard" data-foldpage-lit-edge="metal"
         style="border: 2px solid gold; ...">
  Metallic border, standard-lit background
</section>

Accepts the same preset names as data-foldpage-lit.


data-foldpage-backface ​

Overrides the global backside setting for a specific <img> element. Set to "false" to render that image single-sided (THREE.FrontSide); its back face is not drawn. All depth-repetition copies produced by data-foldpage-depth are also affected.

Any other value, or omitting the attribute entirely, defers to the global backside config (default true).

ValueEffect
"false"Single-sided — back faces not rendered for this image
"true" or (absent)Falls back to the global backside config
html
<!-- Global backside is true; these two images opt out individually -->
<img src="logo-front.png"
     data-foldpage-depth="7"
     data-foldpage-backface="false">

<img src="logo-side.png"
     data-foldpage-depth="7"
     data-foldpage-backface="false">

Useful when images are only ever seen from one side — for example, faces of a 3D box constructed from CSS-positioned images with rotateY transforms. Disabling back faces prevents the inside surface from rendering when the element rotates away from the viewer.

Global default: Controlled by Fold.init({ backside: true }) — see Configuration API → backside. Per-element data-foldpage-backface always wins over the global setting for that specific image.


Interaction ​

data-foldpage-passthrough ​

By default, any element with a rendered background (a background-color, background-image, or gradient) blocks clicks and taps to whatever is positioned behind it in the 3D scene — matching normal DOM/CSS stacking, where an opaque box in front prevents pointer events from reaching elements underneath it. Set data-foldpage-passthrough="true" to opt an element out of this blocking and let pointer events fall through to elements behind it instead.

ValueEffect
"true"Pointer events pass through this element's background to whatever is behind it
"false" or (absent)Default — background blocks pointer events from reaching elements behind it
html
<!-- A decorative panel that shouldn't intercept clicks meant for content behind it -->
<div class="foldpage-bg" data-foldpage-passthrough="true"
     style="background: rgba(255,255,255,0.1);">
  Decorative overlay — clicks pass through to the link below
</div>

<a href="/details" data-foldpage-view-link="#details-panel">Read more</a>

Elements with no rendered background (fully transparent, no gradient, and not otherwise forced into the hit-test area) never block pointer events regardless of this attribute — there's no geometry there to intercept the ray in the first place.

Elements nested inside a link, button, label, <summary>, video, audio, or data-foldpage-view-link element pass through automatically — no attribute needed. Like in the DOM, clicking or tapping any part of a button's contents (an icon, the bars of a hamburger menu) activates the button instead of being swallowed by the child's own background.


Transforms ​

data-foldpage-scale composes additively with the element's CSS transform — it is not a replacement. Fold.page reads the CSS transform and then applies the scale on top. For position offsets and rotation, use CSS transform (translate3d(), rotate3d(), etc.) directly — it composes naturally with responsive layouts (media queries, container queries, etc.) in a way a fixed data-attribute value can't.

XYZ shorthand format: Every attribute that accepts an "x y z" triplet (data-foldpage-scale-origin, -view-rotation, -view-origin, -view-offset, -animate-origin, -particles-bounds-box, -particles-bounds-center) uses the same format: three numbers separated by spaces and/or commas, no brackets — "10 20 30" or "10, 20, 30" both work.

data-foldpage-scale ​

Scales the element uniformly. 1 = no change, 2 = double size, 0.5 = half size.

html
<img src="logo.png" data-foldpage-scale="1.5">
AttributeDescription
data-foldpage-scaleUniform scale multiplier
data-foldpage-scale-origin="x y z"Pivot point for scaling, offset from element center (CSS pixels). Y positive = down (CSS convention).

Views & Camera Navigation ​

See the Views & Navigation guide for full detail. Quick reference:

AttributeDescription
data-foldpage-viewMarks element as a named view for scroll-driven camera framing. Value is an index number (0 = home). Empty value = auto-ordered.
data-foldpage-view-linkPlain form — flies the camera to frame the clicked element at the exact click point.
data-foldpage-view-link="#id"Anchor form — flies the camera to the element with the given id. If the target has data-foldpage-view, triggers a scroll-based view jump instead of a camera spring.
data-foldpage-view-rotation="x y z"Explicit camera orientation when navigating to this view (degrees). Overrides the auto-computed face-on orientation.
data-foldpage-view-origin="x y z"Shifts the focal point for this view (CSS pixels). Adjusts where the camera looks when framing the element. Y positive = down (CSS convention), same as data-foldpage-view-offset.

data-foldpage-view-offset ​

Additively shifts the focal point (where the camera looks) when this view is active. Accepts either a space- or comma-separated shorthand of three numbers, or individual per-axis attributes. Values are in CSS pixels.

html
<!-- Shift focal point 10px up and 40px toward the viewer -->
<section data-foldpage-view data-foldpage-view-offset="0 -10 40">...</section>

<!-- Same result using per-axis attributes -->
<section data-foldpage-view
         data-foldpage-view-offset-y="-10"
         data-foldpage-view-offset-z="40">...</section>

<!-- Single-axis convenience — push focus 50px forward on Z only -->
<section data-foldpage-view data-foldpage-view-offset-z="50">...</section>
AttributeDescription
data-foldpage-view-offset="x y z"Shorthand — space- or comma-separated CSS pixels, e.g. "0 -10 40"
data-foldpage-view-offset-xX-axis focal-point shift (CSS px). Positive = right.
data-foldpage-view-offset-yY-axis focal-point shift (CSS px). Positive = down (CSS convention).
data-foldpage-view-offset-zZ-axis focal-point shift (CSS px). Positive = toward the viewer.

Shorthand priority: When data-foldpage-view-offset is present, per-axis attributes are ignored. Per-axis variants only apply when the shorthand is absent.

Stacking with data-foldpage-view-origin: Both attributes shift the same focal point and stack additively. Set both when you need them for independent reasons — for example, view-origin to adjust the anchor on the element and view-offset to nudge the final camera target.

This attribute applies in both scroll-driven view navigation and step navigation (arrow-key stepping through views). The focal-point shift is included in the camera spring animation in both modes.


data-foldpage-view-dim ​

Overrides the inactive-view opacity for all data-foldpage-view elements that are descendants of this element. Accepts a number between 0 (fully transparent) and 1 (fully opaque). The nearest ancestor with this attribute wins; the global viewDim setting is the fallback.

html
<!-- Panels in this group dim to 15% when not active -->
<div data-foldpage-view-dim="0.15">
  <section data-foldpage-view>Panel A</section>
  <section data-foldpage-view>Panel B</section>
</div>

Works naturally with View Layouts — place it on the data-foldpage-view-layout container to scope dimming to that layout's panels:

html
<div data-foldpage-view-layout="ring"
     data-foldpage-view-dim="0.25">
  <header data-foldpage-view="0">Hub</header>
  <section data-foldpage-view>Spoke 1</section>
  <section data-foldpage-view>Spoke 2</section>
</div>

See View Dimming for full behaviour details.


View Layouts ​

data-foldpage-view-layout and its companion attributes automatically position views in 3D space — rings, grids, helices, and random scatters — without manual CSS transforms.

See the View Layouts guide for the full attribute reference.


Position Override ​

data-foldpage-position ​

Overrides the CSS position property for Fold's scene-graph routing. Useful when CSS position doesn't reflect the intended 3D placement.

html
<div data-foldpage-position="fixed">Treat as fixed in 3D</div>

Valid values: static, relative, absolute, fixed, sticky.


List Markers ​

data-foldpage-list-3d ​

Replaces flat 2D bullet points with animated 3D sphere markers for list items. Place on an ancestor <ul> or <ol>.

html
<ul data-foldpage-list-3d>
  <li>Item with 3D marker</li>
  <li>Another item</li>
</ul>

Without this attribute, list items render as plain text with their CSS-defined list-style (or none, if suppressed by CSS).


Images, SVG & 3D Models ​

See Image Capabilities for full detail. Quick reference:

data-foldpage-model-src ​

Loads a GLB or GLTF 3D model and places it at the host element's position in the 3D scene. Any HTML element can carry this attribute — an empty <div> is typical.

html
<div data-foldpage-model-src="/models/plant/scene.glb"
     style="width: 200px; height: 200px;"></div>

<!-- Draco-compressed GLB — same attribute, handled automatically -->
<div data-foldpage-model-src="/models/trophy-draco.glb"
     style="width: 300px; height: 300px; transform: rotateY(20deg) translateZ(50px);"></div>

The model's longest axis is uniformly scaled to fit the element's CSS width. CSS transform and data-foldpage-scale apply on top. See Image Capabilities — 3D Models for full detail.


Image depth stack attributes (used together on <img> elements):

AttributeDefaultDescription
data-foldpage-depth0Total depth of the layer stack (CSS px). Layers = round(depth / interval), capped at 20.
data-foldpage-depth-interval2Z-spacing between each depth layer (CSS px).

With no CSS box-shadow, the whole stack (image included) is pushed forward toward the camera by the clamped layer count × interval. Pair with CSS box-shadow to style each layer instead — this disables the forward push and keeps depth purely receding/directional per layer. See Depth Repetition.

The same data-foldpage-depth / data-foldpage-depth-interval pair also works on text elements with no visible background and no CSS text-shadow — see data-foldpage-depth above and Text Shadows.


data-foldpage-svg-mode ​

Controls how SVG images are rendered.

ValueEffect
(absent)Auto. <img src="….svg"> renders as a texture. Inline <svg> renders as geometry, or as a texture when it uses texture-only features (gradients, filters, masks, clip paths, <text>, <image>, animation) and has no data-foldpage-depth. See Image Capabilities → SVG Images.
textureTexture mode — SVG rendered to a canvas and shown as a flat texture
geoGeometry mode — SVG paths become 3D mesh geometry with optional extrusion

Geometry mode supports data-foldpage-depth and data-foldpage-lit. In geo mode, individual SVG child nodes can also carry their own data-foldpage-depth to set per-shape extrusion depth — child values override the parent SVG's depth.


Particle System ​

The particle system activates automatically when any element carries data-foldpage-particles or when Fold.init({ particles }) is called. No CSS class is required.

data-foldpage-particles ​

Marks an element as a particle attractor or emitter. Accepts one or more behavior modes as a space- or comma-separated value: attract, repel, orbit, vortex, contain, brownian, gravity.

html
<div data-foldpage-particles="attract">...</div>
<div data-foldpage-particles="vortex gravity"
     data-foldpage-particles-gravity="0,-0.0004,0">...</div>

data-foldpage-particles-target ​

Redirects an attractor's position to a different DOM element while the behavior config (mode, strength, radius) stays on the host element. Accepts any CSS selector; multiple matches create one attractor per matched element.

html
<!-- The attractor force is applied at .hero's position, not this element's -->
<div data-foldpage-particles="attract"
     data-foldpage-particles-strength="0.8"
     data-foldpage-particles-radius="300"
     data-foldpage-particles-target=".hero">
</div>

When absent, the attractor is positioned at the host element's center (existing behavior).

Global simulation config via data attributes ​

Any element with data-foldpage-particles can also carry global simulation settings — the first occurrence of each attribute in DOM order wins. These take lower priority than Fold.init({ particles }).

AttributeMaps toNotes
data-foldpage-particles-countcountAmbient particle count
data-foldpage-particles-modesmodesSpace/comma-separated, e.g. "brownian orbit"
data-foldpage-particles-bounds-shapeboundsShape"box" | "sphere"
data-foldpage-particles-bounds-radiusboundsRadiusWorld units
data-foldpage-particles-bounds-strengthboundsStrengthNumber
data-foldpage-particles-bounds-modeboundsMode"constrain" | "wrap"
data-foldpage-particles-bounds-boxboundsBox"x y z" half-extents
data-foldpage-particles-bounds-centerboundsCenter"x y z"
data-foldpage-particles-bounds-auto-docboundsAutoDoc"true" | "false"
data-foldpage-particles-wrap-dampingwrapDampingNumber
html
<!-- Configure a sphere-bounded particle system, entirely from HTML -->
<div data-foldpage-particles="orbit brownian"
     data-foldpage-particles-emit="80"
     data-foldpage-particles-count="400"
     data-foldpage-particles-bounds-shape="sphere"
     data-foldpage-particles-bounds-radius="0.5">
</div>

Full reference: See Particle System → Element Modes for all per-attractor attributes — strength, radius, emit, lifetime, brownian-strength, gravity, friction — with examples.


Camera Offset ​

Shift and rotate the camera from its default screen-mode position. Place these attributes on the Fold root element (the element with the .foldpage class, or <body> when foldpage: true). They are read once during Fold.init(). Screen mode only — in WebXR the headset pose drives the camera.

AttributeUnitDescription
data-foldpage-camera-offset-xCSS pxShift right (positive) or left (negative)
data-foldpage-camera-offset-yCSS pxShift up (positive) or down (negative)
data-foldpage-camera-offset-zCSS pxShift toward content (positive) or away (negative)
data-foldpage-camera-rotation-xdegreesPitch: positive = nose down
data-foldpage-camera-rotation-ydegreesYaw: positive = turning right
data-foldpage-camera-rotation-zdegreesRoll: positive = clockwise
html
<div class="foldpage"
     data-foldpage-camera-offset-x="-150"
     data-foldpage-camera-offset-y="-120"
     data-foldpage-camera-rotation-x="9"
     data-foldpage-camera-rotation-y="-14">
  ...
</div>

JS API: Fold.set({ cameraOffset }) for runtime updates — see Configuration API → cameraOffset for the full field reference, partial update behavior, and framing notes.


Environment (HDRI) ​

data-foldpage-hdr-src ​

Loads an equirectangular .hdr image and applies it to the Three.js scene as both an IBL environment (image-based lighting for PBR materials and GLB models) and, optionally, a visible background sky.

Only the first element with data-foldpage-hdr-src on the page is used; any additional instances are ignored. Fold.init({ hdr }) takes precedence over the data attribute if both are present.

html
<!-- IBL lighting only — no visible background -->
<div
  data-foldpage-hdr-src="./env.hdr"
  data-foldpage-hdr-background="false"
></div>

<!-- Visible background on screen, IBL in both contexts -->
<div
  data-foldpage-hdr-src="./env.hdr"
  class="foldpage-noxr"
></div>

<!-- Visible background in XR only -->
<div
  data-foldpage-hdr-src="./env.hdr"
  class="foldpage-xr"
></div>

<!-- Visible everywhere, soft blur, boosted intensity -->
<div
  data-foldpage-hdr-src="./env.hdr"
  data-foldpage-hdr-intensity="1.4"
  data-foldpage-hdr-blur="0.3"
></div>

Background visibility rules (in priority order):

ConditionBackground shown
data-foldpage-hdr-background="false"Never (IBL only)
.foldpage-xr class (without .foldpage-noxr)XR session only
.foldpage-noxr class (without .foldpage-xr)Screen only
Neither class, or data-foldpage-hdr-background absentScreen + XR

IBL (environment lighting on GLB models) is always active regardless of background visibility.

AttributeTypeDefaultDescription
data-foldpage-hdr-srcstring—URL or path to the .hdr file
data-foldpage-hdr-background"false"—Set to "false" to suppress visible background (IBL only)
data-foldpage-hdr-intensitynumber1.0Environment light intensity multiplier
data-foldpage-hdr-blurnumber0Background blur 0–1 (only applies when background is visible)

JS API: Fold.init({ hdr }) / Fold.set({ hdr }) — see Configuration API → hdr for the JS form, runtime Fold.set() partial updates, and IBL material behavior.


Animation ​

data-foldpage-animate ​

Applies one or more per-frame animation effects to any Fold.page element — including GLB models, backgrounds, text sections, and images. Effects run inside the existing transform hierarchy: scroll tracking, CSS position handling, and static transforms all remain active; the animation layer composes on top.

Combine multiple effects with a comma-separated value:

html
<!-- Single effect -->
<div class="foldpage" data-foldpage-animate="sway"></div>

<!-- Combined effects -->
<div class="foldpage"
     data-foldpage-animate="pulse,rotate"
     data-foldpage-animate-speed="0.6"
     data-foldpage-animate-amplitude="0.5"></div>

<!-- GLB model with pointer/gyro tilt -->
<div class="foldpage"
     data-foldpage-model-src="/models/trophy.glb"
     data-foldpage-animate="tilt"
     data-foldpage-animate-amplitude="0.5"
     style="width:200px; height:240px;"></div>

Effect catalogue:

EffectDescription
rotateConstant rotation around the axis set by data-foldpage-animate-axis (default y).
swaySinusoidal rotation around the axis set by data-foldpage-animate-axis (default x — forward/backward nodding). Set data-foldpage-animate-axis="y" for a left/right shimmy, or "z" for a roll.
pulseSinusoidal movement along the axis set by data-foldpage-animate-axis (default z — toward/away from the viewer). Set data-foldpage-animate-axis="y" for an up/down hover, or "x" for side-to-side.
breatheSinusoidal uniform scale — subtle breathing/heartbeat effect. Not axis-based (scales all three axes together).
tiltTilts toward the pointer (up to ±45°). On phones with a motion sensor it follows the device's tilt instead (up to ±135° at amplitude=1). In WebXR it follows the right controller's ray, or turns to face the viewer when there's no controller.

Combined effects add together without conflicting.


Animation sub-attributes ​

AttributeDefaultDescription
data-foldpage-animate—Comma-separated list of effect names (required to enable animation).
data-foldpage-animate-speed1.0Normalized speed multiplier. 0.5 = half speed, 2.0 = double. Negative values reverse direction — useful for counter-rotating elements. Accepts a comma-separated list matched by position to data-foldpage-animate — see Per-effect speed / amplitude below.
data-foldpage-animate-amplitude0.5Effect strength, normalized 0–1. 0 = no movement, 1 = maximum range. Values outside 0–1 are accepted. Accepts a comma-separated list matched by position to data-foldpage-animate — see Per-effect speed / amplitude below.
data-foldpage-animate-axiseffect-specificAxis for the rotate, sway, and pulse effects. Accepts x, y, or z. Defaults to y for rotate, x for sway, z for pulse — each effect's own long-standing look — when the attribute is omitted. Has no effect on breathe or tilt.
data-foldpage-animate-phase0Phase offset in radians for sinusoidal effects. Set to random to assign a random offset that drifts to a new random target every 20 seconds, creating organic variation when the same effect is used on multiple elements.
data-foldpage-animate-origin"0,0,0"Custom pivot point in CSS pixels relative to the element center. See Animation Pivot / Origin below.

Per-effect speed / amplitude ​

data-foldpage-animate-speed and data-foldpage-animate-amplitude each accept a comma-separated list, matched by position to the effect list in data-foldpage-animate:

html
<div data-foldpage-animate="rotate, sway"
     data-foldpage-animate-speed="-0.55, 1"
     data-foldpage-animate-amplitude="0.1, 0.002"
></div>

Here rotate gets speed -0.55; sway gets speed 1 and amplitude 0.002 (rotate ignores amplitude — it has no sinusoidal range). If fewer values are given than effects, the last value is reused for the remaining effects — so a single value (the common case) broadcasts to every effect, exactly as before this list syntax existed:

html
<!-- both rotate and pulse use speed -0.25 and amplitude 0.1 -->
<div data-foldpage-animate="rotate, pulse"
     data-foldpage-animate-speed="-0.25"
     data-foldpage-animate-amplitude="0.1"
></div>

Animation Pivot / Origin ​

By default, animation effects pivot around the visible center of the element — the midpoint of the portion currently inside the viewport. This works well for typical pulse, breathe, and sway effects.

For rotate (and any other effect), you can override this pivot with an explicit 3D point using data-foldpage-animate-origin:

html
data-foldpage-animate-origin="x,y,z"

Values are CSS pixels relative to the element's center. The custom offset is added to the automatic visible-center pivot, so "0,0,0" is identical to omitting the attribute.

Coordinate convention:

AxisPositive direction
xRight
yDown (CSS convention)
zToward the viewer — the same direction data-foldpage-depth pushes images and text; backgrounds instead recede away from the viewer for positive data-foldpage-depth values

Example — spinning a 3D logo cube:

When four images are CSS-positioned as faces of a 3D box (using rotateY transforms in the stylesheet), the entire group can be rotated around the box's geometric center rather than the element's screen-space center:

html
<!-- CSS positions the 4 images as faces of a 160px × 160px box.
     The box center is 115px right and 80px toward the viewer
     from the element's upper-left corner.
     data-foldpage-animate-origin shifts the rotation pivot there.
     Negative speed reverses the rotation direction. -->
<a class="logo-wrap"
   data-foldpage-animate="rotate,pulse"
   data-foldpage-animate-axis="y"
   data-foldpage-animate-speed="-0.45"
   data-foldpage-animate-origin="-115,0,-80">

  <img class="face-front"  src="logo.png">
  <img class="face-right"  src="page.png">
  <img class="face-back"   src="logo.png">
  <img class="face-left"   src="page.png">
</a>
css
.logo-wrap { position: relative; width: 400px; height: 120px; }

/* Each face is offset 80px from the box center along Z, rotated 90° apart */
.face-front { position: absolute; transform: rotateY(0deg); }
.face-right  { position: absolute; left: 240px; transform: rotateY(90deg) translateX(80px) translateZ(-80px); }
.face-back   { position: absolute; transform: rotateY(180deg) translateZ(160px); }
.face-left   { position: absolute; transform: rotateY(270deg) translateX(-80px) translateZ(80px); }

The data-foldpage-animate-origin value mirrors the inverse of the CSS offsets that position the box's center. If the box center is at +115px X, +80px Z from the logo-wrap's corner, the origin needed to pivot there from the element's own center uses the appropriate signed offsets.

Combined with speed direction:

Negative data-foldpage-animate-speed reverses the rotate direction. This is useful when multiple elements share the same rotation but need to counter-rotate for visual effect.


Axis Locks ​

After all active effects have written their values each frame, axis locks overwrite specific rotation or position axes with a fixed value. This lets you constrain one axis while other effects continue running freely on the remaining axes.

Rotation locks — value in degrees, applied to the animation group's rotation:

AttributeDefaultDescription
data-foldpage-animate-fix-x0Lock rotation.x to this angle (degrees).
data-foldpage-animate-fix-y0Lock rotation.y to this angle (degrees).
data-foldpage-animate-fix-z0Lock rotation.z to this angle (degrees).

Position locks — value in CSS pixels, applied to the animation group's position:

AttributeDefaultDescription
data-foldpage-animate-fix-pos-x0Lock position.x to this offset (CSS px).
data-foldpage-animate-fix-pos-y0Lock position.y to this offset (CSS px). Positive = down (CSS convention).
data-foldpage-animate-fix-pos-z0Lock position.z to this offset (CSS px).

The attribute value is optional — the attribute alone (no value) defaults to 0. Non-zero values are also valid; they pin the axis to that specific angle or offset.

Examples:

html
<!-- tilt tracks the cursor (or device gyro on mobile) on both X and Y.
     Fix Y to prevent the element from rotating left/right — it only nods up/down. -->
<div data-foldpage-animate="tilt"
     data-foldpage-animate-fix-y="0">…</div>

<!-- sway nods on X by default, pulse moves on Z by default.
     Fix X so only the pulse movement remains — the nodding is suppressed. -->
<div data-foldpage-animate="sway,pulse"
     data-foldpage-animate-fix-x>…</div>

<!-- pulse's axis set to y for an up/down bob (its default is z).
     Fix Y position to eliminate the bob while keeping tilt active. -->
<div data-foldpage-animate="pulse,tilt"
     data-foldpage-animate-axis="y"
     data-foldpage-animate-fix-pos-y="0">…</div>

<!-- Pin Z rotation to a constant 15° lean while tilt tilts freely on X/Y. -->
<div data-foldpage-animate="tilt"
     data-foldpage-animate-fix-z="15">…</div>

Axis locks apply to the animation layer only — they do not affect static transforms from CSS transform, which live in the parent group and are unmodified.

Fold.page — 3D UI for the web.