Advertisement
Guest User

Motion Canvas Long Shadow Thing

a guest
Jun 16th, 2022
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement