Skip to main content
Donate to support Ukraine's independence.

Building a Zalgo text generator as a Chrome extension

These days I am trying to learn how to use Javascript and I thought that building some Chrome extensions might be a good exercise.

Here I will share with you the journey for creating a simple Zalgo text generator as a Chrome extension.

All the files are available here.

The manifest file

First of all, Chrome needs a manifest.json in order to install and execute the extension.

{
  "name": "Zalgare",
  "version": "1.0",
  "description": "Transform normal text into Zalgo text",
  "permissions": ["activeTab", "storage"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "img/glider_16.png",
      "32": "img/glider_32.png",
      "128": "img/glider_128.png"
    }
  },
  "icons": {
    "16": "img/glider_16.png",
    "32": "img/glider_32.png",
    "128": "img/glider_128.png"
  },
  "commands": {
    "_execute_action": {
      "suggested_key": {
        "default": "Ctrl+B",
        "mac": "Command+B"
      }
    }
  },
  "manifest_version": 3
}

Besides the obvious entries, we need to set the permissions in order to access the current tab and to store data into local storage. background.js will be the main service worker, which in this case will contain the code for installing and initializing the extension. However the main functionality will be available as a popup window when clicking on the extension icon (or pressing CTRL-B) and it will be provided by popup.html (which in turn will use some javascript scripts).

As for the icons, they are pretty straightforward: we need 3 icons, 16x16, 32x32 and 128x128 (optionally you can define a 48x48 icon).

The background.js file will contain only this code:

"use strict";

let defaultIntensity = 10;

chrome.runtime.onInstalled.addListener(() => {
  chrome.storage.local.set({ targetIntensity: defaultIntensity });
  console.log(`Default target intensity: ${defaultIntensity}`);
});

Basically it defines a default intensity for our Zalgo text generator and saves its value in local storage.

The popup window

I didn’t invest a lot of time in designing the popup window and I went with a basic setup: a simple page with a text area, a slider and a button to press to re-zalgoify the text in input. Keep in mind we need another container (I used a simple div) to show the result.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="popup.css" />
  </head>
  <body>
    <textarea id="normalText" name="normalText" rows="4" cols="50"> </textarea>
    <div class="slidecontainer">
      <label for="mySlider">Choose intensity:</label>
      <input
        type="range"
        min="1"
        value=""
        max="30"
        class="slider"
        id="mySlider"
      />
    </div>
    <div>
      <button id="change">re-zalgoify</button>
    </div>
    <div id="zalgoedText"></div>

    <script type="text/javascript" src="../zalgoFunc.js"></script>
    <script type="text/javascript" src="popup.js"></script>
  </body>
</html>

As you can see, the popup loads two scripts: the first one is the script which contains the functions that we will use in order to zalgoify the text, the second script contains the DOM manipulation logic.

I didn’t manage to figure out how to use ES6 modules for the popup window in Chrome extensions (I tried but it didn’t work), I decided to go old school by defining the zalgoFunc.js as a single IIFE (Immediately Invoked Function Expression), which “exports” the main zalgoify function.

"use strict";

const ZalgoModule = (function () {
  const range = n => [...Array(n).keys()];

  function getRandomInt(max) {
    return Math.floor(Math.random() * max);
  }

  // gets unicode code points for the diacritics
  const getDiacritical = function () {
    let diacriticals = [];
    let char = 0x300;
    while (char <= 0x36f) {
      diacriticals.push(String.fromCharCode(char));
      char += 0x001;
    }

    return diacriticals;
  };

  const diacriticals = getDiacritical();

  // generator for getting a random diacritic
  // each time it is called
  function* zalgo() {
    const diac_length = diacriticals.length;
    while (true) {
      const randnum = getRandomInt(diac_length);
      yield diacriticals[randnum];
    }
  }

  // our main function to zalgoify the text
  // it loops over the text and for each caracter
  // it adds a number of diacritics equal to intensity
  function zalgoify(str, intensity) {
    let newText = str.split("").map(function (c) {
      const zalgoGen = zalgo();
      if (c == " ") return c;
      for (let i = 0; i < intensity; i++) {
        const zalgoOut = zalgoGen.next().value;
        c += zalgoOut;
      }
      return c;
    });

    newText = newText.join("");
    return newText;
  }

  return { zalgoify };
})();

Finally here’s the popup.js with the DOM manipulation logic:

"use strict";

const normalText = document.querySelector("#normalText");
const zalgoedText = document.querySelector("#zalgoedText");
const changeBtn = document.querySelector("#change");
const sliderRange = document.querySelector("#mySlider");

//sets the range value to the saved value;
//chrome.storage.local.get returns a promise
//so we have to handle it appropriately
chrome.storage.local
  .get(["targetIntensity"])
  .then(resp => resp.targetIntensity)
  .then(value => {
    console.log(value);

    sliderRange.value = value;
  });

async function zalgo() {
  const intensity = (await chrome.storage.local.get(["targetIntensity"]))
    .targetIntensity;

  const oldText = normalText.value;
  const newText = ZalgoModule.zalgoify(oldText, intensity);

  zalgoedText.innerHTML = newText;
}

// we modify the output each time the range value changes (and we save the new intensity)
sliderRange.addEventListener("change", () => {
  chrome.storage.local.set({ targetIntensity: sliderRange.value });
  zalgo();
});

// rezalgoify on each keyboard input in the textarea
// and on each button click
normalText.onkeyup = changeBtn.onclick = zalgo;

Finally lets add a simple CSS file, called popup.css:

.slidecontainer {
  width: 100%; /* Width of the outside container */
}

#zalgoedText {
  margin: 1em;
  font-size: 2em;
}

Project structure

My final project structure looks like this:

Project structure

Demo

Here’s a working demo:

Send a like: