Each part grows from nothing out of its own centre, one after another, with a small overshoot as it settles.
Every animation on the page redraws with the shape you pick.
<!-- 1. Markup -->
<svg viewBox="0 0 120 120" class="k-scale-in-parts" aria-hidden="true">
<circle cx="60" cy="60" r="34" fill="none" stroke="currentColor" stroke-width="8" vector-effect="non-scaling-stroke" />
<line x1="60" y1="12" x2="60" y2="38" stroke="currentColor" stroke-width="8" stroke-linecap="round" vector-effect="non-scaling-stroke" />
<line x1="60" y1="82" x2="60" y2="108" stroke="currentColor" stroke-width="8" stroke-linecap="round" vector-effect="non-scaling-stroke" />
<circle cx="60" cy="60" r="10" fill="currentColor" />
</svg>
// 2. JS
function scaleInParts(svg) {
const _reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
const _duration = _reducedMotion ? 0.01 : 0.6
const _stagger = _reducedMotion ? 0 : 0.08
const _delay = _reducedMotion ? 0 : 0
const parts = paths(svg)
// Without fill-box an SVG origin resolves against the whole canvas, and each
// part would turn about the SVG's top-left corner instead of itself.
parts.forEach((part) => {
part.style.transformBox = 'fill-box'
part.style.transformOrigin = 'center'
})
const easing = 'linear(0.0000,0.0778,0.1521,0.2228,0.2901,0.3540,0.4146,0.4720,0.5264,0.5776,0.6259,0.6713,0.7139,0.7538,0.7910,0.8256,0.8578,0.8875,0.9149,0.9400,0.9630,0.9838,1.0027,1.0196,1.0346,1.0478,1.0594,1.0693,1.0777,1.0846,1.0901,1.0943,1.0973,1.0991,1.0998,1.0996,1.0984,1.0964,1.0936,1.0902,1.0861,1.0816,1.0765,1.0712,1.0655,1.0596,1.0537,1.0476,1.0416,1.0357,1.0300,1.0245,1.0194,1.0147,1.0106,1.0070,1.0040,1.0018,1.0005,1.0000)'
const animations = parts.map((part, i) =>
part.animate([{ transform: 'scale(0)' }, { transform: 'scale(1)' }], {
duration: _duration * 1000,
delay: _delay * 1000 + i * _stagger * 1000,
easing,
fill: 'both',
}),
)
return () => {
animations.forEach((a) => a.cancel())
parts.forEach((part) => {
part.style.transformBox = ''
part.style.transformOrigin = ''
})
}
}
function paths(svg) {
return [...svg.querySelectorAll('path, line, polyline, polygon, circle, ellipse, rect')].filter(
(el) => !el.closest('defs, clipPath, mask'),
)
}
scaleInParts(document.querySelector('.k-scale-in-parts'))