// Javascript Animation Player // // Mark Hadfield (m.hadfield@niwa.cri.nz) April 2000 // // This JavaScript script animates a series of image files // in an HTML document. // // I modelled it on a similar script // used in the CDC map room (http://www.cdc.noaa.gov/~map/maproom/). // The original is marked (c)BASTaRT, Praha, Czech Republic, // 1996 (current contact Martin Holeeko <martin@etnetera.cz>) // and was modified by D. Watson and A. Earnhart (CIRA/CSU) // July 1997 and Greg Thompson (NCAR/RAP) December 1997. // // This version is heavily modified and shares very little code with // the original. Design considerations include: // * Script is self-contained and is invoked via the SRC property // of a SCRIPT element in the document header. // * The animator code is packaged in a JavaScript object, // allowing more than one animator object per HTML document. // * Animator user interface now written by the script (method // Write). // * UI is constructed with HTML elements (INPUT & SELECT) // rather than images. // * The document invoking the script needs only a few SCRIPT // elements in the body to modify the script's global variables, // generate the user interface and initiate playback. // * HTML code generated by the script is more-or-less compliant // with HTML 4.01 and XHTML 1.0. // * I tried to simplify the playback control code using logic // I developed for an IDL animator class. // * The script has been tested on Netscape 4.7 (also occasionally // with 4.08) and Internet Explorer 5.0. // Global variable used to assign each Animator object a unique ID. var animator_counter = 1; function Animator() { // Assign each animator object a unique ID number this.id = animator_counter++; // Specify default properties. These can be overridden // by <script> elements in the HTML document this.image_root = "image"; // Root for image URL this.image_ext = ".png"; // Extension for image URL this.n_frames = 10; // Number of frames this.image_width = 100; // Width of image area this.image_height = 100; // Height of image area this.mode = 1; // Mode (0=once, 1=repeat, 2=autoreverse) this.active = false; // false=stopped, true=playing this.direction = 1; // 0=backward, 1=forward this.delay = 70; // Interframe delay (ms) this.current = null; // Current frame number this.next = null; // Next frame number this.delay_first = 500; // Extra delay (ms) on first frame this.delay_last = 500; // Extra delay (ms) on last frame this.fcst_begin = 0; // Used in generating URLs for the image this.fcst_increment = 1; // files this.timeID = null; // Timeout identifier // Specify methods (function definitions below) this.FrameURL = FrameURL; // Generate the URL for each frame this.Load = Load; // Load images this.Write = Write; // Write HTML code for the animator and user interface this.Display = Display; // Playback methods this.Play = Play; this.PlayNext = PlayNext; this.Stop = Stop; this.StopStart = StopStart; this.Initialize = Initialize; // Call Load, Write and Display(1) // Define functions to be used as methods for animator object. // Load images into the animator object function Load() { // Generate an array of Image objects and set the src property for each one. // This causes the corresponding image files to be preloaded and allows for // rapid transfer of the image data into the animator's IMG element // when the time comes to display it. // Pre-allocate the array this.images = new Array(this.n_frames); // Load the images for (var i = 1; i <= this.n_frames; i++) { this.images[i-1] = new Image(); this.images[i-1].src = this.FrameURL(i); } } //Return URL for specified frame function FrameURL(num) { num = Number(num); if (this.fcst_begin > 0) var fcst_length = this.fcst_begin+(num-1)*this.fcst_increment; else var fcst_length = (num-1)*this.fcst_increment; if (fcst_length < 10) return this.image_root + "00" + fcst_length + this.image_ext; else if(fcst_length < 100) return this.image_root + "0" + fcst_length + this.image_ext; else return this.image_root + fcst_length + this.image_ext; } // Write HTML code for the animator into the current document function Write() { // The user interface and the animation images are displayed in a table. The original // user interface was constructed largely from graphical images but I have rewritten it // using HTML 4 elements like <SELECT> and <INPUT> with text labels. The advantage of this // is that these elements respond better to interaction with the mouse. // The user interface is embedded in a <FORM> element. A reference to the current animator object // is attached to this form element below. The event handlers access the animator via this reference. // Generate names for the <FORM> and <IMG> elements. These must be unique in the document. form_name = 'form_animator_'+this.id img_name = 'img_animator_'+this.id document.write('<form name="'+form_name+'">') document.write('<table border="7">'); document.write(' <tr>'); document.write(' <td bgcolor="#AAAAAA" width="180" align="center" valign="middle">'); document.write(' Loop Mode:<br>'); document.write(' <select name="select_mode">'); document.write(' <option>Once</option>'); document.write(' <option>Repeat</option>'); document.write(' <option>Autoreverse</option>'); document.write(' </select>'); document.write(' <br /> <hr width="70%" size="2" />'); document.write(' Play:<br>'); document.write(' <input type="button" name="button_bwrd" value="Bwrd" accesskey="B" />'); document.write(' <input type="button" name="button_stop" value="Stop" accesskey="S" />'); document.write(' <input type="button" name="button_fwrd" value="Fwrd" accesskey="F" />'); document.write(' <br /><hr width="70%" size="2" />'); document.write(' Step:'); document.write(' <br />'); document.write(' <input type="button" name="button_first" value="First" accesskey="I" />'); document.write(' <input type="button" name="button_prev" value="Prev" accesskey="P" />'); document.write(' <input type="button" name="button_next" value="Next" accesskey="N" />'); document.write(' <input type="button" name="button_last" value="Last" accesskey="L" />'); document.write(' <br />'); document.write(' <hr width="70%" size="2" />'); document.write(' <label>Delay (ms):'); document.write(' <input type="text" name="input_delay" size="3" />'); document.write(' </label>'); document.write(' <br />'); document.write(' <hr width="70%" size="2" />'); document.write(' <label>Extra delay (first):'); document.write(' <input type="text" name="input_delay_first" size="3" />'); document.write(' </label>'); document.write(' <label>Extra delay (last):'); document.write(' <input type="text" name="input_delay_last" size="3" />'); document.write(' </label>'); document.write(' <br />'); document.write(' <hr width="70%" size="2" />'); document.write(' <label>'); document.write(' Frame <input type="text" name="input_current" size="3" />'); document.write(' </label>'); document.write(' <label>'); document.write(' of <input type="text" name="input_n_frames" size="3" disabled="disabled" />'); document.write(' </label>'); document.write(' </td>'); document.write(' <td bgcolor="#ffffff" align="center" valign="middle">'); document.write(' <img name="'+img_name+'" border="0" alt="The animation" width="'+this.image_width+'" height="'+this.image_width+'" />'); document.write(' </td>'); document.write('</tr>'); document.write('</table>'); document.write('</form>'); // The above HTML code has created (amongst other things) a Form object // (the <FORM> element) and an Image object (the <IMG> element). We save // references to these in the current animator object and we save a // reference to the animator in the form for use in event handlers this.form = document.forms[form_name] this.img = document.images[img_name] this.form.animator = this // Attach event handlers to the user interface elements. The form is referred to // as "this.form" (remembering that "this" for the event handlers refers to the element that // originates the event). this.form.select_mode.onchange = new Function("this.form.animator.mode=this.selectedIndex;") this.form.button_bwrd.onclick = new Function("this.form.animator.direction=0; this.form.animator.Play();") this.form.button_stop.onclick = new Function("this.form.animator.Stop();") this.form.button_fwrd.onclick = new Function("this.form.animator.direction=1; this.form.animator.Play();") this.form.button_first.onclick = new Function("this.form.animator.Stop(); this.form.animator.Display(1);") this.form.button_prev.onclick = new Function("this.form.animator.Stop(); this.form.animator.Display(this.form.animator.current-1);") this.form.button_next.onclick = new Function("this.form.animator.Stop(); this.form.animator.Display(this.form.animator.current+1);") this.form.button_last.onclick = new Function("this.form.animator.Stop(); this.form.animator.Display(this.form.animator.n_frames);") this.form.input_delay.onfocus = new Function("this.form.animator.Stop(); this.select();") this.form.input_delay.onchange = new Function("this.form.animator.delay=parseInt(this.value);") this.form.input_delay_first.onfocus = new Function("this.form.animator.Stop(); this.select();") this.form.input_delay_first.onchange = new Function("this.form.animator.delay_first=parseInt(this.value);") this.form.input_delay_last.onfocus = new Function("this.form.animator.Stop(); this.select();") this.form.input_delay_last.onchange = new Function("this.form.animator.delay_last=parseInt(this.value);") this.form.input_current.onfocus = new Function("this.form.animator.Stop(); this.select();") this.form.input_current.onchange = new Function("this.form.animator.Display(parseInt(this.value));") this.form.input_n_frames.onchange = new Function("this.value=this.form.animator.n_frames;") // Set up an "onclick" event handler for the <IMG> element here (this works in Internet Explorer // but is ignored by Netscape). This element is not a child of the <FORM> so we locate the form in the // "forms" property of the document object (c.f. the PlayNext method). this.img.onclick=new Function("document.forms['"+this.form.name+"'].animator.StopStart();") // Set values of UI elements. The only parameters that cannot be set after initialisation // like this are width and height of the image area. this.form.select_mode.selectedIndex = this.mode; this.form.input_n_frames.value = this.n_frames; this.form.input_delay.value = this.delay; this.form.input_delay_first.value = this.delay_first; this.form.input_delay_last.value = this.delay_last; } // Display specified frame function Display(num) { num = Number(num); if (num < 1) num = 1; if (num > this.n_frames) num = this.n_frames; this.current = num; this.img.src = this.images[this.current-1].src; // Display image this.form.input_current.value = this.current; // Display frame number } // Initiate playback function Play() { this.Stop(); this.next = this.current; this.active = true; this.PlayNext(); } // Play next frame function PlayNext() { if (this.active) { this.current = this.next; this.next = this.current + (-1+2*this.direction) this.Display(this.current); delay_time = this.delay; if (this.current == 1) delay_time = delay_time + this.delay_first; if (this.current == this.n_frames) delay_time = delay_time + this.delay_last; // This is the string that is passed to setTimeout so that this method will // be called again. It is processed at global level (I think) so // the animator is identified via the form's animator property. command = "document.forms['"+this.form.name+"'].animator.PlayNext()"; switch (this.mode) { case 0: if ((this.next >= 1) && (this.next <= this.n_frames)) { this.timeID = window.setTimeout(command, delay_time); } else this.Stop(); break; case 1: switch(this.direction) { case 0: if (this.next < 1) this.next = this.n_frames; break; case 1: if (this.next > this.n_frames) this.next = 1; break; } this.timeID = window.setTimeout(command, delay_time); break; case 2: if (this.next < 1) { this.direction = 1; this.next = 2; } if (this.next > this.n_frames) { this.direction = 0; this.next = this.n_frames - 1; } this.timeID = window.setTimeout(command, delay_time); break; } } } // Stop the animation function Stop() { if (this.active) window.clearTimeout(this.timeID); this.active = false; } // Stop or restart the animation function StopStart() { if (this.active) this.Stop(); else this.Play(); } // Initialize function Initialize() { this.Load(); this.Write(); this.Display(1); } } // End of Animator constructor