Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 115 additions & 16 deletions src/stories/components/AnimationDemo/Scene/Scene.module.css
Original file line number Diff line number Diff line change
@@ -1,38 +1,137 @@
.root {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
box-sizing: border-box;
isolation: isolate;
}

.layer {
position: absolute;
inset: 0;
pointer-events: none;
}

.layerSky {
z-index: 0;
}

.layerSun {
z-index: 1;
}

.layerMoon {
z-index: 2;
}

.layerSea {
z-index: 3;
}

.layerBeam {
z-index: 4;
}

.layerHills {
z-index: 5;
}

.svg {
display: block;
width: 100%;
height: 100%;
}

.transition {
transition:
fill 1.15s ease,
stop-color 1.15s ease,
opacity 1.15s ease,
transform 1.15s ease,
filter 1.15s ease;
opacity 1.15s ease;
}

.celestial {
will-change: transform, opacity;
transform-box: fill-box;
transform-origin: center center;
transition:
transform 1.2s ease,
opacity 1.2s ease,
filter 1.2s ease;
}

.sun,
.moon,
.seaBeam {
will-change: transform, opacity;
.sun {
transition:
transform 1s ease-out,
opacity 1s ease-out;
}

.seaBeam {
.sunGlow {
transform-box: fill-box;
transform-origin: center 38%;
transform-origin: center center;
animation: sunGlowFloat 36s linear infinite;
}

.moon {
transition:
opacity 1.2s ease,
transform 1.2s ease,
filter 1.2s ease;
transform 1s ease-out,
opacity 1s ease-out;
}

.beam {
will-change: opacity;
transition: opacity 1s ease;
}

.root[data-mode='sunset'] .sun {
transition:
transform 1s ease-in-out,
opacity 1s ease-in-out;
}

.root[data-mode='night'] .sun {
transition:
transform 0.5s ease-in,
opacity 0.5s ease-in;
}

.root[data-mode='sunrise'] .sun {
transition:
transform 1s ease-out,
opacity 1s ease-out;
}

.root[data-mode='day'] .moon,
.root[data-mode='sunset'] .moon {
transition:
transform 0.5s ease-in,
opacity 0.5s ease-in;
}

.root[data-mode='night'] .moon {
transition:
transform 1s ease-out,
opacity 1s ease-out;
}

.root[data-mode='sunrise'] .moon {
transition:
transform 0.5s ease-in,
opacity 0.5s ease-in;
}

@keyframes sunGlowFloat {
0% {
transform: rotate(0deg) scale(0.96);
}

50% {
transform: rotate(180deg) scale(1.04);
}

100% {
transform: rotate(360deg) scale(0.96);
}
}

@media (prefers-reduced-motion: reduce) {
.sunGlow {
animation: none;
}
}
21 changes: 13 additions & 8 deletions src/stories/components/AnimationDemo/Scene/Scene.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,29 @@ import { describe, expect, it } from 'vitest';
import { Scene } from './Scene';

describe('Scene', () => {
it('namespaces SVG defs per instance to avoid id collisions', () => {
it('namespaces per-instance defs and masks to avoid collisions across layered svgs', () => {
const { container } = render(
<>
<Scene mode="day" />
<Scene mode="night" />
</>,
);

const svgElements = Array.from(container.querySelectorAll('svg'));
const skyRects = Array.from(container.querySelectorAll('svg > rect:first-of-type'));
const gradientIds = Array.from(container.querySelectorAll('linearGradient')).map(node =>
const sceneRoots = Array.from(container.querySelectorAll('[data-mode]'));
const gradientIds = Array.from(
container.querySelectorAll('linearGradient, radialGradient'),
).map(node => node.getAttribute('id'));
const maskIds = Array.from(container.querySelectorAll('mask')).map(node =>
node.getAttribute('id'),
);
const daySkyRect = sceneRoots[0]?.querySelector('svg rect');
const nightSkyRect = sceneRoots[1]?.querySelector('svg rect');

expect(svgElements).toHaveLength(2);
expect(sceneRoots).toHaveLength(2);
expect(new Set(gradientIds).size).toBe(gradientIds.length);
expect(skyRects[0]?.getAttribute('fill')).toMatch(/^url\(#.+-sky\)$/);
expect(skyRects[1]?.getAttribute('fill')).toMatch(/^url\(#.+-sky\)$/);
expect(skyRects[0]?.getAttribute('fill')).not.toBe(skyRects[1]?.getAttribute('fill'));
expect(new Set(maskIds).size).toBe(maskIds.length);
expect(daySkyRect?.getAttribute('fill')).toMatch(/^url\(#.+-sky\)$/);
expect(nightSkyRect?.getAttribute('fill')).toMatch(/^url\(#.+-sky\)$/);
expect(daySkyRect?.getAttribute('fill')).not.toBe(nightSkyRect?.getAttribute('fill'));
});
});
Loading
Loading