Server Push Image Animation Script
Animation is a Perl CGI script that creates image animations using the server push technique with multipart/x-mixed-replace content type. This historic script predates the invention of GIF89a animated images and demonstrates the capabilities of unbuffered output and server push technology.
The Animation script allows you to implement simple image animations in your web pages using the server push technique. The script uses multipart/x-mixed-replace MIME type to continuously send image frames to the browser, creating an animation effect.
Server push (also called "NPH" - No Parsed Headers) works by keeping the HTTP connection open and sending multiple images in sequence. Each new image replaces the previous one in the browser, creating the illusion of animation. This was revolutionary technology before animated GIFs existed.
| File | Description |
|---|---|
nph-anim.pl |
Main Perl script that outputs the animation frames |
README |
Installation instructions and configuration guide |
The script sends its own HTTP headers directly, bypassing server header processing.
Uses multipart/x-mixed-replace to send multiple images in one response.
Images are sent in sequence with a delay between each frame.
Each new image replaces the previous one, creating animation effect.
Support for any number of animation frames. Simply add more images to the configuration.
Control animation speed by adjusting the delay between frames.
Set the number of animation loops or run continuously until the connection closes.
Works with GIF images by default. Can be modified to support JPEG with minor changes.
Works in Netscape Navigator and compatible browsers that support server push.
Uses No Parsed Headers for direct control over HTTP response, enabling unbuffered output.
Self-contained animated images that work everywhere without JavaScript.
Hardware-accelerated, GPU-powered animations.
After Effects animations exported as lightweight JSON.
The industry standard. Timeline-based sequencing, ScrollTrigger, morphing effects.
| Library | Size | Best For | Framework | License |
|---|---|---|---|---|
| GSAP | ~60KB | Complex timelines, scroll effects | Any | Free / Business |
| Motion | ~20KB (tree-shaking) | React apps, declarative | React | MIT |
| Anime.js | ~17KB | Simple to medium animations | Any | MIT |
| React Spring | ~25KB | Physics-based, natural feel | React | MIT |
| Lottie | ~35KB | After Effects exports | Any | MIT |
| CSS | 0KB | Simple transforms, transitions | Any | Native |
nph-anim.pl to your cgi-bin directory. The nph- prefix is required for the server to recognize it as a No Parsed Headers script.
#!/usr/bin/perl).
chmod 755 nph-anim.pl
<img> tag pointing to the CGI script in your HTML page.
#!/usr/bin/perl
# nph-anim.pl - Server Push Animation Script
# Note: Filename must start with "nph-" for No Parsed Headers
use strict;
use warnings;
# Configuration
my @images = (
'/images/anim/frame1.gif',
'/images/anim/frame2.gif',
'/images/anim/frame3.gif',
'/images/anim/frame4.gif',
);
my $delay = 0.5; # Seconds between frames
my $loops = 5; # Number of times to loop (0 = infinite)
# Disable buffering for real-time output
$| = 1;
# Send NPH HTTP headers
print "HTTP/1.0 200 OK\n";
print "Content-type: multipart/x-mixed-replace;boundary=BOUNDARY\n\n";
# Animation loop
my $count = 0;
while ($loops == 0 || $count < $loops) {
foreach my $image (@images) {
print "--BOUNDARY\n";
print "Content-type: image/gif\n\n";
# Read and output image file
open(my $fh, '<:raw', $image) or die "Cannot open $image: $!";
local $/;
print <$fh>;
close($fh);
print "\n";
select(undef, undef, undef, $delay);
}
$count++;
}
# End the multipart message
print "--BOUNDARY--\n";
exit 0;
<!-- Embed the animation in your HTML page -->
<img src="/cgi-bin/nph-anim.pl" alt="Animation" width="100" height="100">
<!-- With parameters (if script supports them) -->
<img src="/cgi-bin/nph-anim.pl?speed=fast&loop=3" alt="Animation">
<!-- Note: Modern browsers may not support server push animations.
Use animated GIFs or CSS/JS animations instead. -->
<!-- Modern alternative using animated GIF -->
<img src="/images/animation.gif" alt="Animation" width="100" height="100">
/* CSS Sprite Animation */
.animation-sprite {
width: 100px;
height: 100px;
background-image: url('spritesheet.png');
animation: play-sprite 1s steps(4) infinite;
}
@keyframes play-sprite {
from { background-position: 0px; }
to { background-position: -400px; }
}
/* CSS Transform Animation */
.animation-rotate {
width: 100px;
height: 100px;
animation: rotate 2s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* CSS Fade Animation */
.animation-fade {
animation: fade 1s ease-in-out infinite alternate;
}
@keyframes fade {
from { opacity: 1; }
to { opacity: 0.3; }
}
/* CSS Bounce Animation */
.animation-bounce {
animation: bounce 0.5s ease infinite alternate;
}
@keyframes bounce {
from { transform: translateY(0); }
to { transform: translateY(-20px); }
}
/**
* Frame Animation - JavaScript Version
* Modern replacement for server push animations
*/
class FrameAnimation {
constructor(element, options = {}) {
this.element = typeof element === 'string'
? document.querySelector(element)
: element;
this.options = {
frames: [],
delay: 100,
loop: true,
autoplay: true,
...options
};
this.currentFrame = 0;
this.isPlaying = false;
this.intervalId = null;
if (this.options.autoplay) {
this.play();
}
}
play() {
if (this.isPlaying) return;
this.isPlaying = true;
this.intervalId = setInterval(() => {
this.showFrame(this.currentFrame);
this.currentFrame++;
if (this.currentFrame >= this.options.frames.length) {
if (this.options.loop) {
this.currentFrame = 0;
} else {
this.stop();
}
}
}, this.options.delay);
}
stop() {
this.isPlaying = false;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
showFrame(index) {
if (this.element.tagName === 'IMG') {
this.element.src = this.options.frames[index];
} else {
this.element.style.backgroundImage =
`url('${this.options.frames[index]}')`;
}
}
goToFrame(index) {
this.currentFrame = index;
this.showFrame(index);
}
}
// Usage example
const animation = new FrameAnimation('#myAnimation', {
frames: [
'/images/frame1.png',
'/images/frame2.png',
'/images/frame3.png',
'/images/frame4.png'
],
delay: 200,
loop: true
});
// Control methods
animation.play();
animation.stop();
animation.goToFrame(2);
// Using requestAnimationFrame for smoother animations
class SmoothAnimation {
constructor(element, frames, fps = 10) {
this.element = document.querySelector(element);
this.frames = frames;
this.fps = fps;
this.frameInterval = 1000 / fps;
this.currentFrame = 0;
this.lastTime = 0;
this.isRunning = false;
}
animate(timestamp) {
if (!this.isRunning) return;
const elapsed = timestamp - this.lastTime;
if (elapsed >= this.frameInterval) {
this.element.src = this.frames[this.currentFrame];
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
this.lastTime = timestamp;
}
requestAnimationFrame((t) => this.animate(t));
}
start() {
this.isRunning = true;
requestAnimationFrame((t) => this.animate(t));
}
stop() {
this.isRunning = false;
}
}
Main Perl script that outputs the animation frames
Installation instructions and configuration guide
nph- prefix stands for "No Parsed Headers." When a CGI script has this prefix, the web server allows the script to send its own HTTP headers directly to the browser without parsing or modifying them. This is required for server push animations because the script needs complete control over the HTTP response, including sending the multipart/x-mixed-replace content type header.
HTTP/1.0 200 OK tells the browser that this is a successful HTTP response. Normally, the web server adds this line automatically, but NPH scripts bypass the server's header processing, so they must include it themselves.
image/gif to image/jpeg. Simply find the line that says print "Content-type: image/gif\n\n"; and change it to print "Content-type: image/jpeg\n\n";. Make sure all your animation frames are in JPEG format.
$delay variable or the select() or sleep() function call and increase the value. Note that very small delays may not work reliably due to network latency and server processing time.
multipart/x-mixed-replace content type, as animated GIFs and JavaScript provide better solutions. This technology was primarily supported in Netscape Navigator and early versions of Firefox. For modern web animations, use animated GIFs, CSS animations, or JavaScript-based animation libraries like GSAP or Lottie.
$| = 1;. For server push animations, unbuffered output is essential because each frame needs to be sent and displayed before the next frame is ready. Without it, the browser would wait for all frames before displaying anything.