Animation

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.

Legacy Script
Version 1.0
Perl CGI

Overview

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.

How Server Push Works

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.

Package Contents
File Description
nph-anim.pl Main Perl script that outputs the animation frames
README Installation instructions and configuration guide
Technology Timeline
  • 1995: Server push animations
  • 1989: GIF89a specification
  • ~1996: GIF89a browser support
  • Today: CSS/JS animations

How It Works

1
NPH Response

The script sends its own HTTP headers directly, bypassing server header processing.

2
Multipart MIME

Uses multipart/x-mixed-replace to send multiple images in one response.

3
Frame Loop

Images are sent in sequence with a delay between each frame.

4
Browser Replace

Each new image replaces the previous one, creating animation effect.

Features

Multiple Frames

Support for any number of animation frames. Simply add more images to the configuration.

Configurable Speed

Control animation speed by adjusting the delay between frames.

Loop Control

Set the number of animation loops or run continuously until the connection closes.

GIF Support

Works with GIF images by default. Can be modified to support JPEG with minor changes.

Browser Independent

Works in Netscape Navigator and compatible browsers that support server push.

NPH Protocol

Uses No Parsed Headers for direct control over HTTP response, enabling unbuffered output.

Modern Alternatives (2024)

Recommendation: For modern web projects, use one of these animation methods instead of server push.

Basic Animation Methods

Animated GIF / WebP / APNG
Image Formats

Self-contained animated images that work everywhere without JavaScript.

  • GIF: 256 colors, universal support
  • APNG: Full color, alpha transparency
  • WebP: 26% smaller than GIF, wide support
CSS Animations
@keyframes & Transitions

Hardware-accelerated, GPU-powered animations.

Lottie
LottieFiles

After Effects animations exported as lightweight JSON.

  • Vector-based, scalable
  • Interactive animations
  • Huge free library
  • React, Vue, vanilla JS

Professional JavaScript Animation Libraries

GSAP

The industry standard. Timeline-based sequencing, ScrollTrigger, morphing effects.

Free Pro Plugins
Motion

Formerly Framer Motion. 2.5x faster than GSAP, React-first, MIT licensed.

Free React
Anime.js

Lightweight (17KB), intuitive API. Great for beginners, performant.

Free MIT
React Spring

Physics-based animations for React. Natural spring dynamics.

Free React

Library Comparison

Library Size Best For Framework License
GSAP~60KBComplex timelines, scroll effectsAnyFree / Business
Motion~20KB (tree-shaking)React apps, declarativeReactMIT
Anime.js~17KBSimple to medium animationsAnyMIT
React Spring~25KBPhysics-based, natural feelReactMIT
Lottie~35KBAfter Effects exportsAnyMIT
CSS0KBSimple transforms, transitionsAnyNative

Installation

  1. Upload Script with NPH Prefix
    Upload 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.
  2. Configure Perl Path
    Edit the first line of the script to point to your Perl interpreter (usually #!/usr/bin/perl).
  3. Upload Animation Frames
    Upload your animation frame images (GIF format) to a web-accessible directory.
  4. Configure Image Paths
    Edit the script to set the paths to your animation frame images and configure the delay time.
  5. Set Permissions
    Set the script permissions to 755: chmod 755 nph-anim.pl
  6. Embed in HTML
    Add an <img> tag pointing to the CGI script in your HTML page.

Code Examples

Server Push Animation (Perl)
#!/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;
HTML Usage
<!-- 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">
Modern CSS Animation (Recommended)
/* 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); }
}
Modern JavaScript Animation
/**
 * 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;
    }
}

Download

Compressed Archives
  • animation.tar.gz 5.1 KB
  • animation.zip 5.1 KB
  • animation.tar.Z 8.1 KB
  • animation.tar 20.5 KB
Individual Files
  • nph-anim.pl

    Main Perl script that outputs the animation frames

  • README

    Installation instructions and configuration guide

Frequently Asked Questions

The 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.

Since this is an NPH (No Parsed Headers) script, it must send its own HTTP status line. 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.

To use JPEG images instead of GIFs, you need to change the Content-type header from 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.

If your animation plays too fast, increase the delay value in the script. The delay is specified in seconds (can be decimal, like 0.5 for half a second). Look for the $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.

Skipping or jumping animations are usually caused by network latency or large image files. Solutions include: (1) Optimize your images to reduce file size, (2) Increase the delay between frames, (3) Use smaller image dimensions, (4) Ensure your server has adequate bandwidth. For reliable animations, consider using animated GIFs or JavaScript-based alternatives that preload images.

Many modern browsers have dropped support for the 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.

In the 1990s, before GIF89a support was widespread in browsers, server push was the only way to create animations. Today, there's no practical reason to use server push for animations. Animated GIFs are universally supported, don't require server resources during playback, can be cached, and are easier to create. This script is preserved for historical and educational purposes only.

Unbuffered output means data is sent to the browser immediately as it's generated, rather than being collected in a buffer and sent all at once. In Perl, this is enabled with $| = 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.