Skip to content

Commit 9b9b4f7

Browse files
committed
Initial styles
1 parent f8c5c2a commit 9b9b4f7

File tree

7 files changed

+180
-28
lines changed

7 files changed

+180
-28
lines changed

packages/sdk-components-animation/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@
6666
"@webstudio-is/sdk-components-react": "workspace:*",
6767
"@webstudio-is/template": "workspace:*",
6868
"@webstudio-is/tsconfig": "workspace:*",
69+
"@webstudio-is/css-data": "workspace:*",
6970
"react": "18.3.0-canary-14898b6a9-20240318",
70-
"react-dom": "18.3.0-canary-14898b6a9-20240318"
71+
"react-dom": "18.3.0-canary-14898b6a9-20240318",
72+
"type-fest": "^4.32.0"
7173
}
7274
}

packages/sdk-components-animation/src/scroll.stories.tsx

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,57 @@
1-
const Component = () => {
2-
return <div>JO</div>;
3-
};
1+
import { parseCssValue } from "@webstudio-is/css-data";
2+
import { Scroll } from "./scroll";
3+
4+
const DEBUG = true;
45

56
export default {
67
title: "Components/Animate",
78
};
89

910
const Story = {
11+
parameters: {
12+
layout: "fullscreen",
13+
},
14+
1015
render() {
1116
return (
12-
<>
13-
<Component />
14-
</>
17+
<Scroll
18+
debug={DEBUG}
19+
action={{
20+
type: "scroll",
21+
animations: [
22+
{
23+
timing: {
24+
fill: "backwards",
25+
},
26+
keyframes: [
27+
{
28+
offset: 0,
29+
styles: {
30+
transform: parseCssValue(
31+
"transform",
32+
"translate(0, 100px)"
33+
),
34+
},
35+
},
36+
],
37+
},
38+
],
39+
}}
40+
>
41+
<div
42+
style={{ height: "200px", width: "200px", backgroundColor: "red" }}
43+
></div>
44+
<div
45+
style={{
46+
height: "100px",
47+
width: "100px",
48+
backgroundColor: "yellow",
49+
position: "fixed",
50+
left: 0,
51+
top: 0,
52+
}}
53+
></div>
54+
</Scroll>
1555
);
1656
},
1757
};

packages/sdk-components-animation/src/scroll.tsx

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { ClientOnly } from "./shared/client-only";
44
import { PolyfillLoader } from "./shared/scroll-polyfill";
55
import { DebugDelayLoader } from "./shared/debug-delay-loader";
66
import { escapeCssSpecifier } from "./shared/css-utils";
7+
import type { AnimationAction } from "./shared/animation-types";
8+
import { findKeyframeStylesWithZeroOffset } from "./shared/keyframes";
9+
import { hyphenateProperty, toValue } from "@webstudio-is/css-engine";
710

811
const componentAttributeId = `data-ws-animate-id`;
912

1013
const createSelector = (id: string) => `[${componentAttributeId}="${id}"] > *`;
1114

