返回 html-video
captions.html
1 <template id="captions-template">
2 <div
3 data-composition-id="captions"
4 data-width="1920"
5 data-height="1080"
6 data-duration="__VIDEO_DURATION__"
7 >
8 <div id="captions-container"></div>
9
10 <style>
11 [data-composition-id="captions"] {
12 width: 1920px;
13 height: 1080px;
14 pointer-events: none;
15 }
16
17 [data-composition-id="captions"] #captions-container {
18 position: absolute;
19 bottom: 100px; /* Position in bottom 1/3 */
20 left: 50%;
21 transform: translateX(-50%);
22 display: flex;
23 justify-content: center;
24 align-items: center;
25 width: 100%;
26 height: 150px; /* Fixed height for the caption box */
27 }
28
29 .caption-box {
30 position: absolute; /* Stacked in the same place */
31 background-color: #ff2d8a;
32 color: white;
33 padding: 20px 40px;
34 border-radius: 16px;
35 font-family: "Nunito", sans-serif;
36 font-weight: 900; /* Nunito Black */
37 font-size: 64px;
38 text-align: center;
39 display: inline-block;
40 white-space: nowrap;
41 opacity: 0;
42 visibility: hidden;
43 transform: scale(0);
44 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
45 }
46 </style>
47
48 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
49 <script>
50 (function () {
51 const TRANSCRIPT = [
52 { text: "We", start: 0.119, end: 0.259 },
53 { text: "asked", start: 0.319, end: 0.479 },
54 { text: "what", start: 0.519, end: 0.659 },
55 { text: "you", start: 0.699, end: 0.819 },
56 { text: "needed.", start: 0.859, end: 1.819 },
57 { text: "Forty-seven", start: 1.839, end: 2.299 },
58 { text: "percent", start: 2.399, end: 2.679 },
59 { text: "of", start: 2.7, end: 2.799 },
60 { text: "you", start: 2.839, end: 2.939 },
61 { text: "said", start: 3.039, end: 3.179 },
62 { text: "motion", start: 3.24, end: 3.559 },
63 { text: "graphics.", start: 3.579, end: 4.639 },
64 { text: "Sixty-two", start: 4.659, end: 5.179 },
65 { text: "percent", start: 5.299, end: 5.759 },
66 { text: "said", start: 5.859, end: 5.98 },
67 { text: "static", start: 6.099, end: 6.399 },
68 { text: "content", start: 6.46, end: 6.879 },
69 { text: "was", start: 6.92, end: 7.079 },
70 { text: "costing", start: 7.099, end: 7.48 },
71 { text: "you", start: 7.5, end: 7.579 },
72 { text: "attention,", start: 7.679, end: 8.659 },
73 { text: "and", start: 8.699, end: 8.86 },
74 { text: "three", start: 8.88, end: 9.06 },
75 { text: "out", start: 9.079, end: 9.18 },
76 { text: "of", start: 9.199, end: 9.34 },
77 { text: "four", start: 9.38, end: 9.799 },
78 { text: "said", start: 9.84, end: 10.0 },
79 { text: "you", start: 10.019, end: 10.159 },
80 { text: "know", start: 10.179, end: 10.36 },
81 { text: "the", start: 10.38, end: 10.479 },
82 { text: "look", start: 10.52, end: 10.699 },
83 { text: "you", start: 10.739, end: 10.859 },
84 { text: "want", start: 10.98, end: 11.34 },
85 { text: "but", start: 11.359, end: 11.52 },
86 { text: "don't", start: 11.56, end: 11.779 },
87 { text: "have", start: 11.819, end: 11.94 },
88 { text: "the", start: 11.96, end: 12.06 },
89 { text: "editing", start: 12.079, end: 12.4 },
90 { text: "skills", start: 12.52, end: 12.86 },
91 { text: "to", start: 12.88, end: 13.0 },
92 { text: "get", start: 13.019, end: 13.18 },
93 { text: "there.", start: 13.22, end: 14.22 },
94 { text: "So", start: 14.239, end: 14.399 },
95 { text: "we", start: 14.42, end: 14.52 },
96 { text: "built", start: 14.619, end: 14.88 },
97 { text: "Hyperframes", start: 15.079, end: 15.42 },
98 ];
99 const container = document.getElementById("captions-container");
100 const tl = gsap.timeline({ paused: true });
101
102 // Group transcript into lines (max 5 words)
103 const lines = [];
104 for (let i = 0; i < TRANSCRIPT.length; i += 5) {
105 lines.push(TRANSCRIPT.slice(i, i + 5));
106 }
107
108 lines.forEach((lineWords, index) => {
109 const lineText = lineWords.map((w) => w.text).join(" ");
110 const startTime = lineWords[0].start;
111 const endTime = lineWords[lineWords.length - 1].end;
112
113 // Create element
114 const el = document.createElement("div");
115 el.className = "caption-box";
116 el.textContent = lineText;
117 el.id = `caption-line-${index}`;
118 container.appendChild(el);
119
120 // Animation: Pop in with scale-up bounce (0% to 110% to 100%)
121 tl.to(
122 el,
123 {
124 autoAlpha: 1,
125 scale: 1.1,
126 duration: 0.2,
127 ease: "power2.out",
128 },
129 startTime,
130 );
131
132 tl.to(
133 el,
134 {
135 scale: 1,
136 duration: 0.1,
137 ease: "power2.inOut",
138 },
139 startTime + 0.2,
140 );
141
142 // Stay visible until next line or end of its duration
143 // We hide it when the next line starts or at its own end time
144 const hideTime =
145 index < lines.length - 1 ? Math.min(endTime, lines[index + 1][0].start) : endTime;
146
147 tl.to(
148 el,
149 {
150 autoAlpha: 0,
151 scale: 0,
152 duration: 0.15,
153 ease: "power2.in",
154 },
155 hideTime,
156 );
157 });
158
159 window.__timelines = window.__timelines || {};
160 window.__timelines["captions"] = tl;
161 })();
162 </script>
163 </div>
164 </template>
165
165 lines HTML