Features CSS Needs

While the work on Houdini continues, I think it makes sense to prioritise a few simple features that would make life much easier for developers trying to build the most common things we need on the web.

I have carefully chosen the following features to fulfil two requirements:

  1. They enable new functionality that was previously impossible or very difficult without Javascript.
  2. They should be modest enough to be able to implemented in browsers.

Recent features such as scroll-snap-type, position: sticky and backdrop-filter are examples of simple capabilities that have greatly expanded the capabilities and quality of what we can build on the web.

A More Powerful calc #

tldr; allow multiplication and division of mixed units

The calc function is a game changer. The function was introduced years ago and it enables us to achieve layout that most of think needs features like container queries, which, to put it bluntly, are not possible because they introduce cyclical dependencies in the layout engine.

calc steps around the problem of cyclical dependencies as it inherently causes the child to be dependent on the parent dimensions, just like percentages have done since CSS started.

However, there is a strange limitation in the specification of calc that has outlived its usefulness:

At *, check that at least one side is <number>. If both sides are <integer>, resolve to <integer>. Otherwise, resolve to the type of the other side.

At /, check that the right side is <number>. If the left side is <integer>, resolve to <number>. Otherwise, resolve to the type of the left side.

What this means is that when you multiply or divide within a calc expression, only one of the two sides can contain a unit. The reason given for this limitation is the complexity of “unit algebra”. I think CSS authors who want to use complex mathematical expressions should be trusted to get their “unit algebra” right.

I should be allowed to write this:
calc((100% / 100px) * 90px).

This may seem like a strange ask, but it would make more sense with the next feature request.

More CSS functions — floor, ceil and round #

calc kind of opened the floodgates to functions in CSS values. Recently we got powerful new functionality with the introduction of min, max and clamp functions. It’s time to add functions to get CSS counterparts to Math.floor, Math.ceil and Math.round.

This would allow us to round our transforms to the nearest pixel value to avoid blurry text, but it would also allow us to do powerful layout without being forced into using grid or flexbox layout.

Consider, again, the example from above.
calc((100% / 100px) * 90px) can simply be written as 90% which is pretty useless. However, with functions like floor we could do this:

width: calc(100% / floor(100% / 100px))

This lets us calculate how many 100px wide elements can fit in a given container and divide the width equally among that many elements. Of course we can do similar things today with flexbox and grid layout. But those layout systems don’t always fit correctly. If you don’t have enough child elements your child elements will become too wide to fill the row. Or if you have a horizontally scrolling element, they’ll squish to less that 100px wide.

That simple calculation above would give us capabilities that are currently impossible in CSS. Further, since it’s just an extension of calc there should be cyclical dependencies and should actually be implementable.

Logical Operations in calc #

While we are making calc more powerful why not add logical operations such as <, >, and == and add a ternary statement? Image:

width: calc(100% > 500px ? 50% : 100%)

Wouldn’t this solve most of what we want from container queries?
But again, I don’t think this breaks any cyclical dependency rules of CSS.

ScrollTimeline #

A recent article talked about a feature like this being considered for Houdini.

This is a great idea. However, it’s a terrible idea to wait around for something so ambitious and dependent on Javascript as AnimationWorklet to get this feature.

How about a new CSS property that gives us most of what we need today:

Consider this:

animation-name: my-keyframe-animation;
/* the animation progress is synced to document scrolling */
animation-timeline: scroll-timeline-y(:root);
/* 
  the animation progress is synced to the horizontal scrolling of the
  element with the id element-id.
*/
animation-timeline: scroll-timeline-x(#element-id);

Almost all of our scroll-based animations could be solved in one fell swoop. Define a @keyframe animation, but set its progression based on the scrolling of an element instead of time. The scroll value would be converted to a percentage based on the scrollTop / scrollHeight so there would be no need to do any pixel math.

This seems like something that should be much easier to implement than ScrollTimeline for AnimationWorklet as it is smaller is scope, doesn’t have to deal with running javascript, a worklet, or stateful code. Rolling this out would also provide additional context for implementing AnimationWorklet.

(A small detail to note that might need additional thought — What happens if the scroll height of the container changes, but the user didn’t scroll. The percentage scrolled would change. In such a case, I would suggest that the animation-timeline should jump to the new percentage scrolled value even if that means a jerky animation change)

Hover and Swipe Timelines #

If we add support for ScrollTimelines to CSS, it would make sense to add other timelines as well. Namely Hover and Swipe. These would depend on mouse position on an element or touch gestures.

animation-timeline: hover-timeline-x(#element-id);

Here the timeline would progress from 0% when the pointer is hovering on left of edge of a the element to 100% when the pointer is hovering on the right edge of the element.

There are more open questions for something like this as you would need to decide what would happen before you start hovering on the given element after you stop hovering on it. We could decide to have a animation-timeline-initial-value property. (Or we could just default to 0). And we could keep the last valid value we did have on blur.

Whatever we decide for those cases would probably not be a problem as we have the ability to customise the behaviour with the :hover pseudo-selector.

Similarly, we can imagine something like:

animation-timeline: touch-timeline-y(#element-id);

This would work the same way as the hover-timeline-(x | y) but for swiping with touchscreens instead. You may have your own ideas for more timelines which we can add over time.

Over time, we can come up with new types of timelines and ways to transform the timelines (clamp the values etc).

Some ideas for additional timelines that should be implemented after ScrollTimeline, HoverTimeline and TouchTimeline:

An advantage of this API in addition to enabling better animations on the web is that this could be a new way to provide a limited version of APIs to the web that would otherwise raise privacy concerns. In the case of face-tracking for example, you would be able to sync an animation to a particular feature, but you won’t be able to query it directly.

Directional-Docking #

One under-appreciated feature introduced in the last few years is position: sticky. It makes many common use-cases simpler. It’s time to add it’s logical successor with position: directionally-sticky (name TBD).

This would work similarly to position sticky. Except it would scroll along with the scroll container every time the scroll direction changes until it hits it’s given limit with the top or bottom value.

Look at this video from Google’s blog about Animation Worklet again:

[right click to enable controls on the video above]

So essentially, it would scroll along with the scroll container but get stuck when it starts to leave the given interval between the top and bottom values.

The styles for the header in the video above could look like this:

height: 60px;
position: directionally-sticky;
top: 0;
bottom: calc(100vh - 60);

Conclusion #

These are some of the CSS features that would make my life building websites easier and enable some truly new capabilities that are currently not possible on the web or have significant performance costs.

If you agree with me, please share this post to get it more visibility. If you have ideas of your own, please tweet at me and we can advocate for features we really want.

 
10
Kudos
 
10
Kudos

Now read this

Skipping Computation altogether using Lazy.js

If you’re a javascript developer, you’ve probably used underscore (or lo-dash). Underscore is an amazing library that really helps keep your code functional, and provides a whole bunch of simple methods to keep your code ‘for-loop-free’.... Continue →