Skip to content

[cssom-view] Consider adding Element.scrollParent #1522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
oriadam opened this issue Jun 11, 2017 · 16 comments
Open

[cssom-view] Consider adding Element.scrollParent #1522

oriadam opened this issue Jun 11, 2017 · 16 comments

Comments

@oriadam
Copy link

oriadam commented Jun 11, 2017

Suggestion: Element.scrollParent
Returns the closest element which controls the position of current element via a scroll.

This feature is useful in many cases, easy for browsers to implement and hard for plugins such as jQueryUI to get it right.

Link to relevant spec: https://www.quirksmode.org/dom/w3c_cssom.html#t33
Link to current workaround by jQueryUI: https://api.jqueryui.com/scrollParent

Thanks

@upsuper upsuper changed the title [cssom-Element.scrollParent] [cssom-view] Consider adding Element.scrollParent Jun 12, 2017
@upsuper
Copy link
Member

upsuper commented Jun 12, 2017

Could you describe exactly what you really want? And include some usecases for this? Also, would it be useful to handle horizontal and vertical scrolling differently?

I checked the "workaround" in jQuery UI, and it seems to me that it just checks the value of overflow? Is that enough for the usecases? I mean, having overflow: auto doesn't mean the element would have scrollbar, let alone whether it would be scrollable. (Actually having overflow: hidden doesn't even mean the element being not scrollable given that author can implement custom scrollbar with setting overflow to hidden.)

Also note that, there are several issues with the function in jQuery UI as far as I can tell:

  1. It assumes that an element with position: absolute wouldn't be scrolled by any static ancestor, which is false, because when you don't specify top/bottom/left/right, the element would be attached to where it would be if it is static-positioned, and thus would be scrolled by a static ancestor.
  2. In addition to the case above, absolutely-positioned and fixed-positioned elements would also be scrolled by ancestor which "acts as a containing block for absolutely positioned and fixed positioned descendants". See CSS Containment spec for example.
  3. Except the cases above, fixed-positioned element shouldn't have scroll parent otherwise. It should really return null rather than the document.

And actually, this isn't going to be "easy" for browsers to implement. Browsers may end up just having a function like what you would have in JavaScript, which tries to walk up through the element tree and check relevant properties. The only thing which may make browsers be able to handle more elegantly is that they can reuse some checking logic with some existing code. But that really depends on how this would be specified.

@upsuper
Copy link
Member

upsuper commented Jun 12, 2017

It assumes that an element with position: absolute wouldn't be scrolled by any static ancestor, which is false, because when you don't specify top/bottom/left/right, the element would be attached to where it would be if it is static-positioned, and thus would be scrolled by a static ancestor.

I was wrong on this. Even if the element would be positioned at where it would be if it's static, its static ancestors are still not able to scroll it.

@zcorpan
Copy link
Member

zcorpan commented Jun 13, 2017

Do you want this API to return the nearest ancestor that is https://drafts.csswg.org/cssom-view/#potentially-scrollable ? How should it interact with shadow DOM?

@oriadam
Copy link
Author

oriadam commented Jun 13, 2017 via email

@zcorpan
Copy link
Member

zcorpan commented Jun 13, 2017

This is blocked on
#1526
#1527

@markcellus
Copy link

markcellus commented Jun 25, 2017

I posted a reply on the www-dom mailing list, but I'll leave it here also...

This sounds like a cool feature but I'm curious about the use-cases as I've never come across a situation where I've needed this. Are there not any cases where there may be two parents in the hierarchy and you don't want the closest, but the one after it? Also, a more specific name would be better like closestScrollParent or similar. scrollParent is vague since there could be multiple "scrollParent"s technically (i.e. grandparents, great grandparents, etc 😁 )

@oriadam
Copy link
Author

oriadam commented Jun 25, 2017 via email

@markcellus
Copy link

markcellus commented Jun 25, 2017

I checked the "workaround" in jQuery UI, and it seems to me that it just checks the value of overflow? Is that enough for the usecases? I mean, having overflow: auto doesn't mean the element would have scrollbar, let alone whether it would be scrollable. (Actually having overflow: hidden doesn't even mean the element being not scrollable given that author can implement custom scrollbar with setting overflow to hidden.)

@upsuper that's weird. I haven't looked at the jQuery UI code, but that seems very problematic. Wouldn't it be simpler to determine if an element is "scrollable" by just doing

if (parentElement.clientHeight < parentElement.scrollHeight) {
  // element is scrollable parent
}

@jonjohnjohnson
Copy link

jonjohnjohnson commented Apr 5, 2018

This is the type of polyfill I've had to do for el.scrollRootY/el.scrollRootX more than once.

function scrollRoot(el) {
  var roots = {}
  roots.x = undefined;
  roots.y = undefined;

  function checkScroll(el) {
    if (!el.tagName || el === document.documentElement) {
      roots.x = roots.x || document;
      roots.y = roots.y || document;
      return;
    }
    var parent = el.parentElement;
    var regScroll = /(auto|scroll|overlay)/;

    if (parent.scrollHeight > parent.clientHeight && regScroll.test(window.getComputedStyle(parent).overflowY) && !roots.y) {
      roots.y = parent;
    }
    if (parent.scrollWidth > parent.clientWidth && regScroll.test(window.getComputedStyle(parent).overflowX) && !roots.x) {
      roots.x = parent;
    }
    if (!roots.x || !roots.y) {
      checkScroll(parent);
    }
  }
  checkScroll(el);
  return roots;
}

