React inline styles VS CSS : benchmark

August 17th, 2015 | javascript, performance, react |

CSS vs Inline

So, I was wondering if there was any performance downside to use React inline styles (I like them!) vs keep the old plain css stylesheets (that we love to override!). Of course there is, but I wanted some numbers!

I think both ways are useful actually, as Michael Chan said at React Europe ():
– the css can be used for the layout (such as the bootstrap layout classes: row-fluid, span*, etc.)
– the inline styles should be used for the “state-styles” (the style that can change according to a state, which is dynamic).

Why stupid

I decided to try something stupid and not truly relevant but anyway :

Generate a big table, one with inline styles, the other (in another page), with the same data, but with css, and then check the performance/size/time of both.

That’s why it’s not relevant because you would not generate a big table in your DOM. You will more likely have some kind of virtual scrolling / lazy rendering.
But, what about a test, just to see if that’s more slow when I scroll or not, and what about the rendering time ?

 
Chrome 45b, React 0.13.3 (debug and production)

Test case

So I generated a

of 1,000 rows with 9 columns, all of them with the same content “”, then I did the same with 10,000 rows to check the trend.

I’m going to test :
– the smoothness when I scroll down the page through Chrome DevTools Timeline
size of the DOM : useful to know for isomorphic universal javascript (server-side rendering)
– time to mount the DOM : I’m going to use componentDidMount, this is where the DOM is available after render is called
– time to be rendered by the browser : I’m going to use requestAnimationFrame to know when the next frame after render is ready

Basically, the skeleton of the React component is :

const createOneInlineStyleRow = (i) => {
  return (
... ); }; export default class AppInline extends React.Component { componentDidMount() { console.timeEnd('didMount'); } render() { console.time('render'); console.time('didMount'); requestAnimationFrame(function() { console.timeEnd('render'); }); const rows = times(NB_ROWS, createOneInlineStyleRow); return (
{rows}
); } }

The style of the :

const style = {
  fontFamily: 'Consolas',
  padding: 10,
  color: "#444",
  border: "3px solid orange",
  position: "relative",
  width: "15%",
  height: "25px",
  letterSpacing: 0,
  overflow: "hidden",
  fontSize: 10,
  fontVariant: "small-caps"
};

And the same in css for the css table :

.tableCss td {
   font-family: Consolas;
   padding: 10px;
   color: #444;
   border: none;
   border: 3px solid orange;
   position: relative;
   width: 15%;
   height: 25px;
   letter-spacing: 0;
   overflow: hidden;
   font-size: 10px;
   font-variant: small-caps;
}

Basically, here is this beautiful design :

Results

Timeline

I found no difference when scrolling, Chrome DevTools timeline supports my perception : no difference, no fps drop.
The fact that the DOM is inline or in a proper css seems to not matter, I guess it’s already in the browser memory when it’s rendered so it does not influence the smoothness.

Size of the DOM

Without surprise, the DOM size is huge with the inline styles and is linearly proportional to the number of rows.

Type 1,000 rows 10,000 rows Ratio
CSS 517,725 chars ~5,177,250 chars x10
Inline 2,283,742 chars ~22,837,420 chars x10
x4.4 x4.4

That’s clearly something to take into account if you do some universal javascript. 2,000,000 chars is 2MB of data. (but who sends a big table anyway?!)

Time to mount the DOM

This is where componentDidMount is useful. We start a timer in the render method and end it when componentDidMount is called.
React has done its job converting the virtual DOM into a proper DOM and has injected it into our mounted node.

I let the times with the development version of React, because I started with and used the production version afterwards. They should not be taken into account, it’s not relevant in our case.

Type 1,000 rows 10,000 rows Ratio
CSS (debug) 650ms-750ms 6,000ms ~x10
Inline (debug) 1,000ms-1100ms 10,000ms ~x10
CSS 310ms 2,300ms ~x7.5
Inline 600ms 4,900ms ~x8
x2 x2.1

Yes, it takes more time to stringify our virtual DOM / mount it into the DOM.
The delta decreases for less rows but it’s still significant: 70ms (css) against 105ms (inline) for 100 rows (x1.5).

Rendering time

The DOM is mounted, the browser needs to render the table now. We can assume it will take longer to render the inline styles, because it has more to parse and store every style attribute of every td.

Type 1,000 rows 10,000 rows Ratio
CSS (debug) 1,100ms-1,200ms 9,500ms ~x10
Inline (debug) 1,500-1,600ms 14,000ms ~x10
CSS 720ms 6,000ms ~x8
Inline 1,080ms 9,400ms ~x8.7
x1.5 x1.56

Still a small overhead. I guess we could see some nice differences according to which browser is tested out!

What about another browser ? Edge !

I gave a try on Edge with 10,000 rows (React production version) :

Type mount time rendering time
CSS 4,115ms 11,591ms
Inline 11,418ms 21,113ms

It’s basically the double of Chrome times. :cry:

We knew that

So yes, we were kinda aware of all these behaviors, but now, we have some numbers !

This global analysis demonstrated that the inline styles take way more size and more time to be handled by the browser, but it has no more impact as soon as it’s rendered.

Compared to all the advantages the inline styles offer, we just have to be aware of those potential performance bottlenecks when thinking about the .

Further

Some plugins exists to extract the inline styles to css such as :
https://github.com/petehunt/jsxstyle : code inline then extract the constants to a stylesheet
https://github.com/js-next/react-style + https://github.com/js-next/react-style-webpack-plugin :

Or if you want the full power of css in javascript (media queries, pseudo-selectors, modifiers, and more!) :
Radium