From e49d916e920aa6402e4d3cfffa122eebe0f2cb5f Mon Sep 17 00:00:00 2001 From: Vladimir Dementyev Date: Tue, 17 Oct 2023 12:34:35 -0700 Subject: [PATCH] Add view transitions --- docs/architecture.md | 6 ++- docs/assets/styles.css | 39 ++++++++++++++- docs/assets/turbo-view-transitions.js | 69 +++++++++++++++++++++++++++ docs/benchmarks.md | 2 +- docs/getting_started.md | 2 +- docs/index.html | 22 ++++++++- 6 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 docs/assets/turbo-view-transitions.js diff --git a/docs/architecture.md b/docs/architecture.md index 59ff968..2752e82 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,8 +2,10 @@ ## Overview -AnyCable arhictecture -AnyCable arhictecture +
+ AnyCable arhictecture + AnyCable arhictecture +
AnyCable **real-time server** (WS, or WebSocket, since it's a primary transport) is responsible for handling clients, or connections. That includes: diff --git a/docs/assets/styles.css b/docs/assets/styles.css index b44e0a2..bf8cc13 100644 --- a/docs/assets/styles.css +++ b/docs/assets/styles.css @@ -604,4 +604,41 @@ h4:has(+.pro-badge-header):after { .sidebar li.open>p { /* color: var(--theme-color-dark); */ font-weight: 600; -} \ No newline at end of file +} + +@keyframes full-slide-right { + from { transform: translateX(0); } + to { transform: translateX(100%); } +} + +@keyframes full-slide-left { + from { transform: translateX(100%); } +} + +@keyframes fade-in { + from { opacity: 0; } +} + +@keyframes fade-out { + to { opacity: 0; } +} + +::view-transition-old(section) { + animation: 700ms ease-in 0ms both full-slide-right, 500ms ease-out 0ms both fade-out; +} + +::view-transition-new(section) { + animation: 500ms ease-in 0ms both fade-in; +} + +section.content { + view-transition-name: section; +} + +@media (prefers-reduced-motion) { + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation: none !important; + } +} diff --git a/docs/assets/turbo-view-transitions.js b/docs/assets/turbo-view-transitions.js new file mode 100644 index 0000000..02aeb03 --- /dev/null +++ b/docs/assets/turbo-view-transitions.js @@ -0,0 +1,69 @@ +window.TurboViewTransitions = (function () { + function shouldPerformTransition() { + return !!( + "undefined" !== typeof document && + document.head && + document.startViewTransition && + document.head.querySelector('meta[name="view-transition"]') + ); + } + const t = "data-turbo-transition"; + const e = "data-turbo-transition-active"; + const resetTransitions = (t, e) => { + t = t || document; + let { activeAttr: r } = e; + t.querySelectorAll(`[${r}]`).forEach((t) => { + t.style.viewTransitionName = ""; + t.removeAttribute(r); + }); + }; + const activateTransitions = (t, e, r) => { + let { transitionAttr: i, activeAttr: n } = r; + let a = Array.from(t.querySelectorAll(`[${i}]`)).reduce((t, e) => { + let r = e.id || "0"; + let n = e.getAttribute(i) || `__${r}`; + t[n] || + (t[n] = { + ids: {}, + active: false, + discarded: false, + }); + t[n].ids[r] = e; + return t; + }, {}); + Array.from(e.querySelectorAll(`[${i}]`)).forEach((t) => { + let e = t.id || "0"; + let r = t.getAttribute(i) || `__${e}`; + if (a[r] && a[r].ids[e]) { + if (a[r].active) { + a[r].discarded = true; + return; + } + a[r].newEl = t; + a[r].oldEl = a[r].ids[e]; + a[r].active = true; + } + }); + for (let t in a) { + let { newEl: e, oldEl: r, active: i, discarded: o } = a[t]; + if (!o && i) { + r.style.viewTransitionName = t; + e.style.viewTransitionName = t; + r.setAttribute(n, ""); + e.setAttribute(n, ""); + } + } + }; + async function performTransition(r, i, n, a = {}) { + a.activeAttr = a.activeAttr || e; + a.transitionAttr = a.transitionAttr || t; + resetTransitions(r, a); + activateTransitions(r, i, a); + await document.startViewTransition(n).finished.then(() => { + resetTransitions(r, a); + resetTransitions(i, a); + }); + } + + return { shouldPerformTransition, performTransition }; +})(); diff --git a/docs/benchmarks.md b/docs/benchmarks.md index da3300e..ce6e840 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -12,7 +12,7 @@ Broadcasting round-trip time benchmark (based on [Hashrocket's bench](https://gi The results of this benchmark could be seen below. -
+
RTT RTT
diff --git a/docs/getting_started.md b/docs/getting_started.md index 84df9b3..c058d16 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -3,7 +3,7 @@ AnyCable acts like a bridge between _logic-less_ real-time server and _Action Cable-like_ Ruby framework (i.e. framework which support [Action Cable protocol](misc/action_cable_protocol.md)). AnyCable is a multi-transport server supporting WebSockets, [Server-Sent Events](/anycable-go/sse.md) and [long-polling](/anycable-go/long_polling.md). -
+
AnyCable diagram AnyCable diagram
diff --git a/docs/index.html b/docs/index.html index afaae35..ccb2f2d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -19,6 +19,7 @@ + @@ -181,11 +182,29 @@ clink.href = "https://docs.anycable.io" + vm.route.path } }); + }, + function(hook, vm) { + if (!TurboViewTransitions.shouldPerformTransition()) return; + + hook.afterEach(function(html, next) { + let newEl = document.createElement("div"); + newEl.innerHTML = html; + + let oldEl = document.querySelector("section.content") + + TurboViewTransitions.performTransition(oldEl, newEl, function() { + next(newEl.innerHTML); + }, + { + transitionAttr: 'data-view-transition', + activeAttr: 'data-active-view-transition' + }); + }) } ] } - + @@ -194,5 +213,6 @@ +