I Disabled GIF Animations on Cohost

If there’s one thing that is guaranteed to piss me off in the current year, it’s a social media tool that doesn’t let me disable GIF animations. Personal websites- yeah, whatever, it’s your website. But when social media lets people blast animations into my feed, I’m just not ok with that. Today’s offender is cohost, some hip new website for Posting that some of my friends are using. The GIFs are fucking everywhere and I want them to stop. Websites shouldn’t take all the blame here. If we weren’t living in a hell disguised as middle-earth, browsers would have a setting to disable GIF animations at the browser level. But we don’t, so websites have to do all sorts of bullshit if they actually want to support disabling GIF animations, especially if they want play-on-hover support. Anyways, today cohost is in my line of fire and I decided to do something about it. Here’s my janky userscript that replaces gifs with canvases, with a snapshot of the gif drawn on.

What this script doesn’t do is play-on-hover. I might get to that later, but I’m not sure how to do it yet. For now, this makes the animations stop, and that’s enough for me. Oh and, if I start seeing shit with CSS animations, I will absolutely also modify this script or spin up a userstyle to turn that stuff off too. I just haven’t seen any yet to test on.

How to use it

I want to make it clear that this script has not seen much testing yet. Nevertheless if you’re into this kinda shit, you can use it by following these steps:

If something goes wrong, I’m not really in a position to provide support. Sorry. But if something goes wrong and you fix it, please feel free to email me what the problem was and a copy of your modified script so I can update my code accordingly.

Just read the code here if you want

I’ve copied the script here below if you just want to read it:

// ==UserScript==
// @name         static cohost gifs
// @namespace    https://artemis.sh/
// @version      0.1
// @description  make the gifs stop
// @author       artemis everfree of the violet spark
// @match        https://cohost.org/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=cohost.org
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log("static gifs is running!");

    function replace_gif(gif) {
        let canvas = document.createElement("canvas");
        canvas.width = gif.naturalWidth;
        canvas.height = gif.naturalHeight;
        canvas.class = gif.class;

        // copied from what cohost applies to img normally. this sucks because we need to manually keep it
        // up to date for now
        let style = "max-width: 100%; display: block; vertical-align: middle;";
        if (gif.style) {
            style = style + gif.style;
        canvas.style = style;

        let ctx = canvas.getContext("2d");
        ctx.drawImage(gif, 0, 0, gif.naturalWidth, gif.naturalHeight);
        gif.parentNode.replaceChild(canvas, gif);

    function replace_gif_on_load(gif) {
        //gif.crossOrigin = "anonymous";
        if (gif.complete && gif.naturalHeight > 0) {
            console.log("gif is complete", gif.src);
        } else {
            console.log("Registering gif callback", gif.src);
            //let replaced = false;
            gif.addEventListener("load", () => {
                //if (gif.src.match(/\.gif$/)) {
                    //replaced = true;

    function replace_all_gifs() {
        let imgs = Array.from(document.querySelectorAll("img"));
        let gifs = imgs.filter((img) => img.src.match(/\.gif$/));

    function init() {
        console.log("Registering vi's mutation observer");


        let observer = new MutationObserver((mutationList, observer) => {
            console.log("mutation received");
            mutationList.forEach((mutation) => {
                if (mutation.addedNodes) {
                    mutation.addedNodes.forEach((node) => {
                        if (node.tagName === "IMG" && node.src.match(/\.gif$/)) {
                            setTimeout(() => { replace_gif_on_load(node); });

        observer.observe(document.querySelector("#root"), { attributes: false, childList: true, subtree: true });

    if (document.readyState === "complete") {
    } else {
        let initialized = false;
        document.addEventListener('readystatechange', (e) => {
            if (document.readyState === "complete" && !initialized) {
                setTimeout(() => { init(); });
                initialized = true;