jakearchibald / navigation-transitions

335 stars 11 forks source link

Synchronising scrolling during a transition #6

Open appsforartists opened 8 years ago

appsforartists commented 8 years ago

In Rendering, you state:

html { transform: translate(0, -20px); } on the top document will leave a 20 pixel gap at the bottom

What happens if you scroll?

jakearchibald commented 8 years ago

I've changed my mind a few times on this, but my feeling is to allow scrolling in both viewports by default, and allow developers to disable it via pointer-events if they want.

appsforartists commented 8 years ago

Doesn't that conflict with clipping the document element to the viewport bounds? Does there need to be a way to specify which edges to clip along?

jakearchibald commented 8 years ago

Maybe I need to work on my wording around this.

My idea is for each documentElement to be the size of the viewport, and each documentElement to have an overflow that isn't visible.

So the contents of each document would be clipped to the viewport, but individually scrollable.

I've updated some of the wording in cf02cc835431bbe098823dc46e5b1d5a7f1a973e, does that help?

surma commented 8 years ago

I’m wondering: If the spec does not prescribe the clipping, width: 100vw and height: 100vh with overflow: scroll or overflow: hidden would allow developers to achieve the effect if desired (and those styles could be added in the event handler). Making the spec strictly more powerful.

appsforartists commented 8 years ago

I like where @surma's head is at.

I do see that you added that each document is scrollable, but that still feels unclear without an example. The document frame and the viewport are normally coincident; it takes some abstract thinking to consider them independently. Since you are breaking this assumption, being more explicit would make it easier to understand. I'd even add a screenshot or a wireframe.

Notice that the scrollbars are attached to the top document's frame; I bumped the translate to 200dp to make more clear:

navtrans

If they are scrollable, there should be a way to map the scroll positions between the two, since most transitions (aside from a simple crossfade or push in) will be mapping geometry from one frame to the other.

You could probably attached a scroll listener to each frame and manually call scrollTo in the other. You'd want to be able for the controlled document to know it's being scrolled, so it can redraw/animate accordingly, but if you aren't careful, you could end up with an infinite loop manually locking the scroll positions to one another.

jakearchibald commented 8 years ago

@surma

I’m wondering: If the spec does not prescribe the clipping, width: 100vw and height: 100vh with overflow: scroll

I'm not against making this a more manual thing, but the above results in horizontal scrollbars in UAs where scrollbars take up space, so it doesn't quite work.

jakearchibald commented 8 years ago

@appsforartists I don't think I understand your post - I can't make out the intent of the screenshot. Is there a URL I can look at?

If they are scrollable, there should be a way to map the scroll positions between the two, since most transitions (aside from a simple crossfade or push in) will be mapping geometry from one frame to the other.

I think it's a bit early to say "most", I'm aiming for a low-level API to avoid making assumptions.

Right now you can use el.getBoundingClientRect() to find an element's position relative to the document's scrolling element.

You could probably attached a scroll listener to each frame and manually call scrollTo in the other. You'd want to be able for the controlled document to know it's being scrolled, so it can redraw/animate accordingly, but if you aren't careful, you could end up with an infinite loop manually locking the scroll positions to one another.

Can you explain a bit more about how the infinite loop could happen. Also, in what situations would updating the scroll position not result in a redraw?

appsforartists commented 8 years ago

It was just a mock in Photoshop, but here's the same thing as iframes. The important bit is that the scrollbar is scoped to just the document frame, which clips the rest of the document. I expect most authors' mental models of a page have the scrollbar pinned the to browser chrome, and the document continuing past the bounds of the viewport - the viewport/window/browser chrome are coincident in this mental model.

Separating the document frame from the viewport may be the right decision to achieve what you're proposing, but since it's a departure from the existing mental model, it's worth being very clear about. Under the current proposal, each document is effectively framed in a 100vw x 100vh container; the scrollbars move with the frame. (This may look weird if only one document has a scrollbar.)

As for my concern about infinite loops, I realize now that it may have been misplaced. I was worried that by having document1 forward its scrolls to document2, and having document2 forward its scrolls to document1, you could cause a loop. Upon further consideration, I realize that scroll would only be dispatched if scrollTop changes values; setting scrollTop = existingScrollValue doesn't trigger scroll, escaping the loop.

Finally, it's hard to objectively say "most" with regards to an API that doesn't exist yet. 😃 Still, shared element transitions extremely common; in fact, they're the core transition style of Material. I expect they'd be an important case to consider for navigation transitions, which motivates my concern about supporting synced scrolling.

appsforartists commented 8 years ago

in what situations would updating the scroll position not result in a redraw?

I meant an application redrawing itself, e.g. a parallax effect driven by the scroll position, not just the browser's default scroll behavior.

jakearchibald commented 8 years ago

@appsforartists thanks for the mock, I think I get it now.

Separating the document frame from the viewport may be the right decision to achieve what you're proposing, but since it's a departure from the existing mental model, it's worth being very clear about.

Agreed. I'm not sure how transitions would work another way.

Under the current proposal, each document is effectively framed in a 100vw x 100vh container

Kinda, although 100vw x 100vh triggers a horizontal scrollbar in UAs where the scrollbar takes up space, and that isn't what we want.

(This may look weird if only one document has a scrollbar.)

Would it? If I'm fading or sliding from one to the other, it seems to do what I'd expect. Which cases would it look broken?

I realize that scroll would only be dispatched if scrollTop changes values; setting scrollTop = existingScrollValue doesn't trigger scroll, escaping the loop.

Hmm, but that wouldn't always be the case. If I'm transitioning from a page with scrollTop == 500 to a page with scrollTop == 0, then one of the other documents is scrolled, I want the other to be scrolled by the relative amount, not the absolute amount. So if the user scrolled the main doc to scrollTop == 550 during transition, I'd want the new doc to be scrollTop == 50.

This may justify an API to syncronise scrolling between two elements. @surma does houdini make this possible in a single document? What about across two same-origin iframes on the same page?

appsforartists commented 8 years ago

My thoughts about weird scrollbars mostly grew out of your translateY(20px) example. In real use cases, it's probably fine.

Regarding scroll syncing, it sounds like you're at risk for a loop again in your example where they have different initial scroll positions. If each document noticed it was off by 50dp and incremented the other, they'd both race to the bottom of the page. To do it correctly, the navigation transition would need to store the initial scroll positions of each document and compare the absolute scroll position on each scroll to those initial positions:

document1.scrollTop = (document2.scrollTop - document2InitialScrollTop) 
                      + document1InitialScrollTop
jakearchibald commented 8 years ago

Yeah, the loop is a problem, but as is the scroll event, since it doesn't fire frequently enough to make two contexts properly in sync. We'll see what @surma says from the houdini side.