app-route icon indicating copy to clipboard operation
app-route copied to clipboard

Explain how to use # for scrolling to an id on a sub page

Open kenchris opened this issue 9 years ago • 12 comments

I can have a route like /article/1#subsection

It would be nice with a explanation on how to load that route and still make it scroll to the #subsection like it would had I used actual pages instead of routing.

kenchris avatar Mar 27 '16 17:03 kenchris

Is this purely documentation? I'm not sure we have an implemented strategy for this yet.

/cc @rictic

cdata avatar Mar 29 '16 21:03 cdata

@cdata that's a good point; I'm pretty sure this will require some implementation.

e111077 avatar Mar 29 '16 21:03 e111077

We haven't proved one out enough to have a recommended strategy, but it may be very straightforward.

e.g. If you're not using the hash as the path then you could observe the hash of an <iron-location> and use this.$$('#' + hash).scrollIntoView()

rictic avatar Mar 29 '16 21:03 rictic

I think it may be something that needs to be handled by the app (or user's element, anyway), but we should talk about patterns.

In particular, if you're loading data dynamically, you need to wait until the data is loaded & rendered, then scrollIntoView. And the ID you're scrolling to could be in light DOM, local DOM, or even a child's local DOM. So you may need to know the structure of the page in order to scroll intelligently.

For example, in iron-doc-viewer, we currently use Polymer.dom.flush() to ensure that the dom-repeats have rendered out before we call scrollIntoView. This may not be ideal, but it works.

https://github.com/PolymerElements/iron-doc-viewer/blob/master/iron-doc-viewer.html#L211

arthurevans avatar Mar 31 '16 19:03 arthurevans

It would be good to verify the behavior of using # to target a scroll point in the page under ShadowDOM. In the Polymer Elements case, we certainly have many duplicate IDs across the several shadow boundaries. Does the browser treat these as if they are all at the top level?

cdata avatar Mar 31 '16 20:03 cdata

Yeah, it doesn't work. Or at least it doesn't work consistently. You can try this little jsbin here:

http://output.jsbin.com/piriwa#3

In shady DOM, #inner-4 scrolls to card #4, #generic scrolls to the first card (each card has a div with the id generic).

In native shadow DOM, only the IDs that aren't contained in any shadow root work at all. (#4 works but not #inner-4).

So in most cases I suspect one of the elements is going to have to handle this themselves. unless: a) the IDs are in light DOM, and b) the content is already loaded.

arthurevans avatar Mar 31 '16 21:03 arthurevans

I wonder if this is an opportunity for us to define specialized semantics for how hash links should work in an app that has several layers of ShadowDOM. Perhaps we could prescribe a pathing mechanism that users can leverage to specify jump points through a series of shadow boundaries. For example:

  1. User navigates to /lur#omicron#persei#8

  2. Target selection is made as follows (approximation):

    let path = window.location.hash.slice(1).split('#');
    let element = path.reduce(function(host, id) {
      let target = host.shadowRoot ? host.shadowRoot.querySelector(`#${id}`) : host;
      return target ? target : host;
    }, this);
    
  3. Target element is scrolled into view (as needed / possible).

This is just a random idea. Maybe not ideal because we're inventing semantics that depart from the platform. @rictic WDYT?

cdata avatar Mar 31 '16 23:03 cdata

@cdata That's clever. I like that the reverse function to generate the hash for a node is similarly straightforward.

Advantages:

  • simple and easy to understand
  • polymer-agnostic, cuts with the grain of the platform primitives and not against them
  • doesn't prevent or clash with a custom scrollIntoView system

Disadvantages:

  • can only link to elements that are in an unbroken chain of elements with ids
  • urls could get long
  • adds (a probably small amount) of code

rictic avatar Apr 01 '16 03:04 rictic

👍 to @cdata's proposal

notwaldorf avatar May 06 '16 01:05 notwaldorf

It feels to me like @cdata's proposal cuts against the grain of the primitives. It also feels like it breaks <carbon-route>'s notion of modular and encapsulated routing by making scroll-to-anchor cut though all those carefully-constructed encapsulated elements.

Also, how do you know which element you have to scroll? And how do you know that the page has rendered enough so that you can scroll to the ID? What if it's a big honking page that takes several frames to load and render?

In these cases, I'd argue, you want the lowest level component that understands scrolling to deal with it.

That said, I don't have a better proposal for an overarching solution. I thought of an event-like scheme where individual components can handle and cancel propagation of the change—but pretty sure that falls apart because data binding can't propagate that way.

Also considered each route having a handlesHashWhenActive flag, but again, there's no real notion of precedence or which route "wins". Because everything's propagated though data binding, I don't think there's any notion of which route is "deepest", for example.

arthurevans avatar May 06 '16 15:05 arthurevans

So, um, I guess for the doc site we'll fall back to "application listens to hashchange, decides which component should manage the scroll state for this event" which is basically what @rictic said way back in https://github.com/PolymerElements/carbon-route/issues/56#issuecomment-203104072. :goat:

arthurevans avatar May 06 '16 15:05 arthurevans

It's awfull way, but you can do something like that:

      ready: function() {
        this.shadowRoot.addEventListener("dom-change", function(event){
            var elementToFocus = this.getElementById(window.location.hash.slice(1));
            if (elementToFocus) {
              elementToFocus.scrollIntoView();
            }
        });
      },

evilj0e avatar Dec 17 '16 06:12 evilj0e