Re: [csswg-drafts] [css-pseudo-4] Standardizing input[type="range"] styling (#4410)

# Problem 6: progress size at any given point

Let's consider a basic `input[type='range']`. When this is at the minimum value, the `::-moz-range-progress` pseudo has a `width` that's `0%` of the range input element's `content-box`. When it's at the maximum value, the `::-moz-range-progress` pseudo has a `width` that's 100% of of the range input element's `content-box`.

This is illustrated below (you can also play with the [live demo](https://codepen.io/thebabydino/pen/jaoZEG) if you want) where the actual `input[type='range']`, the `track`, the `progress` and the `thumb` all have a non-zero `border` and a non-zero `padding`. The padding area is transparent for all, while the border and content areas are semitransparent (gold for the actual `input[type='range']`, tomato red for the `track`, grey for the `progress` and purple for the `thumb`).

![Animated gif. Shows the slider in Firefox with the thumb at the minimum value. The width of the border-box of the progress component is 0 in this case. We drag the thumb to the maximum slider value. The width of the border-box of the progress component equals that of the slider's content-box in this case.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2017/12/fill_mov_firefox.gif)

All seems fine for the default Firefox look where `thumb` is much taller than the `track` and `progress`.

However, the problem with it becomes obvious when we have a `thumb` that's shorter than the `track` or `progress` or has `border-radius: 50%`. Or both. You can see below how this looks in Firefox ([live demo](https://codepen.io/thebabydino/pen/pdMKGp)):

![Animated gif illustrating how the case described above works in Firefox using a slider with a grey track and orange progress.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2017/12/fill_mov_2_firefox.gif)

In the lower half, the `progress` is too short. In the upper part, the `progress` is too long.

---

IE/ pre-Chromium Edge used to do this better with `::-ms-fill-lower`. At the minimum value, this pseudo had a `width` that was half of that of the thumb's border-box. At the maximum value, its `width` was equal to that of the track's content-box minus half of the thumb's  border box. Illustrated below:

![Animated gif. Shows the slider in Edge with the thumb at the minimum value. The width of the border-box of the progress component is half the width of the thumb's border-box minus the track's left border and padding in this case. We drag the thumb to the maximum slider value. The width of the border-box of the progress component equals that of the track's content-box plus the track's right padding and border minus half the width of the thumb's border-box.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2017/12/fill_mov_edge.gif)

This meant it worked nicely in the cases where `::-moz-range-progress` failed:

![Animated gif illustrating how the case described above works in Edge using a slider with a grey track and orange progress.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2017/12/fill_mov_2_edge.gif)

---

I also do something similar for WebKit browsers nowadays. I set a custom property either on the `input[type='range']` itself or on its wrapper in the case of a range with a tooltip/ a range with a ruler with number labels and then update this custom property via JS whenever the slider value changes. When setting the custom property on the `input[type='range']` itself, it looks like this:

```
addEventListener('input', e => {
  let _t = e.target;
 
  _t.style.setProperty('--val', +_t.value)
})
```

(when setting it on the wrapper, I replace `_t` with `_t.parentNode`)

The `--val` custom property is then used to compute the `background-size` of the progress-emulating, non-repeating top `background` layer(s) on the `track` in the case of a single thumb range (the multi-thumb case I dissected in detail in this series of articles: [one](https://css-tricks.com/multi-thumb-sliders-particular-two-thumb-case/), [two](https://css-tricks.com/multi-thumb-sliders-particular-two-thumb-case/)).

```
$thumb-d: 4em; // thumb diameter
$thumb-r: .5*$thumb-d; // thumb radius

--track-w: min(100vw - 2.5em, 32em); /* responsive track width */

/* actually, it's not var(--val)/100 in the general case, 
 * it's (var(--val) - var(--min))/(var(--max) - var(--min)) */
--thumb-x: calc(var(--val, 50)/100*(var(--track-w) - #{$thumb-d}) + #{$thumb-r});
```

To also cover the no JS case, I multiply the computed value for the `background-size` with a `--js` custom property that defaults to `0`, but is switched to `1` in the JS case.

The `background-size`:

```
var(--progr-g) 0/ calc(var(--js, 0)*var(--thumb-x)) 100% no-repeat
```

Switching `--js` to `1`:

```
.js { --js: 1 }
```

```
document.documentElement.classList.add('js')
```

This is simple, lightweight, flexible, but... *no JS means no progress is seen* on the track.

---

Going back to how `::-moz-range-progress` currently works in Firefox, things look even worse when we want the `track` and `progress` to have rounded corners. Giving `::-moz-range-progress` a `border-radius` that's equal to half he height of its `border-box` ends up looking really bad at small values ([live demo](https://codepen.io/thebabydino/pen/YzYNBxb), contrast with the nice-looking JS solution used by the other browsers):

![Animated gif. Shows the issue with setting border-radius: $track-r on ::-moz-range-progress.](https://i.imgur.com/DblQdYi.gif)

## So what can we do?/ Attempted workarounds

We could round [only the left corners](https://codepen.io/thebabydino/pen/rNpjXKM) of the `progress`, but this creates a different kind of issue at the other end while not solving the first one:

![Animated gif. Shows the issue with setting border-radius: $track-r 0 0 $track-r on ::-moz-range-progress.](https://i.imgur.com/sftKD1f.gif)

Sure, we could set `overflow: hidden` on the actual `input[type='range']`, but that wouldn't solve the initial problem and could turn into a an even bigger problem if we want to have an outer shadow on the `thumb`, a shadow that should be visible outside the `padding-box` of our `input[type='range']` element.

Setting a `mask` on the `progress` [works better](https://codepen.io/thebabydino/pen/dyJvoQE), but we still have issues at the `thumb` corners (not to mention that adding a `mask` on the `progress` makes it show on top of the `thumb`, so then we need to set `transform: translateZ(1px)` on the `thumb` to fix that).

```
mask: 
  radial-gradient(circle at $thumb-r, red $thumb-r, transparent 0), 
  linear-gradient(90deg, red var(--mover-x), transparent 0) 
    #{$thumb-r}/ var(--track-w) 100%
```

![Animated gif. Shows the result when setting a mask on ::-moz-range-progress.](https://i.imgur.com/KhRRoqn.gif)

Covering those gaps with `box-shadow` won't do for a gradient/ image `progress`.

If the thumb only has a solid `background` and no outer shadow, we could ditch its rounding and use the `progress` background as a bottom layer underneath a `radial-gradient()` creating the `thumb` disc. But this [comes with alignment issues](https://codepen.io/thebabydino/pen/RwxpLqb), not to mention it requires to hide overflow on the `input[type='range']` element and this creates another problem in certain situations, as mentioned above.

```
@mixin thumb($flag: 0) {
  border: none;
  width: $thumb-d; height: $thumb-d;
  border-radius: $flag*50%;
  @if $flag > 0 { background: mediumvioletred }
  @else {
    background: 
      radial-gradient(closest-side, mediumvioletred calc(100% - 1px), transparent), 
      var(--progr-g) right #{$thumb-r} top 0/ 50% 100% no-repeat #333
  }
}
```

![Animated gif. Shows the result when using a track and progress-emulating background on the thumb.](https://i.imgur.com/LiBnnRi.gif)

There's also the option of moving and scaling `::-moz-range-progress` while also emulating its background on the left end of the `track`.

```
$track-w: 32em; // FIXED track width, not responsive
$track-h: 4em; // track height
$track-r: .5*$track-h; // track radius

$thumb-d: $track-h; // thumb diameter
$thumb-r: .5*$thumb-d; // thumb radius

$mover-x: $track-w - $thumb-d; // range of thumb motion
$progr-f: $mover-x/$track-w; // factor needed to scale progress to a max of $mover-x

transform-origin: 0; /* scale w.r.t. left edge */
transform: translate($track-r) scaleX($progr-f);
background: var(--progr-g) #{-1*$track-r}/ calc(100% + #{$track-r})
```

The result [seems good](https://codepen.io/thebabydino/pen/WNdpdQY) at a first glance, but increasing the height of the components relative to the `track` width shows [we have a big problem](https://codepen.io/thebabydino/pen/jOYBxMJ): squishing the `::-moz-range-progress` pseudo horizontally also affects the angle and the stripe thickness of its repeating gradient.

Sure, we could alter the angle of the `::-moz-range-progress` gradient while leaving the one of the `::-moz-range-track` gradient unchanged and the same for the distance between stripes, but this is going to cause alignment issues between the `::-moz-range-progress` gradient and the one on the end of the `::-moz-range-track`, which just makes us go deeper down the rabbithole of fixing problems caused by the fixes to the earlier problems.

In addition to this, the `transform` method only works if we have a *fixed* ratio between the `track` and `thumb` widths. It doesn't help if we want the `track` width to scale with the viewport while the `thumb` size remains the same because we cannot have division between values with units.

This method also comes with issues if we want to have a `box-shadow` on the `track` and/ or the `progress`.

---

At the end of the day, all these workarounds seem very hacky and not really worth the effort, so most often I just leave `::-moz-range-progress` alone and make Firefox use the same JS solution that I use for the other browsers.

If there's going to be a standard `progress`, it's best if its left edge always coincides with the left edge of the `content-box` of the `track`, while its right edge always coincides to that of the vertical midline of the `thumb`, as it used to be the case for `::-ms-fill-lower`.

cc @emilio as these last two problems are specific to `::-moz-range-progress`

-- 
GitHub Notification of comment by thebabydino
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/4410#issuecomment-1096879971 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Tuesday, 12 April 2022 15:31:14 UTC