Before-cache-render event

I’m working on an animation library/controller system for handling simple animations that can span pages and especially a default fade out/in animation between page visits. It works great until the cache gets involved and I’m not seeing a good way to prevent caching from immediately rendering a cached copy without turning off caching altogether.

Would it be possible to add a before-cache-render event with detail containing the HTML to be restored like before-render has so that we can have some level of control on what’s being rendered from cache rather than an all or nothing approach?

1 Like

Are you familiar with GitHub - domchristie/turn: :open_book: Animate page transitions in Turbo Drive apps?

Yes it doesn’t seem to do what I need it to do and it uses CSS animations which I’m not a fan of in general so I’m building my own that uses web animations API and puts animation creation at the HTML level so I have animation design and control in my Twig templates and don’t have to turn to CSS or Javascript to do it. I did draw some inspiration from Turn though.

Stimulus Animation Orchestrator

Turn uses the Pausable Rendering feature to control rendering timing.

turbo:before-render and turbo:render are dispatched twice when a preview is rendered. You can detect the initial preview render by checking the data-turbo-preview attribute on the HTML element. So if you set a hasPreview variable with the result of this check, you’ll be able to determine when a render is a preview, or a post-preview one.

Checkout turn/controller.js at b588a8497de8bdc1e99842252870590b82371ae9 · domchristie/turn · GitHub for an example of this

I know they fire twice. What I’m talking about happens BEFORE those events fire. I’m talking explicitly about the brief moment where cache is rendered that happens BETWEEN the popstate event and before-render and nothing you do in before-render or render can prevent the cache rendering appearing for a single frame. And when it does this between a fade out and fade in animation spread across back/forward navigations it causes a visual anomaly. I’ve spent a fair bit of time slowing down execution to observe when the anomaly happens.

Are you able to provide a live demo of this anomaly?

I’m not seeing the cached version display until resumption when pressing back in this example:

let isRestore = false

window.addEventListener('turbo:visit', function (event) {
  if (event.detail.action === 'restore') isRestore = true

window.addEventListener('turbo:before-render', function (event) {
  if (isRestore) {
    setTimeout(function () {
      isRestore = false
    }, 2000)

Not a public example. I think I misremembered the timing though based on the reading of the docs.

back/forward click → popstate → before-render → (cached version rendered) → render → (fresh version rendered) → render

Maybe I didn’t do enough testing but I THINK the newBody in before-render in this scenario is the fresh version and so you’re at the mercy of what the cached version looks like. And the problem arises when you do multiple back/forward clicks and have animations firing across those clicks.

I did try to investigate before-cache to see if I could do any prep there but frankly that event did not seem to fire with any predictability so not sure if that’s a me issue, a bug or expected behavior.

Bottom line is that if you want to do a fade out → fade in of content caching introduces complexity that would easily be solved if there was a before-cache-render event that had the html to be rendered from cache accessible to tweak before showing if desired. Everything else is a workaround IMO.

I should say that my testing is centered around a multi-step form so maybe that behaves differently than normal page visits/clicks.

Do you want to customise the behaviour of navigating back (i.e. popstate)? or the behaviour when rendering a preview before rendering the server response. For what it’s worth, here’s the possible rendering flows:

Clicking link with an empty cache:

  1. Click link
  2. turbo:visit (event.detail.action is advance)
  3. turbo:before-render (event.detail.newBody is from server)
  4. turbo:render new body has rendered

Navigating back with an empty cache:

  1. Click back
  2. turbo:visit (event.detail.action is restore)
  3. turbo:before-render (event.detail.newBody is from server)
  4. turbo:render new body has rendered

Clicking link with a warmed cache:

  1. Click link
  2. turbo:visit (event.detail.action is advance)
  3. turbo:before-render (event.detail.newBody is from cache, html[data-turbo-preview] is present)
  4. turbo:render new body has rendered (from cache)
  5. turbo:before-render (event.detail.newBody is from server)
  6. turbo:render new body has rendered (from server)

Navigating back with warmed cache:

  1. Click back
  2. turbo:visit (event.detail.action is restore)
  3. turbo:before-render (event.detail.newBody is from cache)
  4. turbo:render new body has rendered

I thought turbo:render is fired right after the new body is rendered unless I misunderstand you.

Something else must be going on then if newBody in before-render during a back/forward click contains the cached version because fade out is called during before-render and my code reaches into newBody and sets opacity to 0 on content and yet right after before-render content that shouldn’t be seen is visible for a split second in render before fade in is executed. Maybe there’s a bug in my code. I’ll double check.

I’ll do some more investigating and see if I can capture it using screen capture.

Yes, sorry. I’ve clarified those flows.

If you’ve got a warm cache, on a back/forward (restoration) visit, the before-render newBody will be the cached version.

I wonder if this is to do with an old animation being applied, or something? Animation keyframes take precedence over inline styles I think.

I didn’t do exhaustive testing but I think keyframes are additive at least for position. I spent endless hours trying to figure out why an absolute element with position set in the inline style that was part of a form progress bar was WAY off where I expected (further along the progress line) but canceling the animation fixed it. I want to say while troubleshooting the caching issue I checked for any animations on the element but didn’t find any. As soon as I can get some time to dedicate to testing all this I’ll check.