Skip to main content

Transitions

This library supports the CSS View Transition API to enable smooth animations when navigating between pages.
For example, you can build a carousel where each card smoothly transitions into a detailed view.

warning

The View Transition API is available in every modern browser with the exception of Firefox. See here for browser compatibility.

warning

The View Transition API does not work well when developing using web server. For better experience open your app in the browser with CORS disabled. Example for Chrome on macOS:

open -n -a /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security

Step 1

Create a new app using CLI:

npm create nodality@latest my-app

Step 2 - Define the card component

index.html

  1. Set up the environment
    • Add the meta tag with view-transition set to same-origin.
    • Declare @view-transition { navigation: auto; } in your CSS. This enables automatic page navigation transitions.
  2. Create a card
    • The vtn attribute is shorthand for view-transition-name.
    • A matching vtn value must exist both in the parent view (index.html) and in the child view (detail page) for the transition to animate correctly.
  3. Use the HScroller
    • The HScroller class is used to build a horizontally scrolling card carousel.
    • Configure scrolling speed using .seto({ speed }) (higher values = slower scrolling).
    • Add multiple card instances with .add([...]). Each card should have a unique identifier (id or textChild) to ensure smooth transitions.
<meta charset="UTF-8">
<meta name="view-transition" content="same-origin">
<script type="module" src="dist/lib.bundle.js"></script>

<style>
body {
background: gray;
overflow: hidden;
margin: 0;
padding: 0;
}

@view-transition {
navigation: auto;
}
</style>

<div id="mount"></div>


<script type="module">

// Define Card class
class Card {
constructor(obj){
this.id = obj.id;
this.child = obj.child;
this.url = obj.url;
this.textChild = obj.textChild;
this.icon = obj.icon;
}

render(){
return new Wrapper({ isLink: true, child: this.child })
.set({
id: this.id,
child: this.child,
class: this.id + "o",
font: "Arial"
})
.add([


new Image(
this.url)
.set({
width: "400px",
height: "300px",
objectFit: "cover",

class: this.id,
vtn: this.textChild + "img"
})
]);

}
}


// Initialize HScroller and add a card
new HScroller()
.seto({
speed: 1.0 // the bigger the slower
})
.add([
new Card({
child: "child-A.html",
textChild: "icon",
icon: "child-A-icon",
url: "https://media.cnn.com/api/v1/images/stellar/prod/ap24002766263400.jpg?c=original",
}).render()
])
.render("#mount");

</script>

Step 3 - Create the detail view

child-A.html

The detail page is where the user is taken after clicking on a card.


<script>
<meta name="view-transition" content="same-origin">

<body></body>

<style>
body {
background: gray;
margin: 0;
padding: 0;
}

@view-transition {
navigation: auto;
}

</style>

<script type="module" src="dist/lib.bundle.js"></script>
<script type = "module" src="detail.js"></script>

Step 4 - Use detail.js

In detail.js, you can:

  • Render a title, text, and images.
  • Animate elements (e.g., header text sliding in).
  • Provide navigation back to the index page (e.g., with a back arrow).
  • Use the same vtn value (iconimg) for the image to link the transition between index.html and child-A.html.

The code already:\

  • Creates a heading and animates it.
  • Adds a back button (Link("Hello", "index.html")).
  • Renders the same image with the same vtn name.
  • Appends multiple paragraphs of text to simulate a content-rich detail view.


let r = new Center().items(
[
new Text("Best ship")
.set({
font: "Arial",
clampc: "S1",
em: 4.2,
color: "white",
weight: "bold",
removeDecoration: true,
id: "header",
arrayMargin: { sides: ["top"], value: "7rem" },
})
]
)

let ropo = document.createElement("a");
ropo.setAttribute("href", "index");

let l = new Link("Hello", "index.html").set({
link: "index.html",
data: {
options: {
url: "index.html",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Back_Arrow.svg/2048px-Back_Arrow.svg.png",
size: "70px",
radius: "0.1rem"
}
},

});

let flex = new FlexRow().items([
l,
new Spacer(true),
]);

let ela = flex.render();
ela.style.marginTop = "1rem";
document.body.appendChild(ela);


let el = r.render();