Keep in mind, this doesn't check for a overflow:hidden in either axis, which is programmatically scrollable, if not user scrollable. As well as not checking for position:fixed which moves an element outside it's scrolling context, or back in when a transform on an ancestor then takes the place of the initial containing block. But it could still structurally be inside a nested scrolling element and could then accept scrolling/pointer events for the nested scrolling element? Just saying this stuff gets fun, especially between mouse and touch environments.

And after that, adding scroll listeners, where you still take into account compatibility issues of documentElement and scrollingElement, as well as finding the correct scrollY/scrollX when the target of the event comes from the document, etc... it's hairy. Personally, I hate having to support scrolling anything besides an actual element (I wish html simply had the outermost scrolling mechanism).

PS @mkay581 @oriadam @upsuper If something like this is spec'd, I don't see the need for "grandparents" or "closest", when one could access the scrollRoot of an elements scrollRoot, all the way up if need be.

PPS @upsuper

And actually, this isn't going to be "easy" for browsers to implement.

Don't browsers already have a solid understanding of this for features like sticky and SIV in how scrolling chains from their target? Making this as *simple as exposing a targets "scroll context" to devs?

@oriadam
Copy link
Author

oriadam commented Mar 19, 2019

Use cases I personally needed (and used jQueryUI for them):

  1. Sticky th elements must have a very accurate top/bottom value to work properly. I used scrollParent to calculate the correct values.

  2. Infinite scroll based content widget. On mobile the widget is inserted directly to the body, but on desktop it is inserted inside a scrollable div. I used scrollParent to attach the events to load the list.

  3. Video player that pauses when scrolled out of view. The video player can be included anywhere, so it must use scrollParent to attach the proper events and check if it is in view, and how far down/up it is scrolled away.

  4. Lazy loading ad widgets.

@bramus
Copy link
Contributor

bramus commented Nov 1, 2024

This addition would be very helpful, especially in relation to Scroll-Driven Animations.

Given that document.scrollingElement exists, maybe the name should be scrollingParent or scrollingParentElement for consistency reasons?

@flackr
Copy link
Contributor

flackr commented Jan 14, 2025

This sounds like a cool feature but I'm curious about the use-cases as I've never come across a situation where I've needed this. Are there not any cases where there may be two parents in the hierarchy and you don't want the closest, but the one after it? Also, a more specific name would be better like closestScrollParent or similar. scrollParent is vague since there could be multiple "scrollParent"s technically (i.e. grandparents, great grandparents, etc 😁 )

You could make the same argument about Element.parentElement or Element.offsetParent. I think it's expected that you get the nearest parent.

Perhaps scrollParent and scrollParents

To get a grandparent a developer can walk the parent chain, e.g.
target.scrollingParent.scrollingParent

I think what is scrollable should follow the definition of what is a scroll container which is determine from the overflow properties https://www.w3.org/TR/css-overflow-3/#overflow-control and is the scrolling parent that is referred to by things like position: sticky;.

@flackr flackr added the Agenda+ label Jan 14, 2025
@astearns astearns moved this to FTF agenda items in CSSWG January 2025 meeting Jan 22, 2025
@astearns astearns moved this from FTF agenda items to Regular agenda items in CSSWG January 2025 meeting Jan 22, 2025
@astearns astearns moved this to Regular agenda in CSSWG April 2025 meeting agenda Mar 27, 2025
@tabatkins
Copy link
Member

You could make the same argument about Element.parentElement or Element.offsetParent. I think it's expected that you get the nearest parent.

Yes, "parent" implies closest ancestor, both in common English and in existing usage across the web platform and in specs. "Ancestor" is used when a more general relationship is meant.

To get a grandparent a developer can walk the parent chain,

Yup, walking the chain is simple and what the platform already expects for similar patterns.

I think what is scrollable should follow the definition of what is a scroll container

Strong agree.

@Loirooriol
Copy link
Contributor

Presumably the nearest scroll container in the containing block chain?

I would expect c.scrollParent to be a, not b.

<!DOCTYPE html>
<div id="a" style="width: 150px; height: 150px; position: relative; overflow: scroll">
  <div id="b" style="width: 100px; height: 100px; overflow: scroll">
    <div id="c" style="position: absolute; background: cyan">abspos</div>
    <div style="height: 500px; background: linear-gradient(to bottom, yellow, orange)"></div>
  </div>
  <div style="height: 500px; background: linear-gradient(to bottom, yellow, orange)"></div>
</div>

@flackr
Copy link
Contributor

flackr commented Apr 30, 2025

Presumably the nearest scroll container in the containing block chain?

Yes, agree.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [cssom-view] Consider adding Element.scrollParent, and agreed to the following:

  • RESOLVED: add scrollParent to the spec
