DEV Community

Jeff Riggle
Jeff Riggle

Posted on

Do you even Render, Bro?

In an effort to participate in some cutting-edge technology, I have set out on a journey to move a green square back and forth on a web page.

Beginner mode

Conventional wisdom tells me there are a set of CSS primitives that give me this power.

<html>
    <head>
        <style>
            body {
                margin: 0;
                display: grid;
                place-items: center;
            }

            #container {
                background-color: black;
                margin: auto auto;
                height: 600px;
                width: 800px;
            }

            #box {
                width: 100px;
                height: 100px;
                margin-top: 250px;
                background-color: green;
                animation-duration: 4s;
                animation-name: slide;
                animation-iteration-count: infinite;
            }

            @keyframes slide {
                0% {
                    translate: 0 0;
                }
                50% {
                    translate: 700px 0;
                }
                100% {
                    translate: 0 0;
                }
            }
        </style>
    </head>
    <body>
        <div id="container"><div id="box"/></div>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Leveling up

This gets the job done, but let's be honest: “Who has time to read documentation?” There are at least a few thousand words and links on this page. Instead of learning about animation properties and keyframes, let’s do some real programming. Everyone knows that JavaScript is superior to CSS, so we can just calculate the position.

<html>
    <head>
        <style>
            body {
                margin: 0;
                display: grid;
                place-items: center;
            }
            #container {
                background-color: black;
                margin: auto auto;
                height: 600px;
                width: 800px;
                position: relative;
            }
            #box {
                width: 100px;
                height: 100px;
                background-color: green;
                position: absolute;
                top: 250px;
                left: 0px;
            }
        </style>
        <script>
            window.addEventListener('load', () => {
                let target = document.getElementById('box');
                let left = 0;
                let direction = 1;

                function runAnimation() {
                    requestAnimationFrame(() => {
                        left = left + (2.9 * direction);
                        if (left >= 700) {
                            direction = -1;
                        } else if (left <= 0) {
                            direction = 1;
                        }

                        target.style.setProperty('left', `${left}px`);
                        runAnimation();
                    });
                }
                runAnimation();
            });
        </script>
    </head>
    <body>
        <div id="container"><div id="box"/></div>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Gotta have less CSS

This is starting to feel like some real code. However, there is still too much of that pesky CSS. What the heck is an absolute container doing in a relative container anyway? I will not stand for this nonsense! To eradicate this pest they call CSS, we must use a canvas.

<html>
    <head>
        <style>
            body {
                margin: 0;
                display: grid;
                place-items: center;
            }

            #container {
                background-color: black;
                margin: auto auto;
            }
        </style>
        <script>
            window.addEventListener('load', () => {
                let left = 0;
                let direction = 1;
                let canvas = document.getElementById('container');
                let context = canvas.getContext('2d');
                function runAnimation() {
                    requestAnimationFrame(() => {
                        left = left + (2.9 * direction);
                        if (left >= 700) {
                            direction = -1;
                        } else if (left <= 0) {
                            direction = 1;
                        }

                        context.clearRect(0, 0, 800, 600);
                        context.fillStyle = 'green';
                        context.fillRect(left, 250, 100, 100);
                        runAnimation();
                    });
                }
                runAnimation();
            });
        </script>
    </head>
    <body>
        <canvas id="container" width="800px" height="600px"></canvas>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

What is an animation?

We should stop here, right? Wrong! I don’t have time to learn the 2d context APIs. Why do I even have clearRect and fillRect? An animation is no more than a series of images. Instead, we must dynamically generate an image on every keyframe and have the browser display a series of images.

<html>
    <head>
        <style>
            body {
                margin: 0;
                display: grid;
                place-items: center;
            }

            #container {
                margin: auto auto;
            }
        </style>
        <script>
            window.addEventListener('load', () => {
                let left = 0;
                let direction = 1;
                const width = 800;
                const height = 600;
                const imageDataSize = 4 * width * height;  

                const imageHeader = [
                    0x42, 0x4D,
                    0x36, 0x4C, 0x1D, 0x00,
                    0x00, 0x00,
                    0x00, 0x00,
                    0x36, 0x00, 0x00, 0x00
                ];
                const infoHeader = [
                    0x28, 0x00, 0x00, 0x00,
                    0x20, 0x03, 0x00, 0x00,
                    0x58, 0x02, 0x00, 0x00,
                    0x01, 0x00,
                    0x20, 0x00,
                    0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00
                ];
                let dataOffset = imageHeader.length + infoHeader.length;
                let data = new Array(imageDataSize + dataOffset);

                for (let i = 0; i < imageHeader.length; i++) {
                    data[i] = imageHeader[i];
                }
                for (let i = 0; i < infoHeader.length; i++) {
                    data[i + imageHeader.length] = infoHeader[i];
                }

                clearImage(data);
                const imgEl = document.getElementById('container');

                function clearImage(data) {
                    for (let y = 0; y < height; y++) {
                        let pitch = y * width;
                        for (let x = 0; x < width; x++) {
                            const offset = ((pitch + x) * 4) + dataOffset;
                            data[offset] = 0;
                            data[offset + 1] = 0;
                            data[offset + 2] = 0;
                            data[offset + 3] = 0;
                        }
                    }
                }  

                function drawRect(data, x, y, rWidth, rHeight, red, green, blue) {
                    for (let dy = 0; dy < rHeight; dy++) {
                        const pitch = (y + dy) * width;
                        for (let dx = 0; dx < rWidth; dx++) {
                            const offset = ((pitch + dx + x) * 4) + dataOffset;
                            data[offset] = blue;
                            data[offset + 1] = green;
                            data[offset + 2] = red;
                            data[offset + 3] = 0;
                        }
                    }
                }
                function updateImage(imgEl, imageHeader, infoHeader, data) {
                    let imageData = new Uint8Array(data);
                    imgEl.src = `data:image/bmp;base64,${imageData.toBase64()}`;
                } 

                function runAnimation() {
                    requestAnimationFrame(() => {
                        left = left + (2.9 * direction);
                        if (left >= 700) {
                            direction = -1;
                        } else if (left <= 0) {
                            direction = 1;
                        }

                        clearImage(data);
                        drawRect(data, Math.round(left), 250, 100, 100, 0, 128, 0);
                        updateImage(imgEl, imageHeader, infoHeader, data);
                        runAnimation();
                    });
                }
                runAnimation();
            });
        </script>
    </head>
    <body>
        <img id="container" width="800px" height="600px" />
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Real programming

