Open argyleink opened 4 years ago
This is related to https://github.com/w3c/csswg-drafts/issues/4329.
Basically, whatever solutions we come up with in the vertical direction will also be applied in the horizontal direction.
We discussed these issues today at the CSSWG face-to-face in A Coruña. Scrollbars are similar to keyboards. And yes, we were settling on env() variables for the dynamically changing measurements (and not another option). There are pros & cons, usecases & objections.
The scrollbar size can be controlled by the user, so probably should be native-scrollbar-inline-size
or something like that.
I presume this is not meant to be element-dependent, right? env(native-scrollbar-inline-size)
would be the same regardless of the element having scrollbars or not... right?
This was also discussed today at the CSS F2F as part of https://github.com/w3c/csswg-drafts/issues/4674
Something like env(scrollbar-width) that is the initial length or length computed from the auto value of the scrollbar-width property?
https://github.com/w3c/csswg-drafts/issues/2630#issuecomment-387909043
With [css-overflow-4] scrollbar-gutter
making it possible to prevent unwanted Layout Shifts caused by scrollbars, what does the future of env(scrollbar-inline-size)
look like?
I mean, getting the size of the scrollbar is handy, but as we can achieve the same behavior with scrollbar-gutter: stable both-edges;
, do we still need env(scrollbar-inline-size)
?
/* Keep content centered by manually offsetting the padding-inline-start */
.keep-content-centered-using-envvar {
padding-inline-start: env(scrollbar-inline-size);
}
/* Keep content centered using scrollbar-gutter */
.keep-content-centered-using-scrollbar-gutter {
scrollbar-gutter: stable both-edges;
}
Or are there any other use-cases for env(scrollbar-inline-size)
besides keeping things visually centered?
Also note that manually taking env(scrollbar-inline-size)
into account can reproduce funky results when combined with scrollbar-gutter: stable both-edges;
:
/* This looks is a bad combination to do, as you'll end up with a double gap at the edge opposing the scrollbar */
.bad-combination {
padding-inline-start: env(scrollbar-inline-size);
scrollbar-gutter: stable both-edges;
}
I still have a use-case and that is when making elements horizontally break out from their parent elements, extending them back to the full available viewable area + combined with programmatic scrolling.
This is the pretty much standardized™ example code to make child elements extend back to full width, regardless of their parent's width (imagine a hero image in an article):
:root {
overflow-x: hidden;
overflow-y: scroll; /* could also be done with scroll-gutter */
}
.outer {
width: 100%;
max-width: 40rem;
margin: 0 auto;
}
.inner {
width: 100vw;
margin-inline: calc((100vw - 100%) / -2);
}
To my knowledge, this is still the way to go to do these types of things when you work with components and you don't know in what type of element the .inner
element will end up living (the alternative approaches using CSS Grid don't work well with componentization or rely on subgrid).
But .inner
element will now extend below the scrollbar of the root, creating horizontal overflow. This can again be countered by setting overflow-x: hidden
on that root. Problem is, you can still scroll programmatically. This gets apparent, once you start scrolling things like sliders or galleries via scrollIntoView()
from left to right and then back again.
Here is a demo of that behavior: https://codepen.io/Schepp/full/jOYdQbv Note how the page jumps horizontally every time you click on "< start" and "end >" in the slider controls (except for the very first time)
Trying to contain scrolling with overscroll-behavior: contain;
doens't work either, as this works from inside out, but scrollIntoView()
approaches the task from outside in.
Having env(scrollbar-inline-size)
available would enable us to fix that unwanted (programmatic) scrolling by changing our code as follows:
.inner {
--viewable-area: calc(100vw - env(scrollbar-inline-size));
width: var(--viewable-area, 100vw);
margin-inline: calc((var(--viewable-area, 100vw) - 100%) / -2);
}
PS: In theory, one could also trigger page jumping by focusing something close to the right border of the page, and then again something close to the opposing border.
Okay, so the so far unknown to me overflow: clip
seems to be the solution to all my problems 🥳
If you apply this to e.g. <body>
, instead of overflow: hidden
, it stops programmatic horizontal scrolling of it, when children extend beyond its width (being 100vw - scrollbars):
body {
width: 100%;
overflow: clip;
}
Hello, another use case is to to have a position: fixed div stuck on the right of the screen cleanly. Many people use JS workarounds right now for many use cases. See for example https://stackoverflow.com/questions/28360978/css-how-to-get-browser-scrollbar-width-for-hover-overflow-auto-nice-margi The best option in my opinion would be to have parent-scrollbar-width variable accessible to calc.
@Schepp Re --viewable-area: calc(100vw - env(scrollbar-inline-size))
, if the page does not have a classic scrollbar, would you want the .inner
element to extend all the way to the right edge of the browser?
If yes, then env(scrollbar-inline-size)
would not be the solution, since it would be a fixed length, regardless of whether the page actually has a classic scrollbar or not. So --viewable-area
would be a fixed length as well, and on a page without a classic scrollbar, that length would be smaller than the viewport width.
@simevidas yes, I would want the element to then take up the full space up to the right edge of the browser.
I think what I mean with scrollbar size is how much in-flow space it takes up. A lot of scrollbars just overlay the underlying content, only appearing when triggered, and would then result in the env
having the value 0
.
Here is the code I currently have to use to find out myself and fix my problems via custom property --scrollbar-inline-size
:
// the following gets all executed right after opening <body> tag
const elem = document.createElement('div');
const referenceElem = document.createElement('div');
const measureElem = document.createElement('div');
Object.assign(elem.style, {
position: 'absolute',
width: '100%',
visibility: 'hidden',
});
Object.assign(referenceElem.style, {
overflow: 'scroll',
width: '50px',
});
const determineWidth = () => {
const scrollbarInlineSize = 50 - measureElem.offsetWidth;
window.sessionStorage.set('scrollbar-inline-size', scrollbarInlineSize);
document.documentElement.style.setProperty('--scrollbar-inline-size', `${scrollbarInlineSize}px`);
};
elem.appendChild(referenceElem);
referenceElem.appendChild(measureElem);
document.body.appendChild(elem);
const scrollbarInlineSize = window.sessionStorage.get('scrollbar-inline-size');
if (scrollbarInlineSize !== null) {
document.documentElement.style.setProperty('--scrollbar-inline-size', `${scrollbarInlineSize}px`);
} else {
window.requestAnimationFrame(() => determineWidth);
}
(imagine a little more complexity to it and this being the gist)
Would copy my comment from https://github.com/w3c/csswg-drafts/issues/6026, as it fits this issue more:
In our product we're currently essentially calculating the
scrollbar-inline-size
via JS, creating an invisible block with an overflow, and retrieving its width if it has one, then storing it as a custom property on root, to be used in calculations.Being able to use a keyword for this, alongside a query to detect if scrollbars are present on a container would be very helpful.
Why I'm mentioning “keyword” and “container” rather than an “environment variable” and “media query”: we have
scrollbar-width
which can control if we have the scrollbar, and we can have a container that can be scrolled programmatically when it hasoverflow: hidden
. That means that authors could want to a) detect if a certain scrollport has a visible scrollbar, and b) get the width of a scrollbar in that case, which can be different for different containers (scrollbar-width
and also current webkit scrollbar pseudo-elements).
There are two separate problems:
How much space do scrollbars consume in the browser, generally speaking? In browsers with overlay scrollbars, that’s 0px
(because the scrollbars are overlaid). In browsers with classic scrollbars, that’s by default 15px
on macOS and 17px
on Windows. It could be more or less, depending on other factors.
Is a vertical classic scrollbar currently present on the web page?
@Schepp’s function answers the first question. The proposed env(scrollbar-inline-size)
variable that’s the topic of this issue would also answer this question, if I understand correctly.
That’s good, but I’m also interested in the second question. Being able to answer this question in CSS would allow developers to solve the 100vw
problem.
If the scrollbar is present, then 100vw
is larger than the viewport width, so a full-width element needs to subtract the scrollbar size from its width.
/* classic scrollbar present */
.full-width {
width: calc(100vw - env(scrollbar-inline-size));
}
If the scrollbar is not present, then 100vw
is equivalent to the viewport width.
/* classic scrollbar not present */
.full-width {
width: 100vw;
}
As you can see, developers cannot solve the 100vw
problem with env(scrollbar-inline-size)
alone, and without knowing the answer to the second question.
It does not look like the second question will be answered in CSS anytime soon (see https://github.com/w3c/csswg-drafts/issues/6026#issuecomment-1713253071). I think the best bet to make 100vw
less of a problem in browsers with classic scrollbars is to make it smaller when scrollbar-gutter: stable
or overflow-y: scroll
is set on <html>
. That proposal is discussed in https://github.com/w3c/csswg-drafts/issues/5254.
It does not look like the second question will be answered in CSS anytime soon
Well, maybe it will, as people in the Chrome/ium team are experimenting with State Queries, which are meant to reflect exactly these types of things.
Dropping this here, which was proposed at this year‘s F2F in Cupertino: https://gist.github.com/bramus/bcca5788d8ced82837180b7a15760c84
Essentially you need 3 things:
As a sidenote, with an @property
and container query length units we can kinda work around the issue like this: https://codepen.io/kizu/pen/WNLjJvq — would currently work in Chrome and Safari. Won't work in Firefox when there is another container around the element which we want to apply our --vw
to, but can work everywhere if we're sure the topmost wrapper would be the only container.
But yes, this requires an additional wrapper around everything, and is more cumbersome. But can be a good viable workaround as soon as @property
would land in Firefox, and this could be used for any scrollable containers, not only for the viewport, though would require two containers to set it up.
(posting as a way to demonstrate a future workaround, and as something people could use for testing how the potential APIs would work in CSS)
Given scrollbar-width
can change the scrollbar width on a per element basis I don't think env(scrollbar-inline-size)
being a static value from the engine (e.g. 15px) would actually solve the issues.
I think the main one is that 100vw should just account for scrollbar width. That seems to be the primary case where people need to account for scrollbar width. Though I understand that could cause a cycle which seems to be the reason against it?
Given
scrollbar-width
can change the scrollbar width on a per element basis I don't thinkenv(scrollbar-inline-size)
being a static value from the engine (e.g. 15px) would actually solve the issues.
Maybe a second env var that exposes the thin size – e.g. thin-scrollbar-inline-size
– would help here? If authors have set scrollbar-width: thin;
, then they should use env(thin-scrollbar-inline-size)
in their calculations. Otherwise (in case of scrollbar-width: auto;
), they should use env(scrollbar-inline-size)
Would that rely on knowing the current value of the property? Style container queries feel a bit ott for that but perhaps it's fine?
I really do think that adding another env like that is complicating, specially if you consider the interest in having the least edits every time to get a change is done. Or worse, to do it right, with feature detection, you must actually check if the browser supports it and have an alternative for it... What a complete mess that would be...
Given
scrollbar-width
can change the scrollbar width on a per element basis I don't thinkenv(scrollbar-inline-size)
being a static value from the engine (e.g. 15px) would actually solve the issues.Maybe a second env var that exposes the thin size – e.g.
thin-scrollbar-inline-size
– would help here? If authors have setscrollbar-width: thin;
, then they should useenv(thin-scrollbar-inline-size)
in their calculations. Otherwise (in case ofscrollbar-width: auto;
), they should useenv(scrollbar-inline-size)
I believe a proper solution needs to be context aware. So basically the opposite of what @emilio wrote earlier because use cases want to consider the actual scrollbar width in their calculations. With a contextual value, authors don't have to care about whether there are normal or thin scrollbars or no scrollbars at all. That means, there should only be one keyword that refers to the width of the scrollbars of the nearest scroll container.
Sebastian
Scrollbars are a system provided, dynamic, and out-of-developer's control component that can "get in the way" by consuming unpredictable amounts of space. It'd be nice if the platform provided an environment variable that held the contextual system width value for scrollbars so that developer could use
calc()
to mitigate unpredictability.I've gently proposed using logical property syntax for the env variable name, since I feel this is the language used to talk about sizing on the web:
Here's a great bad example of how folks are working around this today: (aka, negative margins)
All in all, having access to the scrollbar width would give folks the values they need to gracefully handle the dynamic aspects of scrollbars with just CSS.
I assume there's aspects to this work that overlap with custom scrollbars, so this env var would need to be a property that can update at runtime yeah? Help me think out the rest of the fringe edge cases, as well as use cases where scrollbar width would be super helpful in your layouts/ui. Thanks!