Skip to content

Commit

Permalink
hide-and-seek
Browse files Browse the repository at this point in the history
  • Loading branch information
martrapp committed Dec 5, 2024
1 parent b5ea31e commit ad809b8
Show file tree
Hide file tree
Showing 15 changed files with 994 additions and 59 deletions.
1 change: 0 additions & 1 deletion src/components/ReloadOnThemeChange.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
---
<script>
const select = document.querySelector("starlight-theme-select select");
console.log(select);
select?.addEventListener("change",()=>location.reload());
</script>
9 changes: 7 additions & 2 deletions src/components/SidePicture.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
---
export interface Props {
float: string;
}
const { float = "left" } = Astro.props;
---

<div>
<span style="float:left"><slot name="pic"/></span><slot />
</div>
<span style=`float: ${float}`><slot name="pic" /></span><slot />
</div>
20 changes: 13 additions & 7 deletions src/components/ToolsBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</div>
<style is:global>
#toolsbar {
display:flex;
display: flex;
flex-wrap: wrap;
}
#toolsbar > div:nth-of-type(1) {
Expand All @@ -25,19 +25,25 @@
<script is:inline>
const mark = (entry) => {
const elem = document.querySelector(
`#toolsbar > div:has(a[href="${new URL(entry.url).pathname}"]) > .logo img`
`#toolsbar > div:has(a[href="${
new URL(entry.url).pathname
}"]) > .logo img`
);
elem && elem.style.setProperty("view-transition-name", "logo");
return elem;
};
addEventListener(
"pageswap",
(e) => e.viewTransition && mark(e.activation.entry)
);
addEventListener("pageswap", (e) => {
if (e.viewTransition) {
const elem = mark(e.activation.entry);
e.viewTransition.finished.then(() =>
elem?.style.removeProperty("view-transition-name")
);
}
});
addEventListener("pagereveal", (e) => {
if (e.viewTransition) {
const elem = mark(navigation.activation.from);
e.viewTransition.ready.then(() =>
e.viewTransition.finished.then(() =>
elem?.style.removeProperty("view-transition-name")
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/content/docs/baglog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ In no particular order:
<summary>Morph aspect ratio of button with stable text</summary>
The view transition API turns elements into bitmaps. If you stretch a button, you also stretch the button's text, don't you?
</details>
<details name="log">
<summary>How to debug view transitions</summary>
Give a quick tour on how to use the animation section in Chrome's Developer Tools and the Inspection Chamber to debug view transitions.
</details>

<style>{`
Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/basics/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Morphing thumbnails into hero images is a classic use case for view transitions.
| Name | Description |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| <DemoLink id="basicim" href="/demo/BasicIM/">Same aspect ratio</DemoLink> | The default morph transition with same aspect-ratio images |
| <DemoLink id="basicim2" href="/demo/BasicIM2/">Different image and aspect ratio</DemoLink> | The default morph transition with images of different aspect-ratio |
| <DemoLink id="basicim2" href="/demo/BasicIM2/">Different image</DemoLink> | The default morph transition with different images and different aspect-ratio |
| <DemoLink id="basicim3" href="/demo/BasicIM3/">Clipped overflow</DemoLink> | An alternative morph transition where images do not grow beyond the group outline |
| <DemoLink id="basicim4" href="/demo/BasicIM4/">Covered object fit</DemoLink> | A non-overflowing alternative for images with different aspect-ratio |
| <DemoLink id="basicim5" href="/demo/BasicIM5/">Changing aspect ratio</DemoLink> | A neat trick to morph the same image between different aspect ratios |
Expand Down
351 changes: 310 additions & 41 deletions src/content/docs/basics/hide-and-seek/index.mdx

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions src/content/docs/basics/pseudos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Think of it like a theater: A curtain drops, painted with the last scene, while
background-color: #000;
}
div.small {
.container div.small {
width: auto;
height: auto;
background-color: rgba(255, 128, 0, 0.1);
Expand Down Expand Up @@ -237,10 +237,7 @@ With the one to one relation between groups and image pairs, the group looks a b

## Creation of Pseudo-Elements

The `::view-transition-old` pseudo elements are created in a pre-order depth first traversal of the old DOM. When an element with a `view-transition-name` is encountered, its old image is captured. During this process, other elements with view transition names are effectively ignored, as if their `visibility` were set to `hidden`. This ensures that the images have "holes" where other images can be properly placed.

The ::view-transition-old pseudo-elements are created during a pre-order depth-first traversal of the old DOM. When an element with a view-transition-name is encountered, its old image is rendered. During this process, other elements with view transition names are effectively ignored, as if their visibility were set to hidden. This ensures that the images have "holes" where other images can be properly placed.

The `::view-transition-old` pseudo elements are created **in the paint-order** of the elements of the old DOM. When an element with a `view-transition-name` is encountered, its old image is captured. During this process, other elements with view transition names are effectively ignored, as if their `visibility` were set to `hidden`. This ensures that the images have "holes" where other images can be properly placed.

The image of the `:root` element, if not [opted out](#opt-out), covers the whole viewport. Its view transition group is the first in the list of `::view-transition-group` children of the `::view-transition` pseudo-element. Subsequent groups are added after the root group once their old images have been created.

Expand Down
1 change: 1 addition & 0 deletions src/pages/basics/hide-and-seek/_solution_styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
}
::view-transition-new(*) {
animation: none;
transform: scale(1.01)
}
:root {
--neon-pink: #ff2e88;
Expand Down
38 changes: 38 additions & 0 deletions src/pages/basics/hide-and-seek/cutter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

export function cutSVG(element: Element, cutters:{element:HTMLElement,inset: string}[]) {
if (!element) return;
const masks = ['<rect width="100%" height="100%" fill="white" />'];
const outerRect = element.getBoundingClientRect();
cutters.forEach((cutter) => {
const inner = cutter.element;
const insets = cutter.inset.split(" ").map(parseFloat);
for (let i = 0; i < 4; i++) {
const len = insets.length;
insets[i] = isNaN(insets[i] ?? 0)
? 0
: (insets[len === 3 && i === 3 ? 2 : i % len] ?? 0);
}
const innerRect = inner.getBoundingClientRect();
const innerStyle = getComputedStyle(inner);

const left = parseFloat(innerStyle.borderLeftWidth);
innerRect.width -=
left +
parseFloat(innerStyle.borderRightWidth) +
insets[1]! +
insets[3]!;
innerRect.x += left + insets[3]!;
const top = parseFloat(innerStyle.borderTopWidth);
innerRect.height -=
top +
parseFloat(innerStyle.borderBottomWidth) +
insets[0]! +
insets[2]!;
innerRect.y += top + insets[0]!;
masks.push(
`<rect x="${innerRect.x - outerRect.x}" y="${innerRect.y - outerRect.y}" width="${innerRect.width}" height="${innerRect.height}" fill="black" />`
);
});
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${outerRect.width}" height="${outerRect.height}">${masks.join("")}</svg>`;
return svg;
}
104 changes: 104 additions & 0 deletions src/pages/basics/hide-and-seek/list-flat.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
import LightDark from "@/components/LightDark.astro";
// demonstrate hiding with other images
---

<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<LightDark />
</head><body>
<ul id="ul">
<li style="view-transition-name: l1">Item 1</li>
<li style="view-transition-name: l2">Item 2</li>
<li style="view-transition-name: l3">Item 3</li>
<li style="view-transition-name: l4">Item 4</li>
<li style="view-transition-name: l5">Item 5</li>
<li style="view-transition-name: l6">Item 6</li>
<li style="view-transition-name: l7">Item 7</li>
</ul>
<button>[Rotate]</button>
<script>
// @ts-expect-error
ul.scrollTop = 40;
document.querySelectorAll("button").forEach((button) => {
button.addEventListener("click", () => {
const update = () => {
const li = document.querySelector<HTMLLIElement>("li")!;
const y = li.parentElement!.scrollTop;
li.parentElement!.insertAdjacentElement("beforeend", li);
y && (li.parentElement!.scrollTop = y);
};
document.startViewTransition
? document.startViewTransition(update)
: update();
});
});
</script>
<style is:global>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: monospace;
}
@view-transition {
navigation: auto;
}
body {
padding-top: 20px;
}
button {
width: 150px;
color: black;
background-color: #aca;
}
ul {
overflow-y: scroll;
height: 200px;
list-style: none;
width: 150px;
border: 1px solid grey;
}
li {
padding: 10px;
color: white;
background-color: #822;
margin: 5px;
border-radius: 5px;
}

::view-transition-group(*) {
animation-duration: 0.8s;
}
::view-transition-image-pair(l1) {
animation: slide-left 0.8s 0s;
}
::view-transition-image-pair(l2) {
animation: slide-left 0.7s 0.1s;
}
::view-transition-image-pair(l3) {
animation: slide-left 0.6s 0.2s;
}
::view-transition-image-pair(l4) {
animation: slide-left 0.5s 0.3s;
}
::view-transition-image-pair(l5) {
animation: slide-left 0.4s 0.4s;
}
::view-transition-image-pair(l6) {
animation: slide-left 0.3s 0.5s;
}
::view-transition-image-pair(l7) {
animation: slide-left 0.2s 0.6s;
}
@keyframes slide-left {
50% {
transform: translateX(60px);
}
}
</style>
</body>
</html>
112 changes: 112 additions & 0 deletions src/pages/basics/hide-and-seek/list-names.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
import LightDark from "@/components/LightDark.astro";
// demonstrate list rotation with a reduced set of named elements
---

<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<LightDark />
</head><body>
<div id="stage">
<ul id="ul">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
</ul>
<button id="button">[Rotate]</button>
</div>
<script>
// @ts-ignore
const button = window.button;
// @ts-ignore
const ul = window.ul;
ul.scrollTop = 40;
button.addEventListener("click", () => {
let first = undefined as unknown as HTMLLIElement;
let last = undefined as unknown as HTMLLIElement;
const rect = ul.getBoundingClientRect();
ul.querySelectorAll<HTMLLIElement>("li").forEach(
(li: HTMLLIElement, idx: number) => {
const lire = li.getBoundingClientRect();
const inside = lire.top >= rect.top && lire.bottom <= rect.bottom;
li.style.viewTransitionName = inside ? `l${idx + 1}` : "none";
first ??= inside ? li : first;
last = inside ? li : last;
}
);
const update = () => {
first.style.viewTransitionName = "none";
const y = ul.scrollTop;
ul.insertBefore(ul.firstElementChild, null);
(last.nextElementSibling as HTMLElement).style.viewTransitionName =
"last";
ul.scrollTop = y;
};
document.startViewTransition
? document.startViewTransition(update)
: update();
});
</script>
<style is:global>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: monospace;
}
#stage {
width: 150px;
}
ul {
margin: 0;
overflow-y: scroll;
height: 205px;
list-style: none;
border: 1px solid grey;
scroll-snap-type: y mandatory;
scroll-padding-top: 4px;
}
li {
padding: 10px;
color: black;
background-color: #dfd;
margin: 5px;
border-radius: 5px;
scroll-snap-align: start;
}
button {
width: 100%;
color: black;
background-color: #aca;
}
::view-transition-group(*) {
animation-duration: 0.4s;
}
::view-transition-old(*):only-child {
animation: shrink 0.3s ease-out;
}
::view-transition-new(*):only-child {
animation: grow 0.4s ease-in-out;
}
@keyframes shrink {
to {
transform: scaleY(0);
transform-origin: top;
}
}
@keyframes grow {
from {
transform: scaleY(0);
transform-origin: bottom;
}
}
</style>
</body>
</html>
Loading

0 comments on commit ad809b8

Please sign in to comment.