text-animations-word-highlight.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { loadFont } from "@remotion/google-fonts/Inter";
  2. import React from "react";
  3. import {
  4. AbsoluteFill,
  5. spring,
  6. useCurrentFrame,
  7. useVideoConfig,
  8. } from "remotion";
  9. /*
  10. * Highlight a word in a sentence with a spring-animated wipe effect.
  11. */
  12. // Ideal composition size: 1280x720
  13. const COLOR_BG = "#ffffff";
  14. const COLOR_TEXT = "#000000";
  15. const COLOR_HIGHLIGHT = "#A7C7E7";
  16. const FULL_TEXT = "This is Remotion.";
  17. const HIGHLIGHT_WORD = "Remotion";
  18. const FONT_SIZE = 72;
  19. const FONT_WEIGHT = 700;
  20. const HIGHLIGHT_START_FRAME = 30;
  21. const HIGHLIGHT_WIPE_DURATION = 18;
  22. const { fontFamily } = loadFont();
  23. const Highlight: React.FC<{
  24. word: string;
  25. color: string;
  26. delay: number;
  27. durationInFrames: number;
  28. }> = ({ word, color, delay, durationInFrames }) => {
  29. const frame = useCurrentFrame();
  30. const { fps } = useVideoConfig();
  31. const highlightProgress = spring({
  32. fps,
  33. frame,
  34. config: { damping: 200 },
  35. delay,
  36. durationInFrames,
  37. });
  38. const scaleX = Math.max(0, Math.min(1, highlightProgress));
  39. return (
  40. <span style={{ position: "relative", display: "inline-block" }}>
  41. <span
  42. style={{
  43. position: "absolute",
  44. left: 0,
  45. right: 0,
  46. top: "50%",
  47. height: "1.05em",
  48. transform: `translateY(-50%) scaleX(${scaleX})`,
  49. transformOrigin: "left center",
  50. backgroundColor: color,
  51. borderRadius: "0.18em",
  52. zIndex: 0,
  53. }}
  54. />
  55. <span style={{ position: "relative", zIndex: 1 }}>{word}</span>
  56. </span>
  57. );
  58. };
  59. export const MyAnimation = () => {
  60. const highlightIndex = FULL_TEXT.indexOf(HIGHLIGHT_WORD);
  61. const hasHighlight = highlightIndex >= 0;
  62. const preText = hasHighlight ? FULL_TEXT.slice(0, highlightIndex) : FULL_TEXT;
  63. const postText = hasHighlight
  64. ? FULL_TEXT.slice(highlightIndex + HIGHLIGHT_WORD.length)
  65. : "";
  66. return (
  67. <AbsoluteFill
  68. style={{
  69. backgroundColor: COLOR_BG,
  70. alignItems: "center",
  71. justifyContent: "center",
  72. fontFamily,
  73. }}
  74. >
  75. <div
  76. style={{
  77. color: COLOR_TEXT,
  78. fontSize: FONT_SIZE,
  79. fontWeight: FONT_WEIGHT,
  80. }}
  81. >
  82. {hasHighlight ? (
  83. <>
  84. <span>{preText}</span>
  85. <Highlight
  86. word={HIGHLIGHT_WORD}
  87. color={COLOR_HIGHLIGHT}
  88. delay={HIGHLIGHT_START_FRAME}
  89. durationInFrames={HIGHLIGHT_WIPE_DURATION}
  90. />
  91. <span>{postText}</span>
  92. </>
  93. ) : (
  94. <span>{FULL_TEXT}</span>
  95. )}
  96. </div>
  97. </AbsoluteFill>
  98. );
  99. };