Why Canvas Styling
This package contains everything needed to create CSS styling. This styling package contains a runtime for development and a static parsing process for build time.
Here are the goals for this project:
- TypeScript autocomplete of CSS object properties
- Low runtime for development
- Static CSS compilation for faster user experience
- Static CSS extraction for CSS only packages
- Dynamic styles using CSS Variables
If you’re using Canvas Kit and not directly using this package, there is nothing extra to do on your end. The Canvas Kit packages are using the static compilation as part of the build process. If you want to use this package for your own styles, you don’t need to do anything special to use in development. Included is a small runtime to get styling working. If you wish to statically compile your CSS from your TypeScript files for faster page loading, visit the Getting Started page.
Why?
Canvas Kit no longer needs to support IE11 which allows us to use CSS Custom Properties a.k.a. CSS Variables. Dynamic style properties (properties that change during the lifecylce of the component) are the most costly in terms of performance in Emotion and should be avoided. Also, any conditionals in your Emotion style functions create unique hashes in the Emotion cache and makes that render frame pay an expensive style recalculation.
We can avoid most of the cost of Emotion’s runtime by using @emotion/css instead and hoist all style definitions outside of a component’s render function. All dynamic styling can be moved into “modifiers” (from BEM).
There’s still a runtime to select which styles should apply to an element and what the CSS Variable
should be, but it is essentially only having to choose what CSS classes should apply to an element
and changing the style property to set the CSS Variables. This does not require new
StyleSheet inserts which cause
expensive style recalculation. Think of the runtime as being the following:
<div
className={getClassNames(/* input from props */)}
style={getCSSVarValues(/* input from props */)}
/>For further information, please read our GitHub discussion on the future of styling
What is Canvas Kit Styling?
Canvas Kit Styling includes two things:
- A utility function wrapper around
@emotion/css - An optional static compiler to remove most of the runtime
Utility Functions
This packages provides three utility functions to make it easier to style element-based components. The following is a brief description of each function. If you’d like to read a more in-depth discussion of each, our API docs.
The primary utility function is the createStyles function. It makes a call to the css function
from @emotion/css. Emotion still does most of the heavy lifting by handling the serialization,
hashing, caching, and style injection.
The other two utility functions, createVars and createModifiers, provide supplemental styling
functionality. createVars allows you to create temporary CSS variables within components to create
dynamic properties. And createModifiers creates a modifier function to create dynamic groups of
properties. If you’re familiar with modifiers in BEM (Block,
Element, Modifier) CSS, you’re well on your way to understanding this function’s intent.
Static Compiler
The static compiler run as a TypeScript transform during TypeScript’s transpilation phase. It
requires the TypeScript type checker to determine the static value of any variables used. The
transformer calls @emotion/serialize to pre-compute the serialized styles and hash so that
@emotion/css can skip these steps. For example, there’s the before/after of the code.
// before
const myVars = createVars('textColor', 'backgroundColor');
const myStyles = createStyles({
fontSize: 12,
color: cssVar(myVars.textColor),
backgroundColor: cssVar(myVars.backgroundColor),
});
// after
const myVars = createVars('textColor', 'backgroundColor');
const myStyles = createStyles({
name: 'a8g234',
styles:
'font-size: 12px; color: var(--css-my-vars-textColor); backgroundColor: var(--css-my-vars-backgroundColor);',
});Emotion has an internal shortcut that recognizes the styles property and
short-circuits interpolation.
Performance
createStyles is more performant than styled components or the css prop because the styles must
be statically evaluated. The actual characters of a CSS property value cannot change at runtime.
This means we do not need to recalculate a hash every render or inject new StyleSheet entries into
the StyleSheetList in a render cycle. Injecting new StyleSheets causes slow
Style Recalculation.
What is not well known is browser engines maintain an internal selector cache to make style
recalculations as fast as possible. Adding a CSS class to a DOM element will invoke a style
recalculation, but if the the CSS selector is already in the StyleSheetList, the browser can
optimize how those styles are applied to the current element.
In the runtime Emotion’s case, a novel style will result in a new style hash which results in a new
StyleSheet being injected into the StyleSheetList. To be safe, the browser’s runtime engine will
throw away any style recalculation cache and start from scratch. This happens if you render a new
component on the page that hasn’t been rendered yet, or if you make one of your style properties
dynamic between render cycles. Eventually the Emotion cache gets warm and style recalcuation costs
start to normalize and no longer invalidate the browser’s selector cache.
On a page with over 1,000 elements and over 1,000
CSSRules, (typical of a large web
application), the difference between a < 1ms for warmed selector cache and > 100ms for a fresh
selector cache. createStyles encourages a pattern similar to BEM which
works well with the browser’s selector cache by not injecting new StyleSheets during a page’s
normal operations. All styles are injected before any rendering takes place.
Note: Since style props force Emotion’s dynamic rendering, style props will fall back to Emotion’s runtime performance characteristics and lose any benefits gained. Also if you use
styledcomponents or thecssprop in a tree that usescreateStyles, the styles created by the runtime APIs will still result in a selector cache invalidation. Even if you want to usestyledor thecssprop, consider using CSS Variables for dynamic CSS property values to reduce the performance overhead of Emotion.
Can't Find What You Need?
Check out our FAQ section which may help you find the information you're looking for. For further information, contact the #ask-canvas-design or #ask-canvas-kitchannels on Slack.