12-
const Animate = ({ id }: { id: string }) => {
15+
const Animate = ({ id, action }: { id: string; action: AnimationAction }) => {
1316
useLayoutEffect(() => {
1417
const selector = createSelector(id);
1518
const elements = document.querySelectorAll(selector);
@@ -18,6 +21,8 @@ const Animate = ({ id }: { id: string }) => {
1821
}
1922
const disposables = Array.from(elements)
2023
.map((elt) => {
24+
return () => {};
25+
2126
if (elt instanceof HTMLElement) {
2227
const animation = elt.animate(
2328
[{ offset: 0, transform: "translate(0, 100px)" }],
@@ -43,37 +48,71 @@ const Animate = ({ id }: { id: string }) => {
4348
return undefined;
4449
};
4550

46-
const StylesBeforeAnimate = ({ id }: { id: string }) => {
51+
const StylesBeforeAnimate = ({
52+
id,
53+
action,
54+
}: {
55+
id: string;
56+
action: AnimationAction;
57+
}) => {
4758
const selector = createSelector(id);
4859

49-
const styleContent = `
50-
@keyframes ws-scroll-animation-${id} {
51-
from {
52-
transform: translate(0, 100px);
60+
const animationsWithInitialStyles = action.animations.filter(
61+
(animation) =>
62+
animation.timing.fill === "backwards" || animation.timing.fill === "both"
63+
);
64+
65+
let styleContent = "";
66+
67+
let animationId = 0;
68+
for (const animation of animationsWithInitialStyles) {
69+
const styles = findKeyframeStylesWithZeroOffset(animation.keyframes);
70+
if (styles === undefined) {
71+
continue;
5372
}
54-
}
5573

56-
${selector} {
57-
animation-name: ws-scroll-animation-${id};
58-
animation-fill-mode: backwards;
59-
animation-play-state: paused;
60-
animation-duration: 1ms;
74+
const style = Object.entries(styles).map(
75+
([property, value]) => `${hyphenateProperty(property)}:${toValue(value)};`
76+
);
77+
78+
styleContent = `
79+
${styleContent}
6180
81+
@keyframes ws-scroll-animation-${id}-${animationId} {
82+
from {
83+
${style}
84+
composite: auto;
85+
}
86+
}
87+
88+
${selector} {
89+
animation-name: ws-scroll-animation-${id}-${animationId};
90+
animation-fill-mode: backwards;
91+
animation-play-state: paused;
92+
animation-duration: 1ms;
93+
94+
}
95+
`;
96+
animationId++;
6297
}
63-
`;
98+
6499
return <style dangerouslySetInnerHTML={{ __html: styleContent }} />;
65100
};
66101

67-
type ScrollProps = { debug?: boolean; children?: React.ReactNode };
102+
type ScrollProps = {
103+
debug?: boolean;
104+
children?: React.ReactNode;
105+
action: AnimationAction;
106+
};
68107

69-
export const Scroll = ({ debug = false, children }: ScrollProps) => {
108+
export const Scroll = ({ debug = false, children, action }: ScrollProps) => {
70109
const nakedId = useId();
71110
const id = escapeCssSpecifier(nakedId);
72111

73112
return (
74113
<>
75-
<ClientOnly fallback={<StylesBeforeAnimate id={id} />}>
76-
<Suspense fallback={<StylesBeforeAnimate id={id} />}>
114+
<ClientOnly fallback={<StylesBeforeAnimate id={id} action={action} />}>
115+
<Suspense fallback={<StylesBeforeAnimate id={id} action={action} />}>
77116
<ErrorBoundary
78117
fallback={null}
79118
onError={(error) => {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { StyleValue } from "@webstudio-is/css-engine";
2+
3+
export type KeyframeStyles = { [property: string]: StyleValue | undefined };
4+
5+
export type AnimationKeyframe = {
6+
offset: number | undefined;
7+
// We are using composite: auto as the default value for now
8+
// composite?: CompositeOperationOrAuto;
9+
styles: KeyframeStyles;
10+
};
11+
12+
type KeyframeEffectOptions = {
13+
rangeStart?: StyleValue | undefined;
14+
rangeEnd?: StyleValue | undefined;
15+
easing?: string;
16+
fill?: FillMode;
17+
};
18+
19+
/*
20+
type AnimationTiming = {
21+
delay?: number;
22+
duration?: number;
23+
easing?: string;
24+
fill?: FillMode;
25+
};
26+
*/
27+
28+
type ScrollAction = {
29+
type: "scroll";
30+
source?: "closest" | "nearest" | "root";
31+
axis?: "block" | "inline" | "x" | "y";
32+
animations: {
33+
timing: KeyframeEffectOptions;
34+
keyframes: AnimationKeyframe[];
35+
}[];
36+
};
37+
38+
// | ViewAction | ...
39+
export type AnimationAction = ScrollAction;
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// Artificial delay for loading the component for debugging purposes
22

3-
import { use } from "react";
3+
import { use, useState } from "react";
44

55
const wait = () => new Promise((resolve) => setTimeout(resolve, 1000));
66

7-
const waitglobal = wait();
8-
97
export const DebugDelayLoader = () => {
10-
use(waitglobal);
8+
const [waitLocal] = useState(wait);
9+
use(waitLocal);
1110
return undefined;
1211
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { AnimationKeyframe, KeyframeStyles } from "./animation-types";
2+
3+
export const findKeyframeStylesWithZeroOffset = (
4+
keyframes: AnimationKeyframe[]
5+
): KeyframeStyles | undefined => {
6+
let zeroOffsetStyles: KeyframeStyles = {};
7+
8+
if (keyframes.length === 0) {
9+
return undefined;
10+
}
11+
12+
if (keyframes[0].offset === undefined) {
13+
zeroOffsetStyles = { ...zeroOffsetStyles, ...keyframes[0].styles };
14+
}
15+
16+
for (const keyframe of keyframes) {
17+
if (keyframe.offset === 0) {
18+
zeroOffsetStyles = { ...zeroOffsetStyles, ...keyframe.styles };
19+
}
20+
}
21+
22+
if (Object.keys(zeroOffsetStyles).length === 0) {
23+
return undefined;
24+
}
25+
26+
return zeroOffsetStyles;
27+
};

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)