Skip to content

Commit 29c8d84

Browse files
committed
Merge branch 'master' of github.com:wix/interact into splittext_integration_2nd_attempt
2 parents 6245d70 + 2542dfe commit 29c8d84

7 files changed

Lines changed: 257 additions & 3 deletions

File tree

apps/demo/src/react/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SequencePlayground } from './components/SequencePlayground';
77
import { SequenceEntranceDemo } from './components/SequenceEntranceDemo';
88
import { SequenceClickDemo } from './components/SequenceClickDemo';
99
import { SequenceEasingComparison } from './components/SequenceEasingComparison';
10+
import { SvgPathMorphDemo } from './components/SvgPathMorphDemo';
1011

1112
const heroCopy = [
1213
'Tune triggers, easings, and delays in real time.',
@@ -39,6 +40,7 @@ function App() {
3940
</header>
4041

4142
<Playground />
43+
<SvgPathMorphDemo />
4244
<SelectorConditionDemo />
4345
<div className="scroll-showcase-wrapper">
4446
<ResponsiveDemo />
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Interaction, type InteractConfig } from '@wix/interact/react';
2+
import { useInteractInstance } from '../hooks/useInteractInstance';
3+
4+
const svgPathMorphConfig: InteractConfig = {
5+
interactions: [
6+
{
7+
key: 'svg-path-morph',
8+
trigger: 'viewEnter',
9+
effects: [
10+
{
11+
effectId: 'svg-path-morph-effect',
12+
selector: 'path:nth-child(1)',
13+
},
14+
],
15+
},
16+
],
17+
effects: {
18+
'svg-path-morph-effect': {
19+
duration: 2000,
20+
easing: 'linear',
21+
iterations: Infinity,
22+
triggerType: 'once',
23+
keyframeEffect: {
24+
name: 'morph-layer-0',
25+
keyframes: [
26+
{ offset: 0, d: 'path("M 0,0 L 100,100 L 0,100 Z")' },
27+
{ offset: 0.25, d: 'path("M 50,25 L 100,100 L 0,100 Z")' },
28+
{ offset: 0.5, d: 'path("M 100,0 L 100,100 L 0,100 Z")' },
29+
{ offset: 0.75, d: 'path("M 50,25 L 100,100 L 0,100 Z")' },
30+
{ offset: 1, d: 'path("M 0,0 L 100,100 L 0,100 Z")' },
31+
],
32+
},
33+
},
34+
},
35+
};
36+
37+
export const SvgPathMorphDemo = () => {
38+
useInteractInstance(svgPathMorphConfig);
39+
40+
return (
41+
<section className="panel svg-path-morph-demo">
42+
<p className="scroll-label">SVG path morph</p>
43+
<div className="svg-path-morph-header">
44+
<div>
45+
<h3>Static SVG, Interact keyframes</h3>
46+
<p>
47+
The static path starts animating when this panel enters view. Interact targets the
48+
nested path and passes the supplied keyframes to the Web Animations API.
49+
</p>
50+
</div>
51+
<span className="svg-path-morph-status">Keyframes</span>
52+
</div>
53+
54+
<Interaction tagName="div" interactKey="svg-path-morph" className="svg-path-morph-stage">
55+
<svg
56+
aria-label=""
57+
role="presentation"
58+
aria-hidden="true"
59+
preserveAspectRatio="xMidYMid meet"
60+
data-type="color"
61+
viewBox="0 0 100 100"
62+
xmlnsXlink="http://www.w3.org/1999/xlink"
63+
xmlns="http://www.w3.org/2000/svg"
64+
data-bbox="0 0 100 100"
65+
>
66+
<g className="_svgImageSource_1lilf_7" data-type="color">
67+
<g>
68+
<path d="m0 0 100 100H0Z" fill="#c0ffee" />
69+
</g>
70+
</g>
71+
</svg>
72+
</Interaction>
73+
74+
<p className="svg-path-morph-note">
75+
The initial <code>d</code> attribute is static. The effect selector targets the nested
76+
<code>path:nth-child(1)</code>, so Interact applies the keyframes only after
77+
<code>viewEnter</code>.
78+
</p>
79+
</section>
80+
);
81+
};

apps/demo/src/styles.css

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,66 @@ body {
458458
color: #a5b4fc;
459459
}
460460

461+
/* SVG path morph demo */
462+
.svg-path-morph-demo {
463+
margin-top: 2rem;
464+
}
465+
466+
.svg-path-morph-header {
467+
display: flex;
468+
align-items: start;
469+
justify-content: space-between;
470+
gap: 1rem;
471+
margin-bottom: 1.25rem;
472+
}
473+
474+
.svg-path-morph-header h3 {
475+
margin: 0 0 0.5rem;
476+
font-size: 1.5rem;
477+
}
478+
479+
.svg-path-morph-header p,
480+
.svg-path-morph-note {
481+
margin: 0;
482+
color: rgb(148 163 184 / 0.9);
483+
line-height: 1.6;
484+
}
485+
486+
.svg-path-morph-status {
487+
flex: none;
488+
border: 1px solid rgb(192 255 238 / 0.35);
489+
border-radius: 999px;
490+
padding: 0.35rem 0.65rem;
491+
color: #c0ffee;
492+
font-size: 0.75rem;
493+
letter-spacing: 0.08em;
494+
text-transform: uppercase;
495+
}
496+
497+
.svg-path-morph-stage {
498+
display: grid;
499+
min-height: 300px;
500+
place-items: center;
501+
overflow: hidden;
502+
border: 1px solid rgb(192 255 238 / 0.15);
503+
border-radius: 1rem;
504+
background: radial-gradient(circle, rgb(192 255 238 / 0.14), transparent 65%);
505+
}
506+
507+
.svg-path-morph-stage svg {
508+
width: min(240px, 65vw);
509+
height: auto;
510+
filter: drop-shadow(0 18px 28px rgb(192 255 238 / 0.25));
511+
}
512+
513+
.svg-path-morph-note {
514+
margin-top: 1.25rem;
515+
}
516+
517+
.svg-path-morph-note code {
518+
color: #c4b5fd;
519+
}
520+
461521
/* Responsive Demo */
462522
.responsive-demo-panel {
463523
margin-top: 20px;

apps/demo/src/web/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SequencePlayground } from './components/SequencePlayground';
77
import { SequenceEntranceDemo } from './components/SequenceEntranceDemo';
88
import { SequenceClickDemo } from './components/SequenceClickDemo';
99
import { SequenceEasingComparison } from './components/SequenceEasingComparison';
10+
import { SvgPathMorphDemo } from './components/SvgPathMorphDemo';
1011

1112
const heroCopy = [
1213
'Tune triggers, easings, and delays in real time.',
@@ -39,6 +40,7 @@ function App() {
3940
</header>
4041

4142
<Playground />
43+
<SvgPathMorphDemo />
4244
<SelectorConditionDemo />
4345
<div className="scroll-showcase-wrapper">
4446
<ResponsiveDemo />
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { type InteractConfig } from '@wix/interact/web';
2+
import { useInteractInstance } from '../hooks/useInteractInstance';
3+
4+
const svgPathMorphConfig: InteractConfig = {
5+
interactions: [
6+
{
7+
key: 'svg-path-morph',
8+
trigger: 'viewEnter',
9+
effects: [
10+
{
11+
effectId: 'svg-path-morph-effect',
12+
selector: 'path:nth-child(1)',
13+
},
14+
],
15+
},
16+
],
17+
effects: {
18+
'svg-path-morph-effect': {
19+
duration: 2000,
20+
easing: 'linear',
21+
iterations: Infinity,
22+
triggerType: 'once',
23+
keyframeEffect: {
24+
name: 'morph-layer-0',
25+
keyframes: [
26+
{ offset: 0, d: 'path("M 0,0 L 100,100 L 0,100 Z")' },
27+
{ offset: 0.25, d: 'path("M 50,25 L 100,100 L 0,100 Z")' },
28+
{ offset: 0.5, d: 'path("M 100,0 L 100,100 L 0,100 Z")' },
29+
{ offset: 0.75, d: 'path("M 50,25 L 100,100 L 0,100 Z")' },
30+
{ offset: 1, d: 'path("M 0,0 L 100,100 L 0,100 Z")' },
31+
],
32+
},
33+
},
34+
},
35+
};
36+
37+
export const SvgPathMorphDemo = () => {
38+
useInteractInstance(svgPathMorphConfig);
39+
40+
return (
41+
<section className="panel svg-path-morph-demo">
42+
<p className="scroll-label">SVG path morph</p>
43+
<div className="svg-path-morph-header">
44+
<div>
45+
<h3>Static SVG, Interact keyframes</h3>
46+
<p>
47+
The static path starts animating when this panel enters view. Interact targets the
48+
nested path and passes the supplied keyframes to the Web Animations API.
49+
</p>
50+
</div>
51+
<span className="svg-path-morph-status">Keyframes</span>
52+
</div>
53+
54+
<interact-element data-interact-key="svg-path-morph">
55+
<div className="svg-path-morph-stage">
56+
<svg
57+
aria-label=""
58+
role="presentation"
59+
aria-hidden="true"
60+
preserveAspectRatio="xMidYMid meet"
61+
data-type="color"
62+
viewBox="0 0 100 100"
63+
xmlnsXlink="http://www.w3.org/1999/xlink"
64+
xmlns="http://www.w3.org/2000/svg"
65+
data-bbox="0 0 100 100"
66+
>
67+
<g className="_svgImageSource_1lilf_7" data-type="color">
68+
<g>
69+
<path d="m0 0 100 100H0Z" fill="#c0ffee" />
70+
</g>
71+
</g>
72+
</svg>
73+
</div>
74+
</interact-element>
75+
76+
<p className="svg-path-morph-note">
77+
The initial <code>d</code> attribute is static. The effect selector targets the nested
78+
<code>path:nth-child(1)</code>, so Interact applies the keyframes only after
79+
<code>viewEnter</code>.
80+
</p>
81+
</section>
82+
);
83+
};

packages/motion/src/api/webAnimations.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import fastdom from 'fastdom';
2626
function getWebAnimationEffect(
2727
preset: AnimationEffectAPI<any> | WebAnimationEffectFactory<any> | null,
2828
animation: AnimationOptions,
29-
target: HTMLElement | string | null,
29+
target: Element | string | null,
3030
trigger?: Partial<TriggerVariant>,
3131
options?: Record<string, any>,
3232
): AnimationData[] | MouseAnimationFactory {
@@ -58,13 +58,13 @@ function getWebAnimationEffect(
5858
}
5959

6060
function getWebAnimation(
61-
target: HTMLElement | string | null,
61+
target: Element | string | null,
6262
animationOptions: AnimationOptions,
6363
trigger?: Partial<TriggerVariant> & { element?: HTMLElement },
6464
options?: Record<string, any>,
6565
ownerDocument?: Document,
6666
): AnimationGroup | MouseAnimationInstance | null {
67-
const element = target instanceof HTMLElement ? target : getElement(target, ownerDocument);
67+
const element = typeof target === 'string' ? getElement(target, ownerDocument) : target;
6868

6969
if (trigger?.trigger === 'pointer-move' && !animationOptions.keyframeEffect) {
7070
let effectOptions = animationOptions;

packages/motion/test/motion.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,32 @@ describe('motion.ts', () => {
494494
(AnimationGroup as Mock).mockRestore();
495495
});
496496

497+
test('should handle SVG path targets', async () => {
498+
const animationOptions: AnimationOptions = {
499+
duration: 2000,
500+
keyframeEffect: {
501+
name: 'morph-layer-0',
502+
keyframes: [
503+
{ d: 'path("M 0,0 L 100,100 L 0,100 Z")' },
504+
{ d: 'path("M 100,0 L 100,100 L 0,100 Z")' },
505+
],
506+
},
507+
};
508+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
509+
510+
const { AnimationGroup } = await import('../src/AnimationGroup');
511+
(AnimationGroup as Mock).mockImplementation(function () {
512+
return mockAnimationGroup;
513+
});
514+
515+
const result = getWebAnimation(path, animationOptions);
516+
517+
expect(result).toBe(mockAnimationGroup);
518+
expect(KeyframeEffect).toHaveBeenCalledWith(path, expect.any(Array), expect.any(Object));
519+
520+
(AnimationGroup as Mock).mockRestore();
521+
});
522+
497523
test('should handle custom mouse effects', async () => {
498524
const animationOptions: AnimationOptions = {
499525
customEffect: {

0 commit comments

Comments
 (0)