Skip to content

Optimizing Performance

This guide goes over what it means when we say CSS performance as well as some potential optimizations you can take advantage of.

CSS Performance

For many, the performance of CSS may sound crazy. It's a stylesheet language not a programming language. But the way you write your CSS can have a serious effect on the performance of rendering the page. This is especially evident in applications with many moving parts and constantly shifting elements. Discord is definitely one of those applications. For some detailed information, take a look at how browsers perform style computations.

Selectors

Descendant Selectors

If you're a developer using CSS, you probably think about descendant selectors from left to right or top to bottom. Meaning if you see the selector .container .row you might read or interpret it as "from .container find .row and apply styles". But browsers do the opposite and process from the right-most to the left-most. So for the selector .container .row every time the browser encounters and element matching .row it walks the ancestors of this element until it finds .container and if it isn't found, that means it will walk the entire tree. You can read a bit more about this process in the article linked above.

But how might this affect performance? In a complex DOM tree like Discord there might be many instances of .container .row and even more instances of just .row. So by adding your style you'll be causing the browser to walk the DOM tree several times over. This can get compounded even more when we deal with specificity and try to overwrite Discord's styles. If Discord's style uses .container .row it might be instinctual to use .main .container .row to override their styles. This means that not only will every .row be tree walked to find .container, now every .container .row will be tree walked to additionally look for .main.

On their own having some styles with many descendant selectors will not have much of an effect on the performance of the client except on the absolute lowest power of machines. But it's easy to see how compounding your selectors can easily add up, especially in large themes with hundreds and hundreds of styles.

Attribute Selectors

Attribute selectors are great and very useful. But they also mean more computations for every single element in an attempt to match them. This is worse for attribute selectors that do partial matching rather than exact matching.

Animations & Transitions

This section may feel a little more obvious than the last, but having more animations can lead to a drop in performance. More animations means more calculations for the users computer to handle and more rendering for it to deal with. The same can be said for transitions. This is usually not as bad when the user has a dedicated GPU and they have hardware acceleration enabled, but this is definitely not the case for all users. What's worse, is that web rendering doesn't always get put onto the dedicated GPU even if it exists and hardware acceleration is on. There are some ways to try and trick it though that we'll go over in the next section.

Optimizations

So we've talked about some potential performance issues with selectors and animations. Now let's talk about how we can improve them.

Selectors

Descendant Selectors

Here are a few tips for improving your descendant selectors while keeping up specificity.

  • Use an element closer in the tree for the next ancestor (.main .container .row => .container .row-wrap .row)
  • Use another class on the target or parent element (.card-container .card => .card.card-info)
  • Use IDs whenever possible
  • Make use of pseudo-classes on target elements
  • Consider the use of !important sparingly
  • Duplicate your selectors (.container .row => .row.row.row)

Attribute Selectors

Here are a few tips for improving your attribute selectors.

  • Consider if it's absolutely necessary.
  • Try using an exact match [attr=value] over a wildcard [attr*=val]
  • Try using native attribute selection via pseudo-classes
  • Try using state checks rather than attributes
  • Monitor the classes on the element to see if a state related class (e.g. active) is added when needed

Animations & Transitions

There are a few different techniques that improve your animations and transitions. One of the first is understanding the rule of thumb: "Do not animate CSS properties that trigger layout or painting whenever possible." This includes properties like heigh and width that could cause other elements to move around as you animate. It is very difficult to produce smooth and performant animations in these cases.

You can indicate to the browser before animations and transitions what properties will change which can help the browser optimize performance. Just add will-change: property; to the element in question.

As we talked about above, not all animations will be rendering on the GPU when available. There a couple ways to try and trigger this, and it usually involves creating new paint layers. If you're unfamiliar with this concept, that's understandable, it's a concept internal to web browsers. But if you do want to go down this route, you can try adding a 3d transform to the element that does nothing (transform: translateZ(0)). This works in all browsers. If you're targeting just modern browsers (like Chrome/Discord) then it's enough to just do will-change: transform.

If you want to learn more about performant CSS animations and even learn how to debug your own, check out the animation guide from web.dev.