turbo-rails (2.0.5)
I’m trying to get Turbo 8 morphing to work with CSS animations. For example, I have the following div box which transitions between blue and red depending on the :toggled
querystring. The page redirects back to the current URL (root) and changes the :toggled
querystring to either true or false, causing the div to change color.
<meta name="turbo-refresh-method" content="morph">
<% color = params[:toggled] == "true" ? "bg-red-200" : "bg-blue-200" %>
<div class='<%= "#{color} w-64 h-64 transition duration-500" %>'></div>
<%= link_to "Toggle", root_path(toggled: params[:toggled] != "true") %>
I’m expecting the div to transition colors, but instead it changes instantly . event.detail.renderMethod
is set to replace
, I would expect this to be morph
?
I can get the desired effect if I hook into the turbo:before-render
event listener and manually call Idiomorph.morph. But shouldn’t turbo 8 do this automatically for you?
<script src="https://unpkg.com/idiomorph@0.3.0"></script>
<script type="text/javascript">
document.addEventListener("turbo:before-render", (event) => {
console.log(event.detail.renderMethod);
event.detail.render = async (prevEl, newEl) => {
await new Promise((resolve) => setTimeout(() => resolve(), 0));
Idiomorph.morph(prevEl, newEl);
};
});
</script>
Am I misunderstanding how Turbo 8 / Morphing is supposed to work?
1 Like
Hey LokPix welcome to the forum, thanks for posting this question what you are looking for is the view-transition
meta tag that was added in Version 8
Here is a link to the docs where they discuss this
If you are interested in seeing the GitHub discussion and PR that added this change you can view it here
hotwired:main
← hotwired:morph-refreshes
opened 09:53AM - 05 Oct 23 UTC
This PR introduces the concept of *page refresh*. A page refresh happens when Tu… rbo renders the current page again. We will offer two new options to control behavior when a page refresh happens:
- The method used to update the page: with a new option to use morphing (Turbo currently replaces the body).
- The scroll strategy: with a new option to keep it (Turbo currently resets scroll to the top-left).
The combination of morphing and scroll-keeping results in smoother updates that keep the screen state. For example, this will keep both horizontal and vertical scroll, the focus, the text selection, CSS transition states, etc.
We will also introduce a new turbo stream action that, when broadcasted, will request a page refresh. This will offer a simplified alternative to fine-grained broadcasted turbo-stream actions.
https://github.com/hotwired/turbo/assets/150107/19aaa245-b569-4388-8ac8-4a259d3ee4b8
https://github.com/hotwired/turbo/assets/150107/0343c15f-2767-4bb6-9276-7e7f6258bb50
[You can learn more about the change here](https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing).
By @afcapel and @jorgemanrubia.
## API
### Page refresh configuration
Turbo will detect page refreshes automatically when it renders the current location again. The user configures how those page refreshes are handled.
#### Configurable refresh method
```html
<meta name="turbo-refresh-method" content="replace|morph">
```
* `replace`: Current behavior: it replaces the body.
* `morph`: It morphs the existing body to become the new body.
The default is `replace`. We may switch to `morph` eventually.
#### Configurable scroll behavior:
```html
<meta name="turbo-refresh-scroll" content="reset|preserve">
```
* `reset`: Current behavior: it resets the scroll to the top-left.
* `preserve`: It keeps the scroll in place.
The default is `reset`. We may switch to `preserve` eventually.
#### Support via helpers
See [companion PR](https://github.com/hotwired/turbo-rails/pull/499) in `turbo-rails`.
```ruby
turbo_refreshes_with scroll method: :morph, scroll: :preserve
```
#### Exclude sections from morphing
There are scenarios where you might want to define sections that you want to ignore while morphing. The most common scenario is a popover or similar UI elements, which state you want to preserve when the page refreshes. We reuse the existing `data-turbo-permanent` attribute to flag such elements.
```html
<div data-turbo-permanent>...</div>
```
### Broadcast changes
#### Turbo stream action to signal a refresh
This introduces a new turbo stream action called `refresh` that will trigger a page refresh:
```html
<turbo-stream action="refresh"></turbo-stream>
```
#### Broadcast changes that require a page refresh
There is a new method in active record's models to broadcast a *page refresh signal* when the record changes. This is implemented internally by broadcasting the turbo stream action referred to above.
```ruby
module Recording::Broadcasting
extend ActiveSupport::Concern
included do
broadcasts_refreshes_to :bucket
end
end
```
In view templates, you would subscribe to these updates using the regular turbo_stream_from helper:
```ruby
turbo_stream_from bucket
```
The system will debounce automatically sequences of updates since it doesn't make sense to trigger multiple signals for multiple updates in a row.
You can learn more about the Rails helpers in the companion PR in `turbo-rails` https://github.com/hotwired/turbo-rails/pull/499
## Implementation notes
### Idiomorph
We started with [`morphdom`](https://github.com/patrick-steele-idem/morphdom) as the DOM-tree morphing library but we eventually changed to [`idiomorph`](https://github.com/bigskysoftware/idiomorph). The reason is that we found `morphdom` to be very picky about `ids` when matching nodes, when `idiomorph` is way more relaxed. In practice, we found that with `morphdom` you had to keep adding `ids` everywhere if you wanted morphing to work, while `idiomorph` worked out of the box with pretty complex HTML structures.
### Mechanism against bounced signals
The system includes a mechanism to ignore refresh actions that originated in a web request that originated in the same browser session. To enable this, these refresh actions can carry a `request-id` attribute to track the request that originated them. Turbo will keep a list of recent requests and, when there is a match, the stream action is discarded.
This is implemented by patching `fetch` to inject a random request id in a custom header `X-Turbo-Request-Id`. We originally intended to use the Rails' standard `X-Request-Id` in the response, but we found a blocking problem: it's not possible with Javascript to read the response headers in a redirect response, you can just read the headers in the target destination response.
The server side where the broadcasts originate is responsible for using that header to flag broadcasted page refreshes with it (==see `turbo-rails` PR with the reference implementation == ).
## Pending
- [ ] Document in guides.
- [ ] Complete code documentation.
1 Like
Hello yunggindigo, thank you for your response.
Using the View Transitions API seems like a lot of extra work (and isn’t supported in all browsers yet) for something so simple. When apply the tricks from this blog post I actually get the result I’m looking for: The future of full-stack Rails: Turbo Morph Drive—Martian Chronicles, Evil Martians’ team blog
I noticed the following sentence at the end:
I must confess: our Turbo Morph Drive is not the same as the upcoming Page Refresh feature of Turbo 8. Turbo is not going to switch to morphing for all page updates, only for those refreshing the current page (via the corresponding Turbo Streams action).
Does this mean that morphing is only applied in turbo streams ? (My main problem is that the rendering method is “replace ” instead of “morph ”).
if this is the case, I can simply continue to use the code in this blog post. This trick (which I thought was the purpose of Turbo 8) covers 90% of my needs.
1 Like