Skip to content

Views & Navigation ​

A few data attributes turn page scrolling into camera movement through the 3D scene, and let clicks fly the camera to any element.


Overview ​

There are two complementary navigation mechanisms:

MechanismAttributeTriggerDescription
Viewsdata-foldpage-viewScrollCamera frames named elements as you scroll past them
View Linksdata-foldpage-view-linkClickClick any element to fly the camera directly to it

These can be used independently or together. A page might use view links for an image gallery (click any image to zoom in) and views for landmark moments (camera pauses at chapter headings).


Views: Scroll-Driven Camera Framing ​

What Views Do ​

A view is an element the camera frames as the user scrolls to it. The camera flies to face the element head-on and holds it in frame, so each view is a waypoint in a scroll-driven journey.

Marking Views ​

Add data-foldpage-view to any element inside the .foldpage wrapper — including framework custom elements such as Angular components, React elements, or Web Components:

html
<!-- Standard HTML elements -->
<div data-foldpage-view>Auto-ordered view One</div>
<section data-foldpage-view="0">Home / landing view</section>
<section data-foldpage-view="2">Second chapter</section>
<section data-foldpage-view="1">First chapter</section>

<!-- Framework component elements work identically -->
<app-hero    data-foldpage-view="0" />
<app-section data-foldpage-view />
<my-panel    data-foldpage-view />

Value options:

ValueMeaning
"0"Home view — the initial camera position on page load
"1", "2", etc.Numbered views, framed in numeric order as you scroll down
"" (empty)Unnumbered — inserted in DOM order relative to other unnumbered views

Views are collected into an ordered list. As the scroll position advances through the document, the camera transitions to each successive view, framing the element with configurable padding on all sides.

How Camera Framing Works ​

The camera faces the element's surface, backs off far enough to fit it with some padding, and moves there with a slightly springy animation.

Content That Isn't a View ​

Not everything on the page needs to be marked as a view. As soon as at least one data-foldpage-view element exists, any ordinary content nearby — a nav bar before the first view, a footer after the last one, or a plain section left between two views — is automatically positioned to continue naturally from the view it's closest to, and the scroll range is extended so it pans into view like everything else. No extra markup needed. See View Layouts → Content Around Your Views for more detail.


A view link flies the camera to frame an element when it's clicked. Typical uses:

  • Image galleries — click any thumbnail to zoom into it
  • Navigation menus — click a menu item to fly to that section
  • Interactive maps or diagrams — click a point of interest to focus on it
  • Product showcases — click any item to bring it front and center

data-foldpage-view-link accepts two forms:

Plain (no value) — frame the clicked element:

html
<img src="gallery/photo1.jpg" data-foldpage-view-link>
<article data-foldpage-view-link>
  <h2>Project Alpha</h2>
  <p>Click to explore</p>
</article>
<button data-foldpage-view-link>Focus this section</button>

When clicked, the camera flies to frame the element itself at the exact point the user clicked.

Anchor ("#id") — fly to a different element by ID:

html
<!-- Navigation menu: clicking each item flies the camera to that section -->
<nav>
  <ul>
    <li><a href="#about" data-foldpage-view-link="#about">Hello</a></li>
    <li><a href="#work" data-foldpage-view-link="#work">Work</a></li>
    <li><a href="#connect" data-foldpage-view-link="#connect">Connect</a></li>
  </ul>
</nav>

<!-- Target sections elsewhere on the page -->
<section id="about" data-foldpage-view="1">...</section>
<section id="work" data-foldpage-view="2">...</section>
<section id="connect" data-foldpage-view="3">...</section>

The anchor form is the 3D version of <a href="#about">: it moves the camera instead of jumping the page. A regular href on the same element still works alongside it.

When data-foldpage-view-link="#id" is clicked, Fold.page resolves the target element and chooses a behavior based on what the target is:

Target elementBehavior
Has data-foldpage-viewTriggers a scroll-based view jump — same as navigating the scroll to that view. No camera spring.
Regular element (no data-foldpage-view)Camera springs to frame the target. If the page is scrollable, the page also scrolls so the target is near the viewport top before the camera fine-aligns.
ID not foundFalls back to framing the source element (the clicked link).

