Using PixiJS to generate or animate a Chrome Extension icon

k_ivanow

Kristian Ivanov

Posted on September 23, 2017

Using PixiJS to generate or animate a Chrome Extension icon

For the last few months I’ve primarily been tinkering with different chrome extensions/plugins. From extensions that simply darken your webpage, to extensions that override the new tab and inject in it a ton of websites, to create sort of a workspace. You can check my latest work here

Disclaimer:

Everything that is described bellow can be done with pure canvas js codding, instead of using PixiJS. However I’ve grown quite fond of PixiJS since I have used it at work and for several side projects.

Why would anyone do this?

At first icons are the most boring part of any project (especially for developers like myself). However this is exactly why some minor touches in an icon can have great impact on the users. I have seen a lot of extensions for Chrome (all adblockers, Limitless, etc) that use the icon badge to display text in it (how many ads are blocked, how much time have you spent on the current webpage, etc).

However earlier today I thought that I haven’t seen any extension, that animates its icon. I searched for a while and saw several StackOverflow posts that explain how to animate icon, by switching between a set of images, defined in the manifest. I decided that it has to be a better way and found that chrome.browserAction.setIcon() receives not only path to the icon, but can accept imageData as well! Hooray!!! Now, let us proceed to the main part of this article.

How to animate the icon

The thought process is straight forward and the execution is easy:

  1. Create background page (it will be responsible for the rendering and updating of the icon)
  2. Create PixiJSstage in the background page (it will actually animate the icon)
  3. Decide on animation — PixiJS has pretty awesome set of filters, tints, scales, skews and so on… Decide what you want to animate and figure out how to animate it. For me, I decided to use my latest extension — Mindfulness was mainly centered on how much time you have left to live (Memento Mori sort of thing) so I decided that an animated clock that changes its color would be appropriate.
  4. Execute said animation and update the chrome extension icon on every frame.

A few things to keep in mind:

  1. PixiJS ticker will not work in a background page. Nothing based on requestAnimation will work, since the background page is never focused. Use setInterval and animate in it. I use around 33ms, but I could’ve used a 1000ms, it all depends on the animation you have decided to make.
  2. When extracting the stage as imageData, don’t use the size of the stage, instead use the size of the renderer. The stage is just a container and as such, its width and height are changed by its children. This can result in stretching, squashing or distorting the icon in any other way.
  3. Chrome extension icons have issues when the size gets over 190px, according to this answer on StackOverflow, so I keep it at128x128.

So, I created a clock class that creates and animates a clock (who would’ve guessed).

var PixiClock = function() {
    this.size = 128;
    this.events = {
        frameRendered: new Signal(),
    }
    this.app = new PIXI.Application(this.size, this.size, { transparent: true });

    document.body.appendChild(this.app.view);

    PIXI.loader.add('icon', 'img/icon512.png').load(function(loader, resources) {

        this.icon = new PIXI.Sprite(resources.icon.texture);
        this.createLines();
        this.app.stage.addChild(this.icon);

        var animate = function() {
            this.lines.secondsLine.rotation = PIXI.DEG_TO_RAD * new Date().getSeconds() * 6;
            this.lines.minutesLine.rotation = PIXI.DEG_TO_RAD * new Date().getMinutes() * 6;
            this.lines.hoursLine.rotation = PIXI.DEG_TO_RAD * new Date().getHours() * 30;
            this.app.renderer.render(this.app.stage);
            this.events.frameRendered.dispatch();
        }.bind(this);
        setInterval(animate, 33);

        animate();
    }.bind(this));
};

PixiClock.prototype.createLines = function(color) {
    var lineWidth = 60;
    this.icon.scale.set(1, 1);

    this.lines = {
        secondsLine: new PIXI.Graphics(),
        minutesLine: new PIXI.Graphics(),
        hoursLine: new PIXI.Graphics()
    }

    for (var i in this.lines) {
        this.icon.addChild(this.lines[i]);
        this.lines[i].cacheAsBitmap = true;
        this.lines[i].lineStyle(lineWidth, 0xffffff);
        this.lines[i].tint = color != undefined ? color : 0x333333;
        this.lines[i].position = {
            x: this.icon.getBounds().width / 2 - lineWidth / 2,
            y: this.icon.getBounds().height / 2
        };
        this.lines[i].moveTo(0, 0 + lineWidth);
        this.lines[i].lineTo(0, 0 - this.icon.getBounds().height / 2);
    }
    // graphics.endFill();

    this.lines.hoursLine.scale.set(1, 0.7);
    this.lines.minutesLine.scale.set(1, 0.8);

    this.icon.scale.set((this.size / this.icon.width), (this.size / this.icon.height));
};

PixiClock.prototype.deleteLines = function() {
    this.icon.removeChildren();
}
Enter fullscreen mode Exit fullscreen mode

The concept is simple

  1. Create stage
  2. Add the main icon of the extension
  3. Add 3 lines (one for each clock hand)
  4. Rotate them based on the current time
  5. Change their colour by recreating them for certain tabs (this logic is out of the scope of the article)
  6. Dispatch event on every frame
  7. In the background page, catch this event and update the extension icon
var pixiClock = new PixiClock();
pixiClock.events.frameRendered.add(function() {
    chrome.browserAction.setIcon({
        imageData: pixiClock.app.renderer.plugins.extract.canvas(pixiClock.app.stage).getContext("2d").getImageData(
            0, 0,
            pixiClock.app.stage.width, pixiClock.app.stage.height
        )
    });
})
Enter fullscreen mode Exit fullscreen mode

Here is the end result. I use two styles for the arrows depending on the webpage the user is at.

Prologue

I guess

I just came up with something that I haven’t seen done (at least so far. Feel free to correct me if I am wrong) and how to do it and decided to share it.

Something similar can be done with a large set of assets, that can be switched to simulate an animation, which is the most common approach I’ve seen while my brief research for the article.

The exact same result can be done with a simple canvas and JS code. I prefer PixiJS because I am familiar with it and it allows me to make more complex animations, if I decide to.

I hope that you found this article interesting, even if it is for the sole fact that you can just take some part of animated canvas or WebGL context and use it as an icon for chrome extension, instead of a simple png. You can even create your whole icon by simple shapes and filters or blend modes, if you have the time and desire to do so.

P.S. Check out the extension, that was the cause for the idea behind this article.


This post was originally published on medium.com

💖 💪 🙅 🚩
k_ivanow
Kristian Ivanov

Posted on September 23, 2017

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related