|
| 1 | +import { Code } from '@motion-canvas/2d'; |
| 2 | +import { BBox, all, DEFAULT, ThreadGenerator } from '@motion-canvas/core'; |
| 3 | + |
| 4 | +export function* centerOn( |
| 5 | + codeRef: Code, |
| 6 | + selectionInput: any, |
| 7 | + duration: number, |
| 8 | + targetFontSize?: number, |
| 9 | +): ThreadGenerator { |
| 10 | + // We only care about the bounding box of the new selection |
| 11 | + let bboxes: BBox[] = []; |
| 12 | + if (selectionInput !== DEFAULT) { |
| 13 | + // getSelectionBBox computes the bounds for the given selection using the CURRENT layout state. |
| 14 | + // We do NOT need to set the selection signal to measure it! |
| 15 | + bboxes = codeRef.getSelectionBBox(selectionInput); |
| 16 | + } |
| 17 | + |
| 18 | + if (bboxes.length === 0) { |
| 19 | + const fallbackAnimations: ThreadGenerator[] = [ |
| 20 | + codeRef.selection(selectionInput, duration), |
| 21 | + codeRef.y(0, duration) |
| 22 | + ]; |
| 23 | + if (targetFontSize !== undefined) { |
| 24 | + fallbackAnimations.push(codeRef.fontSize(targetFontSize, duration)); |
| 25 | + } |
| 26 | + yield* all(...fallbackAnimations); |
| 27 | + return; |
| 28 | + } |
| 29 | + |
| 30 | + let minY = Infinity; |
| 31 | + let maxY = -Infinity; |
| 32 | + |
| 33 | + for (const bbox of bboxes) { |
| 34 | + if (bbox.top !== undefined && !isNaN(bbox.top)) minY = Math.min(minY, bbox.top); |
| 35 | + if (bbox.bottom !== undefined && !isNaN(bbox.bottom)) maxY = Math.max(maxY, bbox.bottom); |
| 36 | + } |
| 37 | + |
| 38 | + let centerY = (minY + maxY) / 2; |
| 39 | + if (!isFinite(centerY) || isNaN(centerY)) { |
| 40 | + centerY = 0; |
| 41 | + } |
| 42 | + |
| 43 | + // Scale the calculated target offset based on how the font size will change. |
| 44 | + // This perfectly centers it without needing to pollute the animation timeline |
| 45 | + // with synchronous layout mutations. |
| 46 | + if (targetFontSize !== undefined) { |
| 47 | + const currentFontSize = codeRef.fontSize(); |
| 48 | + if (currentFontSize > 0) { |
| 49 | + centerY = centerY * (targetFontSize / currentFontSize); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + const animations: ThreadGenerator[] = [ |
| 54 | + codeRef.selection(selectionInput, duration), |
| 55 | + codeRef.y(-centerY, duration) |
| 56 | + ]; |
| 57 | + |
| 58 | + if (targetFontSize !== undefined) { |
| 59 | + animations.push(codeRef.fontSize(targetFontSize, duration)); |
| 60 | + } |
| 61 | + |
| 62 | + yield* all(...animations); |
| 63 | +} |
0 commit comments