hotwired / turbo

The speed of a single-page web application without having to write any JavaScript
https://turbo.hotwired.dev
MIT License
6.72k stars 429 forks source link

Option to maintain current scroll position? #37

Open jaredcwhite opened 3 years ago

jaredcwhite commented 3 years ago

On a regular Drive page update, it's scrolling up to the top of the page. Normally that would be desired, but on some Drive links I want to maintain the current scroll position. I tried looking for a data-turbo-preserve-scroll option or something like that but couldn't find anything. I could use Frames in this scenario instead, but then I'd lose the URL history/back button/etc.

seanpdoyle commented 3 years ago

Would a pair of document- or window-level suit your needs?

let scrollTop = 0

addEventListener("turbo:click", ({ target }) => {
  if (target.hasAttribute("data-turbo-preserve-scroll")) {
    scrollTop = document.scrollingElement.scrollTop
  }
})

addEventListener("turbo:load", () => {
  if (scrollTop) {
    document.scrollingElement.scrollTo(0, scrollTop)
  }

  scrollTop = 0
})
jaredcwhite commented 3 years ago

@seanpdoyle That works in a pinch! 👏

jmarsh24 commented 3 years ago

Could you elaborate on how to implement this? I'm also looking for a similar solution, but I'm not exactly sure how to make it work. 🤔

bvsatyaram commented 3 years ago

We have a slightly different requirement. Instead of one HTML element, we have several elements for which we wanted to retain the scroll position. It is a Kanban board with multiple columns. The following worked for us:

let containerScrollTops = {};

addEventListener("turbo:click", () => {
  document
    .querySelectorAll("[data-turbo-preserve-scroll-container]")
    .forEach((ele) => {
      containerScrollTops[ele.dataset.turboPreserveScrollContainer] =
        ele.scrollTop;
    });
});

addEventListener("turbo:load", () => {
  document
    .querySelectorAll("[data-turbo-preserve-scroll-container]")
    .forEach((ele) => {
      const containerScrollTop =
        containerScrollTops[ele.dataset.turboPreserveScrollContainer];
      if (containerScrollTop) ele.scrollTo(0, containerScrollTop);
    });

  containerScrollTops = {};
});

We are essentially setting a unique value for data-turbo-preserve-scroll-container for each such columns for which we would like to retain the scroll position.

MichalSznajder commented 3 years ago

Is there a solution to keep scroll position during form submission?

tobyzerner commented 3 years ago

This solution doesn't seem to be working anymore. It seems as though turbo is scrolling back to the top after the turbo:load event is fired. I can get it to work if I call scrollTo inside a setTimeout call, but this causes the page to flicker as it jumps to the top and then back down.

daniel-nelson commented 2 years ago

Our solution had been to set scroll in turbo:render. This also stopped working, but we solved it today by setting Turbo.navigator.currentVisit.scrolled = true on turbo:before-render. Here's the full Stimulus controller (this is for navigating to a pane—which is a stand alone page, not Turboframe—that animates over the page one is navigating from):

import { Controller } from '@hotwired/stimulus'
import { Turbo } from '@hotwired/turbo-rails'

export default class extends Controller {
  connect() {
    if (window.previousPageWasAPaneLaunchPage) {
      document.removeEventListener('turbo:before-render', disableTurboScroll)
      document.removeEventListener('turbo:render', pageRendered)
    }

    document.addEventListener('turbo:visit', fetchRequested)
    document.addEventListener('turbo:before-render', disableTurboScroll)
    document.addEventListener('turbo:render', pageRendered)
  }

  disconnect() {
    document.removeEventListener('turbo:visit', fetchRequested)
  }
}

function fetchRequested() {
  window.previousPageWasAPaneLaunchPage = true
  window.paneStartPageScrollY = window.scrollY
}

function disableTurboScroll() {
    if (!window.previousPageWasAPaneLaunchPage) Turbo.navigator.currentVisit.scrolled = true
}

function pageRendered() {
  if (window.previousPageWasAPaneLaunchPage) window.paneStartPageScrollY = null
  if (window.paneStartPageScrollY)
    window.scrollTo(0, window.paneStartPageScrollY)
}

Here's the corresponding pane controller, which is added to the body of the pane layout:

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
  connect() {
    window.previousPageWasAPaneLaunchPage = false
  }
}
seanpdoyle commented 2 years ago

