@@ -20,13 +20,28 @@ export type ComputedStylesOptions = {
2020 classes ?: ClassValue | null ;
2121} ;
2222
23+ const supportedStyles = [
24+ 'fill' ,
25+ 'stroke' ,
26+ 'opacity' ,
27+ 'fillOpacity' ,
28+ 'strokeWidth' ,
29+ 'fontWeight' ,
30+ 'fontSize' ,
31+ 'fontFamily' ,
32+ 'textAnchor' ,
33+ 'textAlign' ,
34+ 'paintOrder' ,
35+ ] as const ;
36+
2337/**
2438 * Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
2539 */
26- export function getComputedStyles (
40+ export function _getComputedStyles (
2741 canvas : HTMLCanvasElement ,
2842 { styles, classes } : ComputedStylesOptions = { }
2943) {
44+ // console.count(`getComputedStyles: ${getComputedStylesKey(canvas, { styles, classes })}`);
3045 try {
3146 // Get or create `<svg>` below `<canvas>`
3247 let svg = document . getElementById ( CANVAS_STYLES_ELEMENT_ID ) as SVGElement | null ;
@@ -61,14 +76,32 @@ export function getComputedStyles(
6176 ) ;
6277 }
6378
64- const computedStyles = window . getComputedStyle ( svg ) ;
79+ // Capture copy to enable memoization and avoid capturing all styles (which is very slow)
80+ const computedStyles = supportedStyles . reduce ( ( acc , style ) => {
81+ acc [ style ] = window . getComputedStyle ( svg ) [ style ] ;
82+ return acc ;
83+ } , { } as CSSStyleDeclaration ) ;
84+
6585 return computedStyles ;
6686 } catch ( e ) {
6787 console . error ( 'Unable to get computed styles' , e ) ;
6888 return { } as CSSStyleDeclaration ;
6989 }
7090}
7191
92+ function getComputedStylesKey (
93+ canvas : HTMLCanvasElement ,
94+ { styles, classes } : ComputedStylesOptions = { }
95+ ) {
96+ return JSON . stringify ( { canvasId : canvas . id , styles, classes } ) ;
97+ }
98+
99+ export const getComputedStyles = memoize ( _getComputedStyles , {
100+ cacheKey : ( [ canvas , styleOptions ] ) => {
101+ return getComputedStylesKey ( canvas , styleOptions ) ;
102+ } ,
103+ } ) ;
104+
72105/** Render onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
73106function render (
74107 ctx : CanvasRenderingContext2D ,
@@ -89,11 +122,29 @@ function render(
89122 )
90123 ) {
91124 // Skip resolving styles if no classes are provided and no styles are using CSS variables
92- // TODO: Convert colors using `rgb(0 0 0 / 50%)` to `rgba(0, 0, 0, 0.5)`
93125 resolvedStyles = styleOptions . styles ?? { } ;
94126 } else {
95- const computedStyles = getComputedStyles ( ctx . canvas , styleOptions ) ;
96- resolvedStyles = computedStyles ;
127+ // Remove constant non-css variable properties (ex. `strokeWidth: 0.5`, `fill: #123456`) as not needed and improves memoization cache hit
128+ const { constantStyles, variableStyles } = Object . entries ( styleOptions . styles ?? { } ) . reduce < {
129+ constantStyles : StyleOptions ;
130+ variableStyles : StyleOptions ;
131+ } > (
132+ ( acc , [ key , value ] ) => {
133+ if ( typeof value === 'number' || ( typeof value === 'string' && ! value . includes ( 'var(' ) ) ) {
134+ ( acc . constantStyles as any ) [ key ] = value ;
135+ } else if ( typeof value === 'string' && value . includes ( 'var(' ) ) {
136+ ( acc . variableStyles as any ) [ key ] = value ;
137+ }
138+ return acc ;
139+ } ,
140+ { constantStyles : { } as StyleOptions , variableStyles : { } as StyleOptions }
141+ ) ;
142+
143+ const computedStyles = getComputedStyles ( ctx . canvas , {
144+ styles : variableStyles ,
145+ classes : styleOptions . classes ,
146+ } ) ;
147+ resolvedStyles = { ...computedStyles , ...constantStyles } ;
97148 }
98149
99150 // Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
0 commit comments