SHOW:
|
|
- or go back to the newest paste.
1 | import { Group } from 'konva/lib/Group'; | |
2 | import { Line } from 'konva/lib/shapes/Line'; | |
3 | import { Rect } from 'konva/lib/shapes/Rect'; | |
4 | ||
5 | import { Project } from '@motion-canvas/core/lib'; | |
6 | import { all, delay, sequence, waitFor } from '@motion-canvas/core/lib/flow'; | |
7 | import type { Scene } from '@motion-canvas/core/lib/Scene'; | |
8 | import { ThreadGenerator } from '@motion-canvas/core/lib/threading'; | |
9 | import { | |
10 | easeInBackGenerator, | |
11 | linear, | |
12 | tween, | |
13 | } from '@motion-canvas/core/lib/tweening'; | |
14 | ||
15 | export default function* example(scene: Scene): ThreadGenerator { | |
16 | yield* scene.transition(); | |
17 | ||
18 | let shadowAngle = Math.PI / 2; | |
19 | ||
20 | const tweenShadow = (rect: Rect, shadow: Line) => () => { | |
21 | const size = rect.size(); | |
22 | ||
23 | let corners = [ | |
24 | { x: 0, y: 0 }, // top left | |
25 | { x: size.width, y: 0 }, // top right | |
26 | { x: size.width, y: size.height }, // bottom right | |
27 | { x: 0, y: size.height }, // bottom left | |
28 | ]; | |
29 | const cornerRadius = rect.cornerRadius() as number; | |
30 | const innerCorners = corners.map(({ x, y }) => ({ | |
31 | x: x === 0 ? cornerRadius : x - cornerRadius, | |
32 | y: y === 0 ? cornerRadius : y - cornerRadius, | |
33 | })); | |
34 | ||
35 | for (let i = 0; i < 4; i++) { | |
36 | corners[i] = rect.getAbsoluteTransform().point(corners[i]); | |
37 | innerCorners[i] = rect.getAbsoluteTransform().point(innerCorners[i]); | |
38 | } | |
39 | ||
40 | const getScore = ({ x, y }: { x: number; y: number }, topRight: boolean) => | |
41 | (x * Math.cos(shadowAngle + Math.PI / 2) + | |
42 | y * Math.sin(shadowAngle + Math.PI / 2)) * | |
43 | (topRight ? -1 : 1); | |
44 | ||
45 | const bottomLeft = corners.reduce((acc, cur) => | |
46 | getScore(cur, false) > getScore(acc, false) ? cur : acc, | |
47 | ); | |
48 | const topRight = corners.reduce((acc, cur) => | |
49 | getScore(cur, true) > getScore(acc, true) ? cur : acc, | |
50 | ); | |
51 | ||
52 | const bottomLeftInner = innerCorners[corners.indexOf(bottomLeft)]; | |
53 | const topRightInner = innerCorners[corners.indexOf(topRight)]; | |
54 | ||
55 | const getRoundPointPos = ( | |
56 | innerPoint: { x: number; y: number }, | |
57 | outerPoint: { x: number; y: number }, | |
58 | dim: 'x' | 'y', | |
59 | ) => | |
60 | innerPoint[dim] + | |
61 | Math[dim === 'x' ? 'cos' : 'sin'](shadowAngle + Math.PI / 2) * | |
62 | (outerPoint === topRight ? -1 : 1) * | |
63 | cornerRadius * | |
64 | rect.getAbsoluteScale()[dim]; | |
65 | ||
66 | const absolutePoints = [ | |
67 | [ | |
68 | getRoundPointPos(bottomLeftInner, bottomLeft, 'x'), | |
69 | getRoundPointPos(bottomLeftInner, bottomLeft, 'y'), | |
70 | ], | |
71 | [ | |
72 | getRoundPointPos(topRightInner, topRight, 'x'), | |
73 | getRoundPointPos(topRightInner, topRight, 'y'), | |
74 | ], | |
75 | ]; | |
76 | ||
77 | const points = absolutePoints | |
78 | .map(([x, y]) => | |
79 | shadow.getAbsoluteTransform().copy().invert().point({ x, y }), | |
80 | ) | |
81 | .map(({ x, y }) => [x, y]); | |
82 | ||
83 | shadow.points( | |
84 | points | |
85 | .concat( | |
86 | points | |
87 | .map(([x, y]) => [ | |
88 | x + 2500 * Math.cos(shadowAngle), | |
89 | y + 2500 * Math.sin(shadowAngle), | |
90 | ]) | |
91 | .reverse(), | |
92 | ) | |
93 | .flat(), | |
94 | ); | |
95 | }; | |
96 | ||
97 | const COUNT = 5 ** 2; | |
98 | const SIZE = 50; | |
99 | const HALF_SIZE = SIZE / 2; | |
100 | const rects = [...Array(COUNT)].map( | |
101 | (_, i) => | |
102 | new Rect({ | |
103 | fill: '#fff', | |
104 | // fill: 'rgba(255,255,255,0.5)', | |
105 | width: SIZE, | |
106 | height: SIZE, | |
107 | x: (i - COUNT / 2 + 0.5) * SIZE, | |
108 | y: -600, | |
109 | }), | |
110 | ); | |
111 | ||
112 | const rectShadows = [...Array(COUNT)].map((_, i) => { | |
113 | const points = [ | |
114 | [-HALF_SIZE, HALF_SIZE], | |
115 | [HALF_SIZE, -HALF_SIZE], | |
116 | ]; | |
117 | const LENGTH = 700; | |
118 | ||
119 | return new Line({ | |
120 | fill: '#b0752e', | |
121 | closed: true, | |
122 | points: points.flat().concat( | |
123 | points | |
124 | .map(([x, y]) => [x + LENGTH, y + LENGTH]) | |
125 | .reverse() | |
126 | .flat(), | |
127 | ), | |
128 | }); | |
129 | }); | |
130 | scene.add(...rectShadows); | |
131 | scene.add(...rects); | |
132 | ||
133 | yield tween(COUNT / 2.5, (value) => { | |
134 | shadowAngle = linear(value, (Math.PI * 1) / 4, (Math.PI * 7) / 4); | |
135 | for (let i = 0; i < rects.length; i++) { | |
136 | tweenShadow(rects[i], rectShadows[i])(); | |
137 | } | |
138 | }); | |
139 | ||
140 | const moveFromMiddle = (i: number, time: number) => | |
141 | Math.abs(i - rects.length / 2) * time; | |
142 | const moveFromEdges = (i: number, time: number) => | |
143 | Math.min(i, rects.length - i - 1) * time; | |
144 | ||
145 | yield* sequence( | |
146 | 0.01, | |
147 | ...rects.map((rect, i) => | |
148 | all(rect.y(0, 0.75), tween(0.75, tweenShadow(rect, rectShadows[i]))), | |
149 | ), | |
150 | ); | |
151 | yield* all( | |
152 | ...rects.map((rect, i) => { | |
153 | const newX = (i - COUNT / 2 + 0.5) * SIZE * 1.5; | |
154 | return delay( | |
155 | moveFromEdges(i, 0.05), | |
156 | all( | |
157 | rect.x(newX, 0.75), | |
158 | tween(0.75, tweenShadow(rect, rectShadows[i])), | |
159 | rect.cornerRadius(SIZE / 10, 0.75), | |
160 | ), | |
161 | ); | |
162 | }), | |
163 | ); | |
164 | ||
165 | const COUNT_SQRT = Math.sqrt(COUNT); | |
166 | yield* all( | |
167 | ...rects.map((rect, i) => { | |
168 | const x = i % COUNT_SQRT; | |
169 | const y = Math.floor(i / COUNT_SQRT); | |
170 | const newX = (x - COUNT_SQRT / 2 + 0.5) * SIZE * 1.5; | |
171 | const newY = (y - COUNT_SQRT / 2 + 0.5) * SIZE * 1.5; | |
172 | return delay( | |
173 | moveFromMiddle(i, 0.1) - 0.3, | |
174 | all( | |
175 | rect.position({ x: newX, y: newY }, 1), | |
176 | tween(1, tweenShadow(rect, rectShadows[i])), | |
177 | ), | |
178 | ); | |
179 | }), | |
180 | ); | |
181 | ||
182 | yield* all( | |
183 | ...rects.map((rect, i) => | |
184 | delay( | |
185 | 0.01 * i, | |
186 | all( | |
187 | rect.position({ x: 0, y: 0 }, 1), | |
188 | tween(1, tweenShadow(rect, rectShadows[i])), | |
189 | ), | |
190 | ), | |
191 | ), | |
192 | ); | |
193 | rects.slice(1).forEach((rect) => rect.destroy()); | |
194 | rectShadows.slice(1).forEach((rect) => rect.destroy()); | |
195 | const rect = rects[0]; | |
196 | const rectShadow = rectShadows[0]; | |
197 | ||
198 | const group = new Group(); | |
199 | group.add(rect); | |
200 | scene.add(group); | |
201 | ||
202 | const TIME = 0.75; | |
203 | ||
204 | yield* all( | |
205 | group.position({ x: 200, y: 200 }, TIME), | |
206 | group.rotation(45, TIME), | |
207 | group.scale({ x: 3, y: 3 }, TIME), | |
208 | tween(TIME, tweenShadow(rect, rectShadow)), | |
209 | ); | |
210 | yield* all( | |
211 | group.position({ x: -200, y: -100 }, TIME), | |
212 | group.rotation(-210, TIME), | |
213 | group.scale({ x: 5, y: 5 }, TIME), | |
214 | tween(TIME, tweenShadow(rect, rectShadow)), | |
215 | ); | |
216 | yield* all( | |
217 | group.position({ x: 0, y: 0 }, TIME), | |
218 | group.rotation(-90, TIME), | |
219 | group.scale({ x: 2.5, y: 2.5 }, TIME), | |
220 | tween(TIME, tweenShadow(rect, rectShadow)), | |
221 | ); | |
222 | yield* all( | |
223 | group.scale({ x: 0, y: 0 }, 0.75, easeInBackGenerator(5)), | |
224 | tween(TIME, tweenShadow(rect, rectShadow)), | |
225 | ); | |
226 | ||
227 | yield* waitFor(1.0); | |
228 | scene.canFinish(); | |
229 | } | |
230 |