Our solution had been to set scroll in turbo:render. This also stopped working, ...

@daniel-nelson writing to Turbo.navigator.currentVisit.scrolled = true reaches far into internal, private interfaces. Ideally, that wouldn't be necessary.

I have a hunch that the changes made in https://github.com/hotwired/turbo/commit/539b249d82f44f5a4fd6bd245fe1e45718b31508#diff-78d8451f964182fd51330bac500ba6e71234f81aad9e8a57e682e723d0f517b3R105-R106 are the cause of the issues in turbo:render.

Could you try and boil down your use case to the original work-around's HTML and JS? I'd like to use that as a test case to revert whatever regressions were introduced in https://github.com/hotwired/turbo/commit/539b249d82f44f5a4fd6bd245fe1e45718b31508.

seanpdoyle commented 2 years ago

This solution doesn't seem to be working anymore. It seems as though turbo is scrolling back to the top after the turbo:load event is fired. I can get it to work if I call scrollTo inside a setTimeout call, but this causes the page to flicker as it jumps to the top and then back down.

@tobyzerner does that behavior occur when using the changes introduced in https://github.com/hotwired/turbo/pull/476? That PR attempts to better synchronize the after-load scrolling to occur within the next available animation frame. Your comment is from October 18, long before https://github.com/hotwired/turbo/commit/539b249d82f44f5a4fd6bd245fe1e45718b31508, so I'm curious if 476 would resolve it.

can3p commented 2 years ago

@daniel-nelson thanks for posting you comment! I was trying to solve exactly the same problem today while trying to migrate my project from turbolinks to turbo

@seanpdoyle In my app the majority of interactions are centered around action buttons. Since I didn't want to do granular updates to the interface I decided that I would just code them as very simple stimulus controllers that would make an ajax call and then reload page (or optionally redirect to a different one) on success.

Keeping the scroll position was challenge with the Turbolinks as well, I've found one of the solutions similar to what @daniel-nelson did, but in terms of turbolinks events.

This is how my reload page function looks now when adopted to turbo code:

export function reloadPage() {
  var scrollPosition;
  var focusId;

  $(document).one("turbo:visit", function () {
    scrollPosition = [window.scrollX, window.scrollY];
    console.log("saving scroll position", scrollPosition)
    focusId = document.activeElement.id;
  });

  $(document).one("turbo:before-render", function () {
    Turbo.navigator.currentVisit.scrolled = true
  });

  $(document).one("turbo:render", function () {
    if (scrollPosition) {
      window.scrollTo.apply(window, scrollPosition);
      scrollPosition = null;
    }
    if (focusId) {
      document.getElementById(focusId).focus();
      focusId = null;
    }
  });

  Turbo.visit(location.toString(), { action: "replace" });
}

and that's the gist of my action controller:

export default class extends Controller {
  run() {
    let button = this.element
    let request = this.element.dataset
    let { name } = request

    button.disabled = true

    performRequest(name, request)
      .then(json => {
        if (json.redirect_to) {
          Turbol.visit(json.redirect_to)
        } else {
          reloadPage()
        }
      })
      .catch((err) => {
        shoutError(err)
        button.disabled = false
      })
  }
}

I think Turbo might be missing one of the possible actions for the visit method - reload that would reload the page while keeping the scroll position and that's the whole reason of the hack using the events.

What could be an optimal way to solve this on your opinion? I would be happy to do a pr

seanpdoyle commented 2 years ago

I think Turbo might be missing one of the possible actions for the visit method - reload that would reload the page while keeping the scroll position and that's the whole reason of the hack using the events.

What could be an optimal way to solve this on your opinion? I would be happy to do a pr

@can3p Turbo uses action: "restore" internally. The Handbook explicitly discourages public usage:

Restoration visits have an action of restore and Turbo Drive reserves them for internal use. You should not attempt to annotate links or invoke Turbo.visit with an action of restore.

If your Stimulus controller invokes Turbo.visit() with { action: "restore" }, does that achieve the outcome you're after?

can3p commented 2 years ago

No, restore didn't help with my case unfortunately. Just tested it, when I use restore instead of replace in my reloadPage function then:

tobyzerner commented 2 years ago

@seanpdoyle Unfortunately #476 does not fix the workaround. The problem is that Turbo is setting the scroll position after turbo:load is called, which that PR does not change.

daniel-nelson commented 2 years ago

Could you try and boil down your use case to the original work-around's HTML and JS? I'd like to use that as a test case to revert whatever regressions were introduced in 539b249.

@seanpdoyle Here is a minimal Rails project reproducing the issue: https://github.com/daniel-nelson/turbo_scroll_pane. The latest commit reverts the Turbo.navigator.currentVisit.scrolled = true hack, resulting in the poor UX. If you want to see how it is supposed to work, check out the commit before that (the second commit in the repo).

daniel-nelson commented 2 years ago

@seanpdoyle Here is a minimal Rails project reproducing the issue: https://github.com/daniel-nelson/turbo_scroll_pane.

@seanpdoyle I should have specified in the project readme that you need to have a narrow viewport to see this properly. On desktop, we just show the new pane. When building this minimal app, I had a weird scaling issue when using the actual Chrome devices simulator. But it worked fine just dragging the window to a mobile width. I'll go update the readme now, but wanted to mention it here in case you already cloned.

AurelianSpodarec commented 2 years ago

Is there some native data-turbo-preserve-scroll or not yet? I have some tabs and sidebar where the scroll position should be preserved. Other links should jump to the top.

Is there any easy way to do this?

And if so, how would you target just specific elements that you do not want to make the page scroll top?

tsangiotis commented 2 years ago

For now I use a simple stimulus controller which is not the most efficient way:

import { Controller } from "stimulus";

export default class extends Controller {
    initialize() {
        this.scrollTop = 0; 
    }

    connect() {
        if (this.scrollTop) {
            this.element.scrollTo(0, this.scrollTop);
            this.scrollTop = 0;
          }
    }

    position() {
        this.scrollTop = this.element.scrollTop;
    }
}
 <nav id="sidebar-nav" data-controller="tree-scroll" data-action="scroll->tree-scroll#position" data-turbo-permanent >
...
</nav>

I believe that most should happen on disconnect but in my case the this.element.scrollTop is always 0 on disconnect.

dillonhafer commented 2 years ago

I created a small utility to freeze the scrolling for the next visit/render:

const freeze = () => {
  // @ts-ignore
  window.Turbo.navigator.currentVisit.scrolled = true;
  document.removeEventListener("turbo:render", freeze);
};

export const freezeScrollOnNextRender = () => {
  document.addEventListener("turbo:render", freeze);
};

Then I simply use it where needed:

import { Controller } from "@hotwired/stimulus";
import { freezeScrollOnNextRender } from "utils";

export default class extends Controller {
  static targets = ["saveDraft"];
  declare saveDraftTarget: HTMLButtonElement;

  saveDraft() {
    freezeScrollOnNextRender();
    this.saveDraftTarget.click();
  }
}

I could make it so every form submission kept the scroll by just have a submit action in my stimulus controller:

import { Controller } from "@hotwired/stimulus";
import { freezeScrollOnNextRender } from "utils";

export default class extends Controller {
  static targets = ["form"];
  declare formTarget: HTMLFormElement;

  submitForm(event: Event) {
    event.preventDefault();
    freezeScrollOnNextRender();
    this.formTarget.requestSubmit();
  }
}
miguelpeniche commented 2 years ago

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

miguelpeniche commented 2 years ago

BTW I get [ERROR] Expected ";" but found "formTarget"

dillonhafer commented 2 years ago

It is quite normal behavior to scroll the page to the top after a form submission, this is what browsers do. Turbo is merely following this standard behavior.

miguelpeniche commented 2 years ago

Sorry @dillonhafer I said it bad. It happens when the form have errors and not when the form is submitted.

nxmndr commented 2 years ago

It really needs to have something like InertiaJS https://inertiajs.com/links#preserve-scroll

nxmndr commented 2 years ago

Finally made it work using <turbo-frame id="root-path-to-post-action">

stasou commented 1 year ago

Finally made it work using <turbo-frame id="root-path-to-post-action">

Could you elaborate a bit further, please? What exactly did you do?

nxmndr commented 1 year ago

@stasou

https://turbo.hotwired.dev/reference/frames

I thought the content of id had to start with the form action URL, but as it turns out, any non-empty id is fine. Here's a Laravel code I wrote for a menu that reorders pages. Wrapping it in <turbo-frame> allows the reordering without reloading. data-turbo="false" is necessary for links.

<turbo-frame id="abc">
        @foreach ($docpages as $docpage)
            <div>
                <span>{{ $docpage->title }}</span>
                {{-- weird bug --}}
                @if ($loop->index === 0) <form></form> @endif

                <form action="{{ route('docpage.up', $docpage) }}" method="post">
                    @csrf
                    <input type="submit" value="︿" />
                </form>
                <form action="{{ route('docpage.down', $docpage) }}" method="post">
                    @csrf
                    <input type="submit" value="﹀" />
                </form>
                <a href="{{ route('docpage.edit', $docpage) }}" data-turbo="false">Éditer</a>
            </div>
        @endforeach
</turbo-frame>
mweitzel commented 1 year ago

This issue has been open since the week 1 of the project being open sourced, will it ever be addressed?

stasou commented 1 year ago

@mweitzel I never found a solution involving turbo directives or otherwise relevant javascript.

I did, however, achieve desired functionality just by adding a preventDefault() directive to the click event.

I should mention, though, that my clicks trigger async actions, not just get routes, so I would test and see if this is enough.

vmiguellima commented 1 year ago

@jaredcwhite You can achieve this using:


Turbo.scrollTop = 0;

Turbo.shouldPreserveScroll = false;

let shouldPreserveScroll = 0;

document.addEventListener("turbo:click", function(event) {
    if (event.target.hasAttribute('data-turbo-preserve-scroll')) {
        shouldPreserveScroll = true;
    } else {
        shouldPreserveScroll = false;
    }
});

document.addEventListener("turbo:visit", function(event) {
    if (shouldPreserveScroll) {
        Turbo.scrollTop = document.documentElement.scrollTop;
    } else {
        Turbo.scrollTop = 0;
    }
});

addEventListener("turbo:visit", () => {
    Turbo.navigator.currentVisit.scrolled = true;
    document.documentElement.scrollTop = Turbo.scrollTop;
});
james-em commented 1 year ago

For me I wanted to keep my left sidebar scroll intact when changing page. Here is what I have done

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="preserve-scroll"
export default class extends Controller {
  connect() {
    window.preserveScroll ||= {};

    this.element.addEventListener('scroll', this.onElementScroll.bind(this));

    this.restoreScroll();
  }

  disconnect() {
    super.disconnect();

    this.element.removeEventListener('scroll', this.onElementScroll);
  }

  onElementScroll() {
    window.preserveScroll[this.element.id] = this.element.scrollTop;
  }

  restoreScroll() {
    if (window.preserveScroll[this.element.id]) {
      this.element.scrollTo(0, window.preserveScroll[this.element.id]);
    }
  }
}

Only requirement is to have a unique ID on my sidebar.

bennadel commented 1 year ago

Before I added Hotwire, I had a form whose action attribute included a fragment. This fragment got the browser to scroll down to the form after submission:

<form id="my-form" action="/path/to/form.htm#my-form">

This worked great. But, when I added Hotwire, and Turbo Drive takes over the form submission, it ignores the #my-form portion of the URL when managing the application visits. Ideally, Turbo Drive would just do this; and, I think it would solve many of the problems discussed above.

KonnorRogers commented 1 year ago

This was my solution:

import * as Turbo from '@hotwired/turbo'

if (!window.scrollPositions) {
  window.scrollPositions = {};
}

function preserveScroll () {
  document.querySelectorAll("[data-preserve-scroll").forEach((element) => {
    scrollPositions[element.id] = element.scrollTop;
  })
}

function restoreScroll (event) {
  document.querySelectorAll("[data-preserve-scroll").forEach((element) => {
    element.scrollTop = scrollPositions[element.id];
  }) 

  if (!event.detail.newBody) return
  // event.detail.newBody is the body element to be swapped in.
  // https://turbo.hotwired.dev/reference/events
  event.detail.newBody.querySelectorAll("[data-preserve-scroll").forEach((element) => {
    element.scrollTop = scrollPositions[element.id];
  })
}

window.addEventListener("turbo:before-cache", preserveScroll)
window.addEventListener("turbo:before-render", restoreScroll)
window.addEventListener("turbo:render", restoreScroll)
<nav id="sidebar" data-preserve-scroll>
  <!-- stuff -->
</nav>
paulodeon commented 1 year ago

If you're submitting a form and want to maintain its scroll position, simply wrap it in a <turbo-frame id="form">

MichaelBrauner commented 1 year ago
const TurboHelper = class {
  constructor() {
    if (!window.scrollPositions) {
      window.scrollPositions = {};
    }

    document.addEventListener("turbo:click", (event) => {
      if (this.isInsidePreserveScrollElement(event.target)) {
        window.scrollPositions['page'] = window.scrollY;
      }
    });

    document.addEventListener("turbo:before-render", () => {
      if (window.scrollPositions['page']) {
        requestAnimationFrame(() => {
          window.scrollTo(0, window.scrollPositions['page']);
        });
      }
    });
  }

  isInsidePreserveScrollElement(target)  {
    const preserveScrollElements = document.querySelectorAll('[data-preserve-scroll]');
    for (let element of preserveScrollElements) {
      if (element.contains(target)) {
        return true;
      }
    }
    return false;
  }
}
// entry point file
import './../bootstrap';
import './../turbo-helper';
    <nav role="navigation" class="flex items-center justify-between" id="pager" data-preserve-scroll>
        <div class="flex-1 flex items-center justify-between">
            <div>
                <span class="relative z-0 inline-flex">
                   your links ....
                </span>
            </div>
        </div>
    </nav>
andreyvit commented 10 months ago

I don't see any movement on this issue, so want to explicitly state here: while there's a bunch of hacky workarounds for this issue, it's a crazy solution to fight Turbo and try to undo its scrolling. Like any hack, it's brittle, it's not a proper solution, and it might result in a bad UX in a pinch.

We need an option to simply disable the scrolling behavior for a particular request.

marienfressinaud commented 10 months ago

@andreyvit We'll get it in the upcoming Turbo 8, and with a way more powerful system (but apparently not too complex). See Turbo with Morphing: https://dev.37signals.com/a-happier-happy-path-in-turbo-with-morphing/

elik-ru commented 10 months ago

This works for me perfectly:

var scrollPositions = {};

document.addEventListener("turbo:before-render", function(){
    document.querySelectorAll("[data-turbo-keep-scroll]").forEach(function(element){
        scrollPositions[element.id] = element.scrollTop;
    });
});

document.addEventListener("turbo:render", function(){
    document.querySelectorAll("[data-turbo-keep-scroll]").forEach(function(element){
        element.scrollTop = scrollPositions[element.id];
    });
});

I have a long sidebar with nested elements and it can have a scroll. Normally when you choose something in the bottom and click on it, sidebar jumps to the top, and you don't see what you just have clicked. With this snippet it is kept in place, no flickering, works like a charm.

jon-sully commented 10 months ago

Worth noting regarding Turbo 8 and morphing — this new system does allow you to retain scroll position, but only for Page Refresh events, meaning only for when you're redirected back to the same page you're already observing. I don't believe Turbo 8 will allow you to retain scroll position when simply navigating from one page to another in a traditional "click a link, click another link" sense. Even retaining the scroll position in Turbo 8 is just a nice side-effect — the system is designed around morphing after a form POST/PATCH.

So, since this issue seems to be written more generically, I don't think Turbo 8 will / should close it.

creativetags commented 7 months ago

Just in case it helps someone else, this issue for me was caused by duplicate IDs on text fields.

james-em commented 7 months ago

urbo 8 and morphing — this new system does allow you to retain scroll position, but only for Page Refresh events, meaning only for when you're redirected back to the same page you're already observing. I don't believe Turbo 8 will allow you to retain scroll position when simply navigating from one page to another in a traditional "click a link, click another link" sense. Even retaining the scroll position in Turbo 8 is just a nice side-effect — the system is designed around morphing after a form POST/PATCH.

So, since this issue seems to be written more generically, I don't think Turbo 8 will / should close it.

Yup I flagged this bug last week: https://github.com/hotwired/turbo-rails/issues/575#issuecomment-2000390645

Maybe I should open an issue

Edit: https://github.com/hotwired/turbo/issues/1226

levkk commented 6 months ago

Probably not the answer anyone is really looking for this issue specifically, but we solved this by using Turbo Frames instead of reloading the entire page. You can put almost the entire page into a single frame and send it as part of the response. Turbo will replace only that frame and maintain scroll position.

thatandyrose commented 2 months ago