The full IRC log of that discussion <JoshT> flackr: We've had a bunch of other APIs to expose the parent and offset parent, but devs have to do a lot of querying to find nearest scroll container
<emilio> q+
<JoshT> ... resolution proposed is to expose it. scrollParent. the nearest container that's a scroll container
<TabAtkins> q+
<iank_> q+
<astearns> ack schenney
<astearns> ack emilio
<JoshT> emilio: Just to confirm, we're talking about the scroll container you scroll relative to
<JoshT> ... for stuff like fixpos, it should return null
<TabAtkins> yes, .scrollingElement I think is right
<JoshT> ... I'm assuming for the root scroller, we return ... there are some complexities there. we could return document.scrollingElement?
<JoshT> ... I guess ?? it sounds good
<astearns> ack TabAtkins
<emilio> s/??/modulo details
<JoshT> TabAtkins: bramus suggested in the thread calling it scrollingParent
<emilio> s/we return .../we return the scrollingElement (in quirks mode that's the body)
<astearns> ack iank_
<JoshT> ... I'm not sure if we have other scroll APIs that suggest we should stick with 'scroll'
<TabAtkins> to be consistent with .scrollingElement
<JoshT> iank_: just to check, flackr, this will walk up the containing block chain?
<kizu> q+
<smfr> q+
<JoshT> ... so if abspos skips, it can skip a scroller and make sure it returns the next one
<astearns> ack kizu
<emilio> q+
<JoshT> kizu: I wanted to mention how we handle elements with ??? auto, scroll, where there an element not overflowing
<JoshT> ... and cases where it clips something, so it is inside content but not scrollable by usual means
<JoshT> ... this is what we currently on our app do. we go up the chain, looking for scrollable containers (compared to offsetHeight with actual height) and go further if it's not currently scrollable
<JoshT> ... so if it will not work as expected, we will need to mention this and propose what it should do in this case
<JoshT> flackr: I disagree we should make it work this way. e.g. stickypos is offset to the nearest scroll container.
<TabAtkins> +1 to flackr's point
<iank_> +1
<JoshT> ... I think it should follow the spec definition of a scroll container
<smfr> q-
<oriol> +1. overflow:hidden can scroll by script, no scrollbar needed
<emilio> +1
<JoshT> ... if a dev wants to find the thing that actually scrolls, they will still need additional logic
<JoshT> astearns: you were saying the spec needs an example of how to do it this way?
<JoshT> kizu: authors will want to be able to do this. there is also a precident for us to have state queries to ask if an element is scrollable right now
<JoshT> ... this is what authors want now. not theoretical scrollability
<JoshT> ... it will be already accessible, but maybe we could make it better
<iank_> It depends a lot on the usecase for what you want to do.
<JoshT> flackr: this at least makes it easier to get what you're asking for
<JoshT> ... but changing the size of your window could cause a different scroll parent
<astearns> ack emilio
<JoshT> emilio: if we wanted to do something like that, it might be worth turning this into a function with options
<JoshT> ... but checking if it's scrollable is a matter of checking if scroll height is less than client height
<JoshT> ... modulo sub pixels get tricky but that's a separate issue.
<JoshT> ... if we need filters and whatnot, we need a different property for 'does it have scroll overflow'
<TabAtkins> *using* this API, it's pretty easy to walk the scrollParent chain to find one *with* a scrollbar. still a strict improvement over today, where if you just walk the *parent* chain you might catch something not in the element's CB chain
<JoshT> ... I do agree that pages tend to ask for it and get it wrong.
<ydaniv> +1 to not skip hidden etc. Otherwise will behave different from ViewTimelines as well
<JoshT> ... it is trickier to tell if something is truly scrollable.
<TabAtkins> and enough things *will* want the spec's notion of "scroll container" that we should expose that; can't really get scroll container from an API that returns "nearest thing with a scrollbar" as it might overshoot
<JoshT> ... but this API should just tell you if it's a scroll container
<emilio> q+
<JoshT> kizu: sounds good. we will need to think about how to fix it but not in this issue
<astearns> ack fantasai
<JoshT> fantasai: I agree with flackr and emilio
<JoshT> ... if we want to make it easier for authors to filter, we could do it based on container queries and filter by x or y
<flackr> +1 to emilio's suggestion of an IDL attribute to easily check if something is currently scrollable
<JoshT> ... that is a separate issue. this proposal makes sense
<astearns> ack emilio
<JoshT> emilio: for the root scroller case, should we return an element at all?
<JoshT> astearns: I'm not hearing objections
<TabAtkins> happy to leave name decision to flackr
<iank_> "scroll" matches "scrollWidth" etc.
<JoshT> ... does anyone have a pref for scrollParent or scrollingParent?
<kizu> +1 to `scrollParent`
<JoshT> astearns: proposed to add scrollParent to the spec
<JoshT> RESOLVED: add scrollParent to the spec
<JoshT> fantasai: who will make edits in which spec?
<JoshT> flackr: I can make edits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Regular agenda
Status: Regular agenda items
Development

No branches or pull requests