diff --git a/css-view-transitions-2/Overview.bs b/css-view-transitions-2/Overview.bs index 13780fa960c..66d717f1416 100644 --- a/css-view-transitions-2/Overview.bs +++ b/css-view-transitions-2/Overview.bs @@ -1,12 +1,13 @@
 Title: CSS View Transitions Module Level 2
-Shortname: css-view-transitions-2
+Shortname: css-view-transitions
 Level: 2
-Status: ED
+Status: FPWD
 Group: csswg
-Date: 2023-05-30
+Date: 2024-05-09
 Prepare for TR: yes
 ED: https://drafts.csswg.org/css-view-transitions-2/
+TR: https://www.w3.org/TR/css-view-transitions-2/
 Work Status: exploring
 Editor: Noam Rosenthal, Google, w3cid 121539
 Editor: Khushal Sagar, Google, w3cid 122787
diff --git a/css-view-transitions-2/diagrams/desktop-browser-snapshot-root.svg b/css-view-transitions-2/diagrams/desktop-browser-snapshot-root.svg
new file mode 100644
index 00000000000..79d3e8ef821
--- /dev/null
+++ b/css-view-transitions-2/diagrams/desktop-browser-snapshot-root.svg
@@ -0,0 +1 @@
+Exampleexample.comSnapshotroot
diff --git a/css-view-transitions-2/diagrams/desktop-browser.svg b/css-view-transitions-2/diagrams/desktop-browser.svg
new file mode 100644
index 00000000000..32ba150eca3
--- /dev/null
+++ b/css-view-transitions-2/diagrams/desktop-browser.svg
@@ -0,0 +1 @@
+Exampleexample.com
diff --git a/css-view-transitions-2/diagrams/phases/phases.html b/css-view-transitions-2/diagrams/phases/phases.html
new file mode 100644
index 00000000000..1141fa13d4c
--- /dev/null
+++ b/css-view-transitions-2/diagrams/phases/phases.html
@@ -0,0 +1,98 @@
+
+
+
+
diff --git a/css-view-transitions-2/diagrams/phases/script.js b/css-view-transitions-2/diagrams/phases/script.js new file mode 100644 index 00000000000..ee1af79e6ab --- /dev/null +++ b/css-view-transitions-2/diagrams/phases/script.js @@ -0,0 +1,129 @@ +import { Slide, transition, transitionFrom } from "../resources/slides.js"; + +const slide = new Slide(async function* () { + slide.innerHTML = ` +
+ + + +
Developer calls document.startViewTransition()
+
+ `; + + /** @type {HTMLElement[]} */ + const [domPage, transitionPage, combinedPage] = + slide.querySelectorAll(".page"); + + /** @type {HTMLElement} */ + const whatUserSees = slide.querySelector(".what-user-sees"); + + // This pauses the slide until 'next' is clicked. + yield; + + /** @type {HTMLElement} */ + const step = slide.querySelector(".step"); + step.textContent = `Current state captured as the "old" state`; + + yield; + + step.textContent = "Rendering paused"; + whatUserSees.textContent = "(Paused render)"; + + yield; + + step.textContent = "Developer updates document state"; + domPage.innerHTML = `
State 2
`; + + yield; + + step.textContent = `Current state captured as the "new" state`; + + yield; + + step.textContent = "Transition pseudo-elements created"; + transitionPage.innerHTML = ` +
+
State 1
+
State 2
+
+ `; + + yield; + + step.textContent = + "Rendering unpaused, revealing the transition pseudo-elements"; + whatUserSees.textContent = "(Transition root)"; + + yield; + + step.textContent = "Pseudo-elements animate"; + + // Wow, this would be way easier with view transitions… + const states = [transitionPage, combinedPage].map((el) => + el.querySelector(".states") + ); + const state1s = [transitionPage, combinedPage].map((el) => + el.querySelector(".state-1") + ); + const state2s = [transitionPage, combinedPage].map((el) => + el.querySelector(".state-2") + ); + + for (const state of states) { + transition( + state, + { transform: "translate(219px, 469px)" }, + { + duration: 1000, + easing: "ease-in-out", + } + ); + } + + for (const state1 of state1s) { + transition( + state1, + { opacity: "0" }, + { + duration: 1000, + easing: "ease-in-out", + } + ); + } + + for (const state2 of state2s) { + transition( + state2, + { opacity: "1" }, + { + duration: 1000, + easing: "ease-in-out", + } + ); + } + + yield; + + step.textContent = "Transition pseudo-elements removed"; + transitionPage.innerHTML = ""; + whatUserSees.textContent = "(Main DOM)"; +}); + +document.querySelector(".stage").append(slide); diff --git a/css-view-transitions-2/diagrams/phone-browser-scrolled-to-top-with-url.svg b/css-view-transitions-2/diagrams/phone-browser-scrolled-to-top-with-url.svg new file mode 100644 index 00000000000..4499d92c70a --- /dev/null +++ b/css-view-transitions-2/diagrams/phone-browser-scrolled-to-top-with-url.svg @@ -0,0 +1 @@ +position: fixed elementPage contentexample.com diff --git a/css-view-transitions-2/diagrams/phone-browser-scrolled-to-top-without-url.svg b/css-view-transitions-2/diagrams/phone-browser-scrolled-to-top-without-url.svg new file mode 100644 index 00000000000..4d457beb7da --- /dev/null +++ b/css-view-transitions-2/diagrams/phone-browser-scrolled-to-top-without-url.svg @@ -0,0 +1 @@ +position: fixed elementPage content diff --git a/css-view-transitions-2/diagrams/phone-browser-snapshot-root.svg b/css-view-transitions-2/diagrams/phone-browser-snapshot-root.svg new file mode 100644 index 00000000000..700a29b8a1f --- /dev/null +++ b/css-view-transitions-2/diagrams/phone-browser-snapshot-root.svg @@ -0,0 +1 @@ +example.comQWERTYUIPOASDFGHJKLZXCVBNMspace12:42Snapshotroot diff --git a/css-view-transitions-2/diagrams/phone-browser-with-url.svg b/css-view-transitions-2/diagrams/phone-browser-with-url.svg new file mode 100644 index 00000000000..57a63111a0e --- /dev/null +++ b/css-view-transitions-2/diagrams/phone-browser-with-url.svg @@ -0,0 +1 @@ +position: fixed; elementPage contentexample.com diff --git a/css-view-transitions-2/diagrams/phone-browser-without-url.svg b/css-view-transitions-2/diagrams/phone-browser-without-url.svg new file mode 100644 index 00000000000..a5c70cfdfdb --- /dev/null +++ b/css-view-transitions-2/diagrams/phone-browser-without-url.svg @@ -0,0 +1 @@ +position: fixed; elementPage contentThis content was ‘beneath’the URL bar diff --git a/css-view-transitions-2/diagrams/phone-browser.svg b/css-view-transitions-2/diagrams/phone-browser.svg new file mode 100644 index 00000000000..1a98160d6f7 --- /dev/null +++ b/css-view-transitions-2/diagrams/phone-browser.svg @@ -0,0 +1 @@ +example.comQWERTYUIPOASDFGHJKLZXCVBNMspace12:42 diff --git a/css-view-transitions-2/diagrams/resources/scaler.js b/css-view-transitions-2/diagrams/resources/scaler.js new file mode 100644 index 00000000000..680ec738716 --- /dev/null +++ b/css-view-transitions-2/diagrams/resources/scaler.js @@ -0,0 +1,45 @@ +export class Scaler extends HTMLElement { + static observedAttributes = ["canvaswidth", "canvasheight"]; + + #shadowRoot; + #scaledElement; + #contentElement; + + attributeChangedCallback(name, oldValue, newValue) { + const width = Number(this.getAttribute("canvaswidth")); + const height = Number(this.getAttribute("canvasheight")); + + this.#contentElement.style.aspectRatio = `${width} / ${height}`; + this.#scaledElement.style.width = `${width}px`; + this.#scaledElement.style.height = `${height}px`; + } + + constructor() { + super(); + this.#shadowRoot = this.attachShadow({ mode: "closed" }); + this.#shadowRoot.innerHTML = ` + +
+
+
+ `; + this.#scaledElement = this.#shadowRoot.querySelector(".scaled"); + this.#contentElement = this.#shadowRoot.querySelector(".content"); + + new ResizeObserver(([entry]) => { + this.#scaledElement.style.transform = `scale(${ + entry.contentRect.width / Number(this.getAttribute("canvaswidth")) + })`; + }).observe(this.#contentElement); + } +} + +customElements.define("spec-scaler", Scaler); diff --git a/css-view-transitions-2/diagrams/resources/slides.js b/css-view-transitions-2/diagrams/resources/slides.js new file mode 100644 index 00000000000..c48b7714fd3 --- /dev/null +++ b/css-view-transitions-2/diagrams/resources/slides.js @@ -0,0 +1,111 @@ +export class Slide extends HTMLElement { + #slideFunction; + #slideIterator; + #currentState = -1; + #queueChain = Promise.resolve(); + #done = false; + + useTransitions = false; + + constructor(slideFunction = async function* () {}) { + super(); + this.#slideFunction = slideFunction; + this.goto(0); + } + + async #unqueuedGoto(targetState) { + this.innerHTML = ""; + + this.#done = false; + this.#slideIterator = this.#slideFunction(this); + this.#currentState = -1; + + while (this.#currentState !== targetState) { + await this.#advance({ useTransitions: false }); + if (!this.hasNext) return; + } + } + + #queue(callback) { + return (this.#queueChain = this.#queueChain.finally(callback)); + } + + goto(targetState) { + return this.#queue(() => this.#unqueuedGoto(targetState)); + } + + async #advance({ useTransitions }) { + if (this.#done) return; + this.useTransitions = useTransitions; + + this.#currentState++; + const { done } = await this.#slideIterator.next(); + this.#done = done; + } + + previous() { + return this.#queue(() => { + if (this.#currentState === 0) return; + return this.#unqueuedGoto(this.#currentState - 1); + }); + } + + next() { + return this.#queue(() => this.#advance({ useTransitions: true })); + } + + get hasNext() { + return !this.#done; + } + + get hasPrevious() { + return this.#currentState > 0; + } +} + +customElements.define("spec-slide", Slide); + +/** + * @param {HTMLElement} element + * @param {Keyframe[] | PropertyIndexedKeyframes} from + * @param {KeyframeAnimationOptions} options + */ +export function transitionFrom(element, from, options) { + const slide = element.closest("spec-slide"); + if (!slide) throw Error("Transitioning element must be within spec-slide"); + + from = Array.isArray(from) ? from : { ...from, offset: 0 }; + + const anim = element.animate(from, { + ...options, + fill: "backwards", + duration: slide.useTransitions ? options.duration : 0, + delay: slide.useTransitions ? options.delay : 0, + }); + + return anim; +} + +/** + * @param {HTMLElement} element + * @param {Keyframe[] | PropertyIndexedKeyframes} to + * @param {KeyframeAnimationOptions} options + */ +export function transition(element, to, options) { + const slide = element.closest("spec-slide"); + if (!slide) throw Error("Transitioning element must be within spec-slide"); + + const anim = element.animate(to, { + ...options, + fill: "both", + duration: slide.useTransitions ? options.duration : 0, + delay: slide.useTransitions ? options.delay : 0, + }); + + anim.finished.then(() => { + anim.commitStyles(); + anim.cancel(); + }); + + return anim; +} diff --git a/css-view-transitions-2/diagrams/videos/bad-sidebar.mp4 b/css-view-transitions-2/diagrams/videos/bad-sidebar.mp4 new file mode 100644 index 00000000000..15452b0fe92 Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/bad-sidebar.mp4 differ diff --git a/css-view-transitions-2/diagrams/videos/circle.mp4 b/css-view-transitions-2/diagrams/videos/circle.mp4 new file mode 100644 index 00000000000..7de5374709c Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/circle.mp4 differ diff --git a/css-view-transitions-2/diagrams/videos/default.mp4 b/css-view-transitions-2/diagrams/videos/default.mp4 new file mode 100644 index 00000000000..3148cabbf02 Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/default.mp4 differ diff --git a/css-view-transitions-2/diagrams/videos/good-sidebar.mp4 b/css-view-transitions-2/diagrams/videos/good-sidebar.mp4 new file mode 100644 index 00000000000..7c914328bb7 Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/good-sidebar.mp4 differ diff --git a/css-view-transitions-2/diagrams/videos/header.mp4 b/css-view-transitions-2/diagrams/videos/header.mp4 new file mode 100644 index 00000000000..7341f15d181 Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/header.mp4 differ diff --git a/css-view-transitions-2/diagrams/videos/slide.mp4 b/css-view-transitions-2/diagrams/videos/slide.mp4 new file mode 100644 index 00000000000..cf30b022cb3 Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/slide.mp4 differ diff --git a/css-view-transitions-2/diagrams/videos/slow.mp4 b/css-view-transitions-2/diagrams/videos/slow.mp4 new file mode 100644 index 00000000000..6f293a6154b Binary files /dev/null and b/css-view-transitions-2/diagrams/videos/slow.mp4 differ