1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+
4+ < head >
5+ < meta charset ="UTF-8 ">
6+ < meta http-equiv ="X-UA-Compatible " content ="IE=edge ">
7+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
8+ < link rel ="stylesheet " href ="./assets/global.css ">
9+
10+ < style >
11+ @font-face {
12+ font-family : "SourceHanSansCN-Light" ;
13+ src : url ("./assets/SourceHanSansCN-Light.otf" );
14+ }
15+
16+ .frame-container {
17+ display : flex;
18+ align-items : center;
19+ justify-content : center;
20+ height : 100vh ;
21+ position : relative;
22+ width : 100% ;
23+ font-family : "SourceHanSansCN-Light" ;
24+ }
25+
26+ .video-contianer {
27+ /* background-color: ; */
28+ height : 100vh ;
29+ width : 100% ;
30+ filter : grayscale ();
31+ display : flex;
32+ align-items : center;
33+ overflow : hidden;
34+ }
35+
36+ .video-contianer .light {
37+ filter : none;
38+ }
39+
40+ .video-contianer video {
41+ width : 100% ;
42+ height : 100% ;
43+ object-fit : cover;
44+ position : absolute;
45+ }
46+
47+ .text-box {
48+ position : absolute;
49+ color : lightblue;
50+ font-size : 80px ;
51+ text-shadow : 2px 0px # 000 ;
52+ text-align : center;
53+ font-weight : bold;
54+ }
55+ </ style >
56+ </ head >
57+
58+ < body >
59+ < div class ="frame-container ">
60+ < div class ="video-contianer ">
61+ < video src ="./assets/effects-video/yskd.mp4 "> </ video >
62+ </ div >
63+ < div class ="text-box ">
64+ < div class ="title "> 蓝色</ div >
65+ < div class ="subtitle "> Blue</ div >
66+ </ div >
67+ </ div >
68+ < script type ="module ">
69+ // purplish red
70+ const audioConfig = {
71+ url : './assets/musics/only you.mp3' ,
72+ keyframes : [
73+ {
74+ keys : [ 0 , 65 ] ,
75+ color : 'orange' ,
76+ title : '橙色' ,
77+ subtitle : 'orange'
78+ } ,
79+ {
80+ keys : [ 94 , 122 ] ,
81+ color : 'green' ,
82+ title : '绿色' ,
83+ subtitle : 'green'
84+ } ,
85+ {
86+ keys : [ 149 , 177 ] ,
87+ color : 'pink' ,
88+ title : '粉红色' ,
89+ subtitle : 'pink'
90+ } ,
91+ {
92+ keys : [ 205 , 233 ] ,
93+ color : 'orange' ,
94+ title : '橙色' ,
95+ subtitle : 'orange'
96+ } ,
97+ {
98+ keys : [ 260 , 288 ] ,
99+ color : 'white' ,
100+ title : '白色' ,
101+ subtitle : 'white'
102+ } ,
103+ {
104+ keys : [ 316 , 343 ] ,
105+ color : 'skyblue' ,
106+ title : '天蓝色' ,
107+ subtitle : 'sky blue'
108+ } ,
109+ {
110+ keys : [ 371 , 398 ] ,
111+ color : 'orange' ,
112+ title : '橙色' ,
113+ subtitle : 'orange'
114+ } ,
115+ {
116+ keys : [ 426 , 454 ] ,
117+ color : '#ef726c' ,
118+ title : '紫红色' ,
119+ subtitle : 'purplish red'
120+ }
121+ ] ,
122+ fps : 30
123+ }
124+
125+ const audioContext = new ( window . AudioContext || window . webkitAudioContext ) ( ) ;
126+ const audioSource = audioContext . createBufferSource ( ) ;
127+ const videoDom = document . querySelector ( 'video' )
128+ let playing = false ;
129+ let startTime = 0 ;
130+ let lastKeyframeContentIndex = 0 ;
131+
132+ updateTextDomAndFilter ( ) ;
133+ audioSource . onended = ( ) => {
134+ playing = false ;
135+ location . reload ( )
136+ }
137+
138+ fetch ( './assets/effects-video/yskd.mp4' ) . then ( ( ) => {
139+
140+ fetch ( audioConfig . url )
141+ . then ( response => response . arrayBuffer ( ) )
142+ . then ( buffer => audioContext . decodeAudioData ( buffer ) )
143+ . then ( audioBuffer => {
144+ document . addEventListener ( 'click' , ( ) => {
145+ if ( playing ) return ;
146+
147+ audioSource . buffer = audioBuffer ;
148+ audioSource . connect ( audioContext . destination ) ;
149+ audioSource . start ( ) ;
150+ videoDom . play ( )
151+ playing = true
152+ startTime = + new Date ( )
153+ playVideo ( ) ;
154+ } )
155+ } )
156+ } )
157+
158+
159+
160+ /**
161+ * 时间戳转关键帧
162+ * @param {number } stamp
163+ */
164+ function timestampToKeyframe ( stamp ) {
165+ return ~ ~ ( stamp / 1000 * audioConfig . fps )
166+ }
167+
168+ /**
169+ * 查找关键帧内容
170+ * @param {number } key
171+ */
172+ function findKeyframeContentIndex ( key ) {
173+ return audioConfig . keyframes . findIndex ( ( keyframe ) => {
174+ if ( keyframe . keys [ 0 ] <= key && key <= keyframe . keys [ 1 ] ) {
175+ return true
176+ }
177+ return false
178+ } )
179+ }
180+
181+ /**
182+ * 刷新文字及过滤器
183+ */
184+ function updateTextDomAndFilter ( ) {
185+ let videoContianerDom = document . querySelector ( '.video-contianer' )
186+ let textBoxDom = document . querySelector ( '.text-box' )
187+ let titleDom = textBoxDom . querySelector ( '.title' )
188+ let subtitleDom = textBoxDom . querySelector ( '.subtitle' )
189+ if ( lastKeyframeContentIndex == - 1 ) {
190+ textBoxDom . hidden = true ;
191+ videoContianerDom . classList . add ( 'light' )
192+ return ;
193+ }
194+ let currentKeyframeContent = audioConfig . keyframes [ lastKeyframeContentIndex ]
195+ titleDom . textContent = currentKeyframeContent . title ;
196+ subtitleDom . textContent = currentKeyframeContent . subtitle ;
197+ textBoxDom . style . color = currentKeyframeContent . color ;
198+ textBoxDom . hidden = false ;
199+ videoContianerDom . classList . remove ( 'light' )
200+ }
201+
202+ function playVideo ( ) {
203+ if ( playing ) requestAnimationFrame ( playVideo ) ;
204+ let timestamp = + new Date ( ) - startTime
205+ let key = timestampToKeyframe ( timestamp )
206+ let currentKeyframeContentIndex = findKeyframeContentIndex ( key )
207+
208+ if ( currentKeyframeContentIndex != lastKeyframeContentIndex ) {
209+ lastKeyframeContentIndex = currentKeyframeContentIndex ;
210+ updateTextDomAndFilter ( )
211+ console . log ( '帧数' , key , lastKeyframeContentIndex ) ;
212+ }
213+ }
214+
215+
216+ </ script >
217+
218+ </ body >
219+
220+ </ html >
0 commit comments