document.body.appendChild(el);
document.querySelector("#header").style.transform = "translateY(200px)";
document.querySelector("#header").animate([
{ opacity: "1", opacity: "0" },
{ transform: "translateY(200px)", transform: "translateY(0px)", },

], {
duration: 800,
fill: 'forwards'
});


let a = document.createElement('a');
a.href = 'index.html';

let e = new Image("https://media.cnn.com/api/v1/images/stellar/prod/ap24002766263400.jpg?c=original", 'exact').set({
url: 'https://media.cnn.com/api/v1/images/stellar/prod/ap24002766263400.jpg?c=original',
class: "img-small",
width: "900px",
height: "600px",

arrayMargin: { sides: ["top"], value: "7rem" },
vtn: "iconimg"
});

let ra = new Center().items(
[
e
]);



let tz = ra.render();

a.appendChild(tz);
document.body.appendChild(a);


let paragraph = new Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam a maximus massa. Nam at egestas ante, vitae posuere turpis. Suspendisse non metus sagittis, tristique sem id, pellentesque urna. Phasellus tempus quam at tellus suscipit, non maximus leo euismod. Nunc quis facilisis turpis. Donec tristique vel felis eu finibus. Maecenas malesuada rutrum est pulvinar dapibus. Praesent eu sem condimentum, posuere turpis sit amet, mollis libero. Praesent nulla nibh, sagittis sed justo ut, facilisis malesuada odio.")
.set({
font: "Arial",
clampc: "S1",
em: 1.2, color: "white",
weight: "bold",
removeDecoration: true,
id: "header",
arrayMargin: { sides: ["left", "right"], value: "auto" },
width: "50%",
arrpad: { sides: ["bottom", "top"], value: "2.6rem" }
});

document.body.appendChild(paragraph.render());

let paragraphB = new Text("Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam vulputate nunc a turpis tristique vestibulum. Aliquam pretium aliquam risus, ac laoreet eros ullamcorper et. Maecenas elementum libero vestibulum cursus fringilla. Sed facilisis, nibh et convallis pharetra, eros nisl placerat risus, eget varius magna arcu sit amet dui. Pellentesque consectetur nulla sit amet arcu tristique varius. Nullam at elit sit amet lectus cursus tempus vel et ligula. Pellentesque a tincidunt felis, vitae semper enim. Integer luctus nunc nec elementum dignissim. Nam aliquet euismod quam, non pellentesque arcu tristique porta.")
.set({
font: "Arial",
clampc: "S1",
em: 1.2,
color: "white",
weight: "bold",
removeDecoration: true,
id: "header",
width: "50%",
arrayMargin: { sides: ["left", "right"], value: "auto" },
arrpad: { sides: ["bottom"], value: "2.6rem" }
});


let paragraphC = new Text("Nullam a ornare eros, a vestibulum nulla. Aenean cursus dui pharetra, pellentesque leo eu, euismod tortor. Curabitur tempor mollis lorem, in dignissim nisl convallis at. Etiam dictum, ex eu tempus varius, massa purus malesuada ipsum, elementum aliquet sem quam ut ante. Quisque sed facilisis lacus. Curabitur aliquet egestas nisl sit amet malesuada. Cras vitae finibus ligula, vitae vestibulum magna. Cras molestie ac elit eu aliquam. Phasellus posuere sit amet tellus ut mattis. Quisque blandit at lacus at hendrerit.")
.set({
font: "Arial",
clampc: "S1",
em: 1.2,
color: "white",
weight: "bold",
removeDecoration: true,
id: "header",
width: "50%",
arrayMargin: { sides: ["left", "right"], value: "auto" },
});

document.body.appendChild(paragraph.render());
document.body.appendChild(paragraphB.render());
document.body.appendChild(paragraphC.render());

Key Notes

  • vtn consistency: The vtn attribute (shorthand for view-transition-name) must match between the originating element and its target for the transition to animate properly.\
  • Navigation: The @view-transition { navigation: auto; } CSS rule makes browser navigation (a href="...") eligible for transitions.\
  • Fallback: In browsers without View Transition API (e.g., Firefox), navigation will still work, but without smooth transitions.\
  • Customization: You can add animations (e.g., .animate([...], { duration })) for fine-grained control of how elements enter or leave the page.