Clicking the same link-view a second time dismisses it and returns to the normal scroll-driven position.

The Click-to-Frame Experience ​

When a plain view link (no #id) is clicked, the camera flies to the exact point that was clicked, not just the element's center — clicking the corner of a large image zooms into that corner. The camera holds there until the view is dismissed.

A focused view link is dismissed — and the camera returns to its scroll-driven position — when the user:

  • clicks the same view link again,
  • presses Escape, or
  • scrolls past a small threshold (100px on screen), so small accidental scrolls don't dismiss it.

Active View Styling ​

Fold adds two classes you can style in your own stylesheet:

AttributeClassAdded while...
data-foldpage-view-link.foldpage-view-link-focused...the camera is framing this link's target. Removed on dismiss.
data-foldpage-view.foldpage-view-focused...this view is fully in focus. While moving between views, no view has it.

.foldpage-view-focused is set by whichever way a view became active — scrolling, a view link, or Fold.viewTo() — so it is the reliable class for styling the current view.

css
/* Highlight a view-link nav item while its own link view is open */
[data-foldpage-view-link].foldpage-view-link-focused {
  outline: 2px solid #7cc7ff;
  outline-offset: 2px;
}

/* Distinguish whichever view currently has full (non-transitioning) focus */
[data-foldpage-view].foldpage-view-focused {
  box-shadow: 0 0 0 2px #7cc7ff inset;
}

See the CSS Class API for the class reference.


Programmatic Navigation: Fold.viewTo() ​

Fold.viewTo() is the JavaScript API equivalent of clicking a data-foldpage-view-link. It flies the camera to any element on the page — no HTML attribute required.

js
Fold.viewTo('#hero')              // by CSS selector
Fold.viewTo('.highlight')         // any CSS selector
Fold.viewTo(document.querySelector('.card'))  // or a direct HTMLElement reference

Signature ​

ts
Fold.viewTo(target: string | HTMLElement): void
ParameterTypeDescription
targetstringAny CSS selector — passed to document.querySelector
targetHTMLElementA direct element reference — useful from event handlers

viewTo chooses its behavior the same way data-foldpage-view-link="#id" does:

Target elementBehavior
Has data-foldpage-viewScroll-jump to the scroll position that frames that view — same as arrow-key navigation
Any other elementCamera spring — framing flies smoothly to the element

Toggle / dismiss ​

Calling viewTo on the element that is already active dismisses it — the camera pulls back to the normal scroll-driven position. This matches the behavior of clicking an active view-link a second time.

js
Fold.viewTo('#hero')  // zoom in
Fold.viewTo('#hero')  // called again → dismiss, return to scroll position

Use cases ​

Keyboard shortcuts:

js
document.addEventListener('keydown', (e) => {
  if (e.key === '1') Fold.viewTo('#intro')
  if (e.key === '2') Fold.viewTo('#features')
  if (e.key === '3') Fold.viewTo('#gallery')
  if (e.key === 'Escape') Fold.viewTo(document.querySelector('[data-active-view]'))
})

Self-zooming cards — any element can fly the camera to itself on click without needing data-foldpage-view-link:

html
<div class="card" onclick="Fold.viewTo(this)">
  <h2>Click to zoom</h2>
</div>

Timer-driven tour:

js
const sections = ['#intro', '#features', '#gallery', '#contact']
let idx = 0
setInterval(() => {
  Fold.viewTo(sections[idx % sections.length])
  idx++
}, 4000)

From a custom UI outside .foldpage:

js
// A button that lives outside the Fold.page container
document.querySelector('#my-nav-btn').addEventListener('click', () => {
  Fold.viewTo('#target-section')
})

View Dimming ​

Non-active views are automatically dimmed to help the user focus on the view currently in frame. The dim value controls the opacity of all views that are not the current destination — the active view always renders at full opacity.

Global Dim Value ​

Set the dim level in Fold.init() or update it at runtime with Fold.set():

ts
Fold.init({ viewDim: 0.5 }); // inactive views at 50% opacity
ts
Fold.set({ viewDim: 0.2 }); // update without reinitialising

The default is 0.8 (inactive views at 80% opacity). Set to 1 to disable dimming.

Per-Container Override ​

Place data-foldpage-view-dim on any ancestor element wrapping data-foldpage-view elements to override the dim value for that group only:

html
<!-- These three panels dim to 20% when not active -->
<div data-foldpage-view-dim="0.2">
  <section data-foldpage-view>Panel A</section>
  <section data-foldpage-view>Panel B</section>
  <section data-foldpage-view>Panel C</section>
</div>

This pairs naturally with View Layouts — place data-foldpage-view-dim on the layout container to scope the override to all panels in that layout:

html
<div data-foldpage-view-layout="ring"
     data-foldpage-view-dim="0.3">
  <header data-foldpage-view="0">Center</header>
  <section data-foldpage-view>Panel 1</section>
  <section data-foldpage-view>Panel 2</section>
</div>

The nearest ancestor with data-foldpage-view-dim wins; the global viewDim value is the fallback.

Dimming Behaviour ​

  • Transition between views — the outgoing view fades toward the dim value and the incoming view fades up to full opacity, proportional to scroll progress.
  • Tall views (content taller than the viewport) — the view holds at full opacity for the entire scroll zone; dimming only occurs during the transition into or out of it.
  • View links — when data-foldpage-view-link navigates to a target, the linked view snaps to full opacity; all others dim.

Custom View Orientation ​

By default, the camera orients to look directly at a view element's face. You can override this with an explicit orientation:

html
<!-- Camera approaches from a 45° angle on Y -->
<div data-foldpage-view data-foldpage-view-rotation="0 45 0">
  Side-angle view
</div>

<!-- Shift the focal point 50px right and 20px down -->
<div data-foldpage-view data-foldpage-view-origin="50 20 0">
  Off-center framing
</div>

data-foldpage-view-rotation takes a space- or comma-separated "x y z" triplet in degrees. This is an absolute orientation — not relative to the element's normal. Useful when you want a specific cinematic angle or when the element is not a flat surface facing the viewer.

data-foldpage-view-origin shifts the focal point (in CSS pixels) used when computing the camera position, allowing fine-tuned framing of specific areas within the element. Y follows CSS convention: positive = down, negative = up.


Auto-Origin in XR ​

In XR, the page re-centers in front of the user when they move or turn. See XR Features — Auto-Origin.


Combining Navigation Mechanisms ​

Views, view links, and anchor links can coexist on the same page:

html
<div class="foldpage">

  <!-- Sticky nav — anchor links fly the camera to each section -->
  <nav style="position: sticky; top: 0;">
    <a href="#about" data-foldpage-view-link="#about">About</a>
    <a href="#work"  data-foldpage-view-link="#work">Work</a>
    <a href="#gallery" data-foldpage-view-link="#gallery">Gallery</a>
  </nav>

  <!-- Home view: initial camera position -->
  <header data-foldpage-view="0">
    <h1>Portfolio</h1>
  </header>

  <!-- Named sections — targets for anchor links AND scroll-driven views -->
  <section id="about" data-foldpage-view="1">
    <h2>About</h2>
  </section>

  <section id="work" data-foldpage-view="2" data-foldpage-view-rotation="0 -20 0">
    <h2>Work (viewed from slight angle)</h2>
  </section>

  <!-- Gallery — images are plain view links (frame on click) -->
  <section id="gallery" data-foldpage-view="3">
    <h2>Gallery</h2>
    <img src="project1.jpg" data-foldpage-view-link>
    <img src="project2.jpg" data-foldpage-view-link>
    <img src="project3.jpg" data-foldpage-view-link>
  </section>

</div>

Interaction model:

  • Scrolling triggers view transitions as the camera frames each section in sequence.
  • Clicking a nav anchor (data-foldpage-view-link="#about") flies the camera directly to that section's view, bypassing scroll. The page also scrolls to place the section in frame.
  • Clicking a gallery image (data-foldpage-view-link) zooms the camera into that image at the exact click point.
  • Scrolling past the dismiss threshold returns to scroll-driven navigation.

View Layouts ​

To automatically position views around a center view in a 3D ring or arc — without writing manual position: absolute or CSS transforms — see View Layouts.

Layouts run at Fold.init() and set each panel's position before rendering; views and view links work on the result unchanged.

Fold.page — 3D UI for the web.