Probably not the answer anyone is really looking for this issue specifically, but we solved this by using Turbo Frames instead of reloading the entire page. You can put almost the entire page into a single frame and send it as part of the response. Turbo will replace only that frame and maintain scroll position.

Was literally going to write the same thing. In our case we needed the entire page to reload without the scroll issue.

We solved this by splitting the page into it's logical two parts where all of the page is in one of either frames. Then in the controller we use turbo streams to simply re-render both frames. This achieves the same reload we needed before but without any scrolling.

The ONLY downside is having to put turbo specific code in the controller which sucks a bit, but in the end, if we're aiming for SPA like UX then it's probably good to be explicit about it 🤷

kuba-orlik commented 2 months ago

I'm using a modified version of @vmiguellima's workaround. My fix makes it so scroll is also preserved when pressing "back" button after regular navigation.

(function enableScrollPreservation() {
    let scrollTop = 0;

    let shouldPreserveScroll = false;

    document.addEventListener("turbo:click", function (event) {
        if ((event.target as HTMLDivElement).hasAttribute("data-turbo-preserve-scroll")) {
            shouldPreserveScroll = true;
        } else {
            shouldPreserveScroll = false;
        }
    });

    document.addEventListener("turbo:visit", function () {
        if (shouldPreserveScroll) {
            scrollTop = document.documentElement.scrollTop;
        } else {
            scrollTop = 0;
        }
    });

    addEventListener("turbo:visit", () => {
        if (shouldPreserveScroll) {
            (window as any).Turbo.navigator.currentVisit.scrolled = true;
            document.documentElement.scrollTop = scrollTop;
        }
        shouldPreserveScroll = false;
    });
})();

I really wish this was built-in into Turbo :pray: but at least we have the workarkound ;)

fritzmg commented 2 months ago

In our case we do not want to maintain the original scroll position - but scroll to a specific HTML element once the page is rendered. For example: you submit a form and then the server responds with 422 as one form field did not validate. We then have a Stimulus controller that would automatically scroll the non-validating form field into view automatically.

However, this doesn't work as the Stimulus controller (using …targetConnected()) will execute the scrolling first - and is then overridden by Turbo's scroll to top.

I feel like there should be a way within Turbo to be able to handle such situations - I mean scrolling to an erronenous form field after submission shouldn't be that uncommon? Yes, you can wrap the <form> in a <turbo-frame>, but I feel like this should be possible regardless of Turbo Frames.

andreyvit commented 2 months ago

@fritzmg Seconding the request to handle scrolling as part of Turbo navigation (maybe if the response redirect has an anchor, we could scroll to that anchor?) I guess the current canonical solution is to respond with <turbo-stream> actions and defining a custom scroll action.

lpknv commented 1 week ago

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

Me neither and I absolutely do not understand why it is the way it is. It is very bad UX!

MarcusRiemer commented 1 week ago

Just to make sure: You are aware of this rather buried possibility?

If you're submitting a form and want to maintain its scroll position, simply wrap it in a <turbo-frame id="form">

dillonhafer commented 1 week ago

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

Me neither and I absolutely do not understand why it is the way it is. It is very bad UX!

I'm not arguing that it is a good or preferred idea, I'm merely stating that if you use a browser without javascript, and you submit a form, then the next page will be at the top when the browser renders the next document. I'm not arguing for it, I'm just saying browser have behaved that way since the 1990s.

lpknv commented 1 week ago

@dillonhafer can you elaborate more? I am having this issue. I don't get why when you submit a form it scrolls you all the way up to the top. In a landing page with a form at the end is just a ridiculous behavior. 😒

Me neither and I absolutely do not understand why it is the way it is. It is very bad UX!

I'm not arguing that it is a good or preferred idea, I'm merely stating that if you use a browser without javascript, and you submit a form, then the next page will be at the top when the browser renders the next document. I'm not arguing for it, I'm just saying browser have behaved that way since the 1990s.

Yes, I am aware of that. It just shows again how developers have to fight the web standards in some way or another.

lpknv commented 1 week ago

If you're submitting a form and want to maintain its scroll position, simply wrap it in a <turbo-frame id="form">

Tried it and it leads to an error The response (200) did not contain the expected <turbo-frame id="contactform"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload., although the form submit is doing fine. (I am new to the whole rails world btw.)