http://morning.photos

High Performance Dynamic Styles

Oleg Isonen
DailyJS
Published in
4 min readMay 9, 2017

--

Recently we released a new API for JSS, which is called “function values”. It delivers a 5–10× performance boost for animations and state based styles compared to React Inline Styles and most probably other libs as well. In its essence these techniques are not related to JSS, it is a CSSOM API lots of people are not aware of.

What kind of performance boost

I will use React as an example. The perf issue itself most probably applies to others as well. Also, I am “only” talking about “JavaScript Controlled Animations”. This means that every frame of that animation needs to be controlled by JavaScript. I am not talking about pure CSS transitions and keyframes. Also it is not “just” about animations, it is more a “state driven styling” — when your application has a number of elements which are dynamically positioned or styled based on the application state. I consider the entire application as an “Animation Scene” where elements get moved from time to time.

What exactly is the performance issue

Currently, for such tasks people use Inline Styles. Inline Styles have a number of limitations by design:

1. You can’t share an inline style declaration between multiple elements.
2. You need to apply them directly to a specific element.

This leads to an expensive Inline Styles application in React:

1. Reconciliation is expensive. In order to apply Inline Styles to some child node deep down in the tree structure, React goes through a complex process.
2. In order to apply a declarative style definition which provides the next style state, React needs to accomplish multiple steps:
- Find the style props which have been set before and unset them.
- Add default units and vendor prefixes to the new styles.
- Apply the new styles.

Now imagine this overhead at 60 FPS!

What we can do to avoid that overhead is — to not use React for this task at all.

CSSOM to the rescue

It turns out, CSSOM gives us access to CSSStyleDeclaration interface — same interface that DOM is using when you call `element.style`. This API has been implemented in DOM Level 2 and is supported by all browsers. Here is an example how it works in JavaScript:

// Create a <style> element. You can use an existing one as well.
const style = document.createElement(‘style’)
document.head.appendChild(style)
// Insert a CSS Rule to the sheet at position 0.
style.sheet.insertRule(‘.my-button {color: red}’, 0)
console.log(style.sheet.cssRules)

Now lets set the color to “green”:

style.sheet.rules[0].style.color = ‘green’
console.log(style.sheet.cssRules)

Thats it, we created and modified a CSS Rule.

Using this technique we didn’t just set a color in an efficient way. It is not faster or slower than using element.style.color = ‘green’. Using this API we can create CSS Rules before the elements get rendered. Those elements get a class name from the CSS Rule. After the elements are rendered we don’t need to touch them ever again. No reconciliation, no individual inline styles application.

By design CSS Rules is a flat list compared to a tree structure of the DOM. Having those two connected in advance, we can iterate over a flat list quickly and let the DOM update styles efficiently.

JSS Function Values API

The API described above is very low level. It would be great to have some nice declarative abstraction on top of it. Here is a small example of function values using React and styled-jss:

import styled from 'styled-jss'const Button = styled(‘button’)({ 
fontSize: 12,
color: (props) => props.theme.textColor
})

In this example, color property is defined by a function which receives React component props object and computes a value based on them. Once component receives new props, this function is called and value is applied to the CSS Rule.

Here is a small demo which shows the difference in the rendering performance between pure JSS, React-JSS and React Inline Styles. Of course, that demo is still very simplistic and doesn’t cover real world situations with complex tree structures, where perf benefits will be much much bigger than there.

In its essence, that demo can be described with the following pseudo code:

import React, {Component} from 'react'
import styled from 'styled-jss'
const Circle = styled('div')({
position: 'absolute',
width: 50,
height: 50,
borderRadius: '50%',
background: (props) => (
/* Generates a random or based on props color */
),
transform: (props) => (
/* Generates a random or based on props transform for positioning */
)
})
class Demo extends Component {
componentDidMount() {
this.tick()
}
tick = () => {
requestAnimationFrame(() => {
this.setState({/* Some new state */}, this.tick)
})
}
render() {
return (
<div>
{[...Array(amount)].map(() => <Circle />)}
</div>
)
}
}

Conclusion

React rendering is not ideal for frequent updates. A better way to do it is outside of React’s reconciliation process.

--

--

Oleg Isonen
DailyJS

Eng Core Automation @Tesla, ex @webflow, React, CSSinJS and co. Creator of http://cssinjs.org. Stealing ideas is great. Semantics is just meaning. Twitter: htt