Now we have hit perfection, right? Again wrong! As everyone knows, the best engineers do backend, and the best backend engineers use real programming languages like C. To rise to the occasion, we must transcend JavaScript to reach true enlightenment. You might be thinking WASM. Stop it! We cannot tolerate that because it has JavaScript bindings. With a little help from a meta tag, we can make this work.

// who needs to see includes anyway, but if you must read the real code https://github.com/JeffreyRiggle/green-box/blob/main/refresh-server/main.c
#include "util.h"
#include "render.h"

// Who needs structs anyway
static unsigned char image_header[] = {
    0x42, 0x4D,
    0x36, 0x4C, 0x1D, 0x00,
    0x00, 0x00,
    0x00, 0x00,
    0x36, 0x00, 0x00, 0x00
};

static unsigned char info_header[] = {
    0x28, 0x00, 0x00, 0x00,
    0x20, 0x03, 0x00, 0x00,
    0x58, 0x02, 0x00, 0x00,
    0x01, 0x00,
    0x20, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00
}; 

static int width = 800;
static int height = 600;
static int data_offset = sizeof(info_header) + sizeof(image_header);
static int left = 0;
static int direction = 1;
static unsigned char* image_data;

// functions omitted for brevity, no one reads code anyway
void generate_image()
{
    left = left + (2.9 * direction);
    if (left >= 700)
    {
        direction = -1;
    }
    else if (left <= 0)
    {
        direction = 1;
    }

    clear_image(image_data, width, height, data_offset);
    draw_rectangle(image_data, width, height, data_offset, round(left), 250, 100, 100, 0, 128, 0);
}

void handle_img_request(int client_fd, const tcp_buf_t *buf)
{
    generate_image();
    size_t image_size = data_offset + (width * height * 4);

    char header[256];
    int header_len = snprintf(
        header,
        sizeof(header),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: image/bitmap\r\n"
        "Content-Length: %zu\r\n"
        "\r\n",
        image_size);
    write(client_fd, header, header_len);  

    size_t written = 0;

    while (written < image_size)
    {
        ssize_t n = write(
            client_fd,
            image_data + written,
            image_size - written);

        if (n == -1)
        {
            perror("write");
            break;
        }  

        written += n;
    }
    close(client_fd);
}



void handle_home_request(int client)
{
    char header[256];


    int header_len = snprintf(
        header,
        sizeof(header),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "Content-Length: %zu\r\n"
        "Connection: close\r\n"
        "\r\n",
        strlen(home_body));

    write(client, header, header_len);
    write(client, home_body, strlen(home_body));
    close(client);
}


void process_event(struct kevent evt, int server_fd, struct kevent *change_event, int kqueue_fd)
{
    // boring condition checking
    if (evt.filter == EVFILT_READ)
    {
        char *buffer = malloc(1024);
        ssize_t bytes_read = read(target_fd, buffer, 1023);
        if (bytes_read > 0)
        {
            tcp_buf_t tcp_buffer;
            tcp_buffer.body = buffer;
            tcp_buffer.len = 1023;

            http_header_details_t header_details = get_header_details(&tcp_buffer);
            if (is_home_page_request(header_details))
            {
                handle_home_request(target_fd);
            }
            else if (is_image_file_request(header_details))
            {
                handle_img_request(target_fd, &tcp_buffer);
            }
            // Blah blah blah
        }
    }
}


int main()

{
    image_data = malloc(sizeof(info_header) + sizeof(image_header) + (width * height * 4));
    memcpy(image_data, image_header, sizeof(image_header));
    memcpy(image_data + sizeof(image_header), info_header, sizeof(info_header)); 

    // setup server or something
    while (1)
    {
        int event_count = kevent(kqueue_fd, NULL, 0, event_list, 32, NULL);
        if (event_count == -1)
        {
            break;
        }

        for (int i = 0; i < event_count; i++)
        {
            struct kevent target_event = event_list[i];
            process_event(target_event, server_fd, &change_event, kqueue_fd);
        }
    }

    close(server_fd);
    close(kqueue_fd);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode
<html>
    <head>
        <meta http-equiv="Refresh" content=".05"/>
        <style>
            body {
                margin: 0;
                display: grid;
                place-items: center;
            }

            #container {
                margin: auto auto;
            }
        </style>
    </head>
    <body>
        <img src="/img" id="container" width="800px" height="600px" />
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now that we have reached nirvana, tell me: did you learn something, are you amused, horrified, or just upset that this might reach the training data of your favorite model?

Top comments (0)