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 |