Skip to content

Commit

Permalink
components: Rewrite Spinner for better performance (#2384)
Browse files Browse the repository at this point in the history
  • Loading branch information
lachlanjc authored Jan 23, 2023
1 parent 83bd581 commit 2d8bfc3
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 83 deletions.
67 changes: 24 additions & 43 deletions packages/components/src/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import React, { SVGAttributes } from 'react'
import { keyframes } from '@emotion/react'

import React from 'react'
import type { ThemeUICSSObject } from '@theme-ui/css'

import { Box, BoxOwnProps, BoxProps } from './Box'
import type { ForwardRef } from './types'
import { __internalProps } from './util'

const spin = keyframes({
from: {
transform: 'rotate(0deg)',
},
to: {
transform: 'rotate(360deg)',
},
})

export interface SpinnerProps
extends Omit<
React.ComponentPropsWithRef<'svg'>,
'opacity' | 'color' | 'css' | 'sx' | 'strokeWidth'
>,
BoxOwnProps {
size?: number | string
size?: number
strokeWidth?: number
title?: string
duration?: number
Expand All @@ -34,37 +23,17 @@ export const Spinner: ForwardRef<SVGSVGElement, SpinnerProps> =
size = 48,
strokeWidth = 4,
max = 1,
title = 'Loading...',
duration = 500,
title = 'Loading',
duration = 750,
...props
},
ref
) {
const r = 16 - strokeWidth
const C = 2 * r * Math.PI
const offset = C - (1 / 4) * C

const __css: ThemeUICSSObject = {
color: 'primary',
overflow: 'visible',
}

const circleProps: SVGAttributes<SVGCircleElement> = {
cx: 16,
cy: 16,
r,
strokeDasharray: C,
strokeDashoffset: offset,
}

const __circleCss: ThemeUICSSObject = {
transformOrigin: '50% 50%',
animationName: spin.toString(),
animationTimingFunction: 'linear',
animationDuration: duration + 'ms',
animationIterationCount: 'infinite',
}

const svgProps = {
strokeWidth,

Expand All @@ -77,6 +46,14 @@ export const Spinner: ForwardRef<SVGSVGElement, SpinnerProps> =
role: 'img',
}

const circleProps = {
strokeWidth,
r: 16 - strokeWidth,
cx: 16,
cy: 16,
fill: 'none',
}

return (
<Box
ref={ref}
Expand All @@ -86,14 +63,18 @@ export const Spinner: ForwardRef<SVGSVGElement, SpinnerProps> =
{...__internalProps({ __css })}
>
<title>{title}</title>
<circle cx={16} cy={16} r={r} opacity={1 / 8} />
<Box
as="circle"
{...(circleProps as {})}
{...__internalProps({
__css: __circleCss,
})}
/>
<circle {...circleProps} opacity={1 / 8} />
<circle {...circleProps} strokeDasharray="20 110">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="0 16 16"
to="360 16 16"
dur={`${duration}ms`}
repeatCount="indefinite"
/>
</circle>
</Box>
)
})
53 changes: 17 additions & 36 deletions packages/components/test/__snapshots__/index.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1329,22 +1329,6 @@ exports[`Slider renders 1`] = `
`;

exports[`Spinner renders 1`] = `
@keyframes animation-0 {
from {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.emotion-0 {
box-sizing: border-box;
margin: 0;
Expand All @@ -1353,21 +1337,6 @@ exports[`Spinner renders 1`] = `
overflow: visible;
}
.emotion-1 {
box-sizing: border-box;
margin: 0;
min-width: 0;
transform-origin: 50% 50%;
-webkit-animation-name: animation-0;
animation-name: animation-0;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-duration: 500ms;
animation-duration: 500ms;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
<svg
className="emotion-0"
fill="none"
Expand All @@ -1379,22 +1348,34 @@ exports[`Spinner renders 1`] = `
width={48}
>
<title>
Loading...
Loading
</title>
<circle
cx={16}
cy={16}
fill="none"
opacity={0.125}
r={12}
strokeWidth={4}
/>
<circle
className="emotion-1"
cx={16}
cy={16}
fill="none"
r={12}
strokeDasharray={75.39822368615503}
strokeDashoffset={56.548667764616276}
/>
strokeDasharray="20 110"
strokeWidth={4}
>
<animateTransform
attributeName="transform"
attributeType="XML"
dur="750ms"
from="0 16 16"
repeatCount="indefinite"
to="360 16 16"
type="rotate"
/>
</circle>
</svg>
`;

Expand Down
9 changes: 5 additions & 4 deletions packages/docs/src/pages/components/spinner.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import { Spinner } from 'theme-ui'
| Name | Type | Description |
| ------------- | ------ | ------------------------------------------------ |
| `title` | String | (default `'loading'`) Text for SVG `<title>` tag |
| `size` | Number | (default `48`) chart diameter |
| `size` | Number | (default `48`) indicator diameter |
| `strokeWidth` | Number | (default `4`) stroke width |
| `duration` | Number | (default `750`) duration of animation in ms |

A `title` attribute should be provided to the component for accessibility purposes.
The element uses `role='img'` by default.
Pass any overrides or additional attributes for the SVG element as props.
A `title` attribute should be provided to the component for accessibility
purposes. The element uses `role='img'` by default. Pass any overrides or
additional attributes for the SVG element as props.

## Variants

Expand Down

0 comments on commit 2d8bfc3

Please sign in to comment.