Showing posts with label comics. Show all posts
Showing posts with label comics. Show all posts

Monday, April 18, 2016

Better Static Images for Amphibian.com

The new, improved version of the image for a recent comic.
As I mentioned last week, I have been working to close Issue #7 on my webcomic. Basically, Amphibian.com is not a normal webcomic. It's not an image, which is great until someone wants to share a link to a comic on social media. I auto-generate static images of the comic just for sharing. But the comics which contain embedded games and stuff haven't looked right in the static captures. I wanted to at least put a warning in the image that explains why they often look weird, but as an added bonus I actually made them look better (most of the time).

As you can see in the picture on the right, I was able to add some warning text to the top of dynamic comics. I generally share this version on TopWebcomics.com but you may see it other places as well. When people see comics like this in the future, they'll know that they should view it on Amphibian.com to get the full effect.

Also, when this comic was originally published, the third frame was black. The game was not captured as part of the image. This was due to two things - the URL of the page used for the image capture was such that the game's image assets could not be loaded, and the fact that the capture was taken before the game initialized asynchronously. I fixed the URL issue by changing the static image comic path from /basic/[comic-number] to /[comic-number]&b=1. There wasn't really a generic way to ensure the comic will be rendered before the capture in all cases, but I added a 500 millisecond delay which should be good enough the majority of the time.

Finally, I also locked-down the ability to see the basic versions of the comic. It was the case that you could view a future comic if you asked for the basic rendering. I changed the rules so that only authenticated users or requests coming from the server itself can see them. I use PhantomJS to perform the image capture and it runs on the server, so that was a simpler fix than I initially thought that it would be.

It always feels good to resolve an issue. It also feels good to read one of my comics. Here's the link so you can feel good too.

Amphibian.com comic for 18 April 2016

Friday, December 11, 2015

Residual Traffic from my Ad

Remember about a month ago when my ad for Amphibian.com finished its run on TopWebcomics.com? I wrote about how I was tracking its effectiveness using a special URL and a cookie:
Tracking Visitors from Ads with Express Middleware
Today I was curious just how many comics are still being viewed because of the ad almost a month later. I ran my database queries again and found that over the past 3 weeks (all outside of the ad's run) there have been 183 visits to the main page of Amphibian.com by people who have the cookie.

While not an astounding number, I'm still pleased with it. It means there are quite a number of new people coming to read the comics that would not be had I not run the ad.

In related news, I'm having a lot of trouble staying in the top 100 this month. Voting is fierce in December, I suppose. I could use a few more votes if you have a second. Just click here.

Remember to take a look at today's comic, even if you never clicked on the ad or visited TopWebcomics.com. It doesn't matter to me why you look at them. Just do it!

Amphibian.com comic for 11 December 2015

Monday, November 30, 2015

Adding Emoji to a Web Page

I've decided that emoji are the 21st century version of cave wall drawings. What ever happened to using words?
I draw frog pictures online.
Emoji set designed and offered free by Emoji One
Anyway, I wanted to do a comic where the frogs talked to each other using emoji, but I quickly encountered several problems. The native emoji on Windows look terrible, and are incomplete. And knowing exactly what my mobile users would see was impossible due to inconsistencies between the different phone emoji sets. Additionally, I would have to make some code changes to Amphibian.com's editor because it destroys the extended Unicode character values that are required to write in emoji. As it turns out, emoji on the desktop web is a legitimate problem.

But there is a solution! I found Emoji One, open-source emoji engineered for the web. This is a great project that made adding emoji support to Amphibian.com extremely simple.

First, they provide their JavaScript and CSS files via CDN, so I didn't have to really install anything directly into my web application. Just a few lines of code into my header template...

<script
  src="//cdn.jsdelivr.net/emojione/1.5.2/lib/js/emojione.min.js">
</script>

<link rel="stylesheet"
  href="//cdn.jsdelivr.net/emojione/1.5.2/assets/css/emojione.min.css"
/>

Once that was done, I had a few options. They provide functions for converting both Unicode and shorthand (such as :frog:) into pictures. I decided to go with the shorthand version for my site, mainly because it allowed me to more easily write the comics from my desktop browser (Chrome on Windows).

When I write some lines for a frog's speech bubble, I can type

:pizza: + :bowling: @ :clock730: :question:

which is then converted to emoji when the page loads. It's not 100% automatic, though. I had to add a few lines of JavaScript to my site but it was really simple.

// Emojis! (See http://emojione.com/developers)
$(function() {

    $('.bubble, .free-text').each(function() {
        var origText = $(this).html();
        var eText = emojione.shortnameToImage(origText);
        $(this).html(eText);
    });

});

The above code relies on jQuery, which I use on my site. As soon as the page is done loading, it finds every element on the page that has a class of bubble or free-text (the classes I use for speech bubbles and floating text areas) and converts the contents to include emoji images. It happens fast enough that I haven't been able to catch the shorthand text in there, but there could theoretically be some flicker. If I was more serious about this I could do the conversion on the server side instead...but this is just for a comic about frogs.

Make sure you read today's emojinal comic so that you can appreciate all the work I had to do for it. I'm not sure if I'll use emojis in the future, but you never know...

Amphibian.com comic for 30 November 2015

Monday, November 16, 2015

Review of my TopWebcomics Ad

Instead of doing another post about game progress today, I thought that I'd just give a quick update on my ad I ran on TopWebcomics.com for the past several weeks. My run just ended on Saturday, and I'd like to review it.

To review, I put a special parameter in the link from the ad that I could use to track people who visit Amphibian.com as a direct result of clicking the ad. Here's the stats for the past 4 weeks:

Total Comics Viewed44,121
Comics Viewed by Someone Who Clicked on the Ad15,285
Comics Viewed not related to the Ad28,836

So much like last time I checked, which was only a few days after the ad started running, about a third of the comics viewed were a direct result of the advertising. Combine this with the fact that overall site traffic has increased by around 50% since the ad started, and I would have to conclude that TopWebcomics advertising has blown away my Facebook advertising in terms of getting traffic to the site. As a bonus, 6 people added my comic to their "Favorites" list on TopWebcomics.com over the past 4 weeks. While that doesn't seem like a big number, it is an increase of 50% as well.

My ad, in case you missed it.
I ended up with a cost-per-click of around $0.04. But once someone clicked through to my site, it was highly likely that they stayed and viewed several comics. Most of the increase in views was a direct result of people browsing through the complete set of comics after visiting the site for the first time. About 25% of the people who originally came from the ad came back on their own at least once.

Overall, I would say that I am highly satisfied with my experience advertising with TopWebcomics. I would do it again.

In other news, the test level for the 8-bit style platformer that I'm making with Phaser is now live at https://amphibian.com/eight-bit. Give it a try if you have a few minutes, and feel free to send feedback. I'll try to make some significant updates this week, but I'm also working on adding some new features to the comic as well. Here's the link to today's comic, even without those new features.

Amphibian.com comic for 16 November 2015

Friday, October 9, 2015

First Impressions from my TopWebcomics.com Ad

Here's my ad, but you don't have to click on it.
As I mentioned last week, I am running a month-long ad banner on TopWebcomics.com. It just started appearing on Wednesday. In the past, I tried promoting Amphibian.com on Facebook but did not get really great results. My hope is that advertising on a site devoted to web comics will have provide better returns.

Okay, first of all let's address the irony of running ads to promote a comic that I refuse to monetize with ads. I don't think I'm too much of a hypocrite. Right now, I don't need ad revenue to fund the server that runs Amphibian.com so I am free to try other methods of earning money from the comics. And I don't hate all ads on the Internet, I just feel like in many cases they've become so horrible that they actually detract from the content on many web pages. Ad blockers have only gotten more popular because ads have gotten more horrible. My decision to pay for ad promotion on TopWebcomics helps their site, which has already been driving a fair amount of traffic to my comics (as long as I stay near the top of the 2nd page). Even if no one sees my ad because they're all running ad blockers, I feel like I'm giving back to the site.

So how many people have seen the ad?

Well, I don't know. Unlike Facebook advertising, I don't know how many people have seen it. But I can tell how many comics have been viewed because of it. I am using a referral code in the banner's URL and a cookie to track how many times a person who came to the site by clicking on the ad views a comic for the next month.

So far, over 800 comics have been viewed as a direct result of the ad. That's approximately 29% of the comics viewed since the ad started running just 48 hours ago. Not a bad start.

Other interesting things to note
  • A majority of the people coming from the ad are jumping back to the first comic and reading the early ones as well as the current ones.
  • There has been a significant increase in the percentage of visitors to the site who are using iOS devices. Maybe my mobile-first approach to webcomics will finally pay off?
  • Spiders are not a problem. Is it because they don't do cookies, or follow ad banner links, or include query strings on links they follow? I'm not super-knowledgeable in this field of Internetting, so I don't know. I'm only reporting what I see.

That's all for today. I hope you like today's comic, which pokes fun at ageism in the software industry (at 36, I'm starting to feel it) by making clearly absurd claims as to the origin of new programming languages. Or maybe I'm on to something...

Amphibian.com comic for 9 October 2015

Friday, October 2, 2015

Tracking Visitors from Ads with Express Middleware

I'm getting ready to run another month of promotion for Amphibian.com. This time I'll be running an ad on TopWebcomics.com, a webcomics list and voting site.

The best list of active web comics, including mine!
Unlike before, when I ran ads on Facebook, I'd like to get a better idea about how people interact with the site after coming from an ad banner. This is sometimes called conversion tracking, but since I'm not really trying to convert any clicks to purchases I'm not sure the term applies. I am trying to convert people to regular readers of the comic, so maybe it does. In any case, I added a piece of middleware to my Express web app in order to track visitors from ads.

Amphibian.com is built using Node with the Express framework. I use various Express middlewares to add features to the site, such as access control, logging, multipart uploads, etc. I've written quite a bit about these in the past (here, here, and here, for example). I wrote a custom middleware and modified my logging middleware in order to create this ad tracking feature.

Here's how it should work... I create my ad banner and give it to a site to display, along with a URL. Instead of just plain http://amphibian.com, I assign each ad its own unique code and append that to the URL as part of the query string. For example, I tell TopWebcomics that I want my ad banner to link to http://amphibian.com/?s=abcdefg. When I see a value for s (I use "s" for "source") on the query string of any comic access, I add the value (abcdefg in this example) to the request object in a special field. Then in my logger, I look for a value in that field and write it to the database along with the rest of the request data. Now I'll be able to query my database later and determine which accesses originated with a user clicking on the banner. If I ran more than one banner at a time, I could just give them each a different code and compare their performances.

My tracker middleware looks like this:

module.exports = function() {

    var tracker = function(req, res, next) {

        var src = null;

        if (req.query.s) {

            src = req.query.s;
            res.cookie("s", src, { maxAge: 2629746000 });

        } else if (req.cookies.s) {

            src = req.cookies.s;

        }

        if (src !== null) {
            req["click-source"] = src;
        }

        next();

    };

    return function(req, res, next) {
        tracker(req, res, next);
    };

};

The code is fairly simple. First I check the query string for an s field (line 7). If present, I set the source code variable to its value and set a cookie with the same value. The cookie has an expiry time one month in the future. If the request does not have a s value in the query string but does have the cookie value, I set the source code variable from the cookie. This is how I know if you come back to the site later without clicking on the ad - you'll have the cookie. As long as you clicked on the ad once, I count the ad as being the source of all your visits for the next month.

The source code gets put on the request object in a field called "click-source" there on line 19. This is the field that I check in the logger middleware and, if found, write to the database.

One final note about using this middleware. Since it relies on the cookie-parser middleware to populate the request's cookies field, I have to make sure that I set my Express app to use the cookie-parser first. Express chains the middlewares together in the order they are declared.

var cookieParser = require('cookie-parser');
var adTrack = require('./sourceTracker');
var logging = require('./logger');

// ... other stuff ...

app.use(cookieParser()); // required before source tracker
app.use(adTrack());
app.use(logging());

// ... other stuff ...

So my plan for tracking conversions is simple, and only took a few minutes to implement. After I run this ad for a month I'll be sure to write up my results and what, if any, improvements I want to make for the next time I try a promotion. Until then, keep reading the comics by clicking on the link below.

Amphibian.com comic for 2 October 2015

Friday, August 28, 2015

Comic Formulas

No technical post today, just me getting some thoughts out there about what types of comics seem to work out the best. I've had a lot of time to think about this over the past 13 months.

Four panels seems to be the best length for me. Particularly, I am fond of a 2+1+1 pattern in which the frogs talk back and forth for two frames, a third frame cuts away to another scene, and then in the final frame a punchline is delivered. You'll notice that pattern a lot in my work. I've done some as short as three panels and as many as 8, but four seems to be my favorite.

Obviously, I do a lot of work with puns. I like puns. I think everyone should.

Do you get it?
Over time, I've tried to back away from the really technical jokes. I had a set of comics about the frogs trying to get accepted into the Order of Log N, but I've never published them. There was a log that guided them through a series of tasks related to efficiency of algorithms. You know, O(log n). No, probably not. Unless you are a computer scientist or software engineer you probably have no idea what that means. I'm not saying that I write jokes specifically for an audience. I write the comics for myself, but part of that is my desire to make people laugh. If more people are confused than amused, I don't feel I've accomplished my goal. It's a complicated feeling.

The comics that I like the least are usually the most well-received. I get more compliments about comics that I almost deleted than any others. I'm not sure what's going on. Maybe I over-think things. Maybe I'm too critical of myself. Maybe my sense of humor is shared by no one else in the world.

I have to write what I know. The whole point of Amphibian.com is that the frogs are trying to master and profit from the latest technologies. As a software engineer who was self-trained from age 10 until entering college in 1997, that's what I know. It's what I still do. It's probably what I'll always do. My kids want me to write comics about soccer or gymnastics because that's what they know (I did a game about frog soccer though). I have to explain to them that my work expresses my own experiences and encourage them to write their own comics about their experiences. I am trying to train them to take over writing Amphibian.com someday, but I can't convince any of them to become software engineers.

Today's comic is about something else I know - answering tech support questions. Besides doing this for family members since 1991, I used to work at a small Internet provider back in the days when Windows didn't always come installed with Internet Explorer or even TCP/IP. Helping people over the phone was tough, but there were worse parts. I once had to re-install Windows on a customer's computer because it would no longer boot. After they took the computer home, they called in furious at me because they couldn't find Windows anymore. I asked what was on their screen. Just a little picture that said "My Computer" and another one that said "Recycle Bin." I told them that they were looking at Windows. They called me a liar. I decided that I should get a real job.

Amphibian.com comic for 28 August 2015

Friday, May 8, 2015

Editable Text on Your Web Pages with X-editable

Was He Whig? No, WYSIWYG!
Right behind pictures of frogs, speech balloons are the next most important part of my web comic. I've discussed previously how the balloons in my comic are really just cleverly styled <P> tags. For my WYSIWYG comic editor, I needed to display them just like they appear in the comics, but easily allow me to type new text into them.

Inline editing of text on a page can be a helpful addition to many types of web applications that rely on human-entered data. It's not just about speech balloons. If you need to use a feature like this as well, consider X-editable, the JavaScript/CSS library I selected for my comic editor.

X-editable is a great package to use when you need inline editing and your site uses jQuery. It gets even better if you also use jQuery UI and/or Bootstrap, but it can be a little confusing to set up properly. First of all, it offers multiple download packages depending on if you want to use it with just jQuery, jQueryUI, or two different version of Bootstrap. My comic uses jQuery and Bootstrap 3, so my examples are based off of that package.

Here is the HTML for a simple demo.

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Editable Text</title>
  <link rel="stylesheet" href="css/bootstrap.min.css">
  <link rel="stylesheet" href="css/bootstrap-editable.css">
  <style>

    p {
        border: 1px solid #CCCCCC;
        margin: 10px;
        padding: 5px;
        width: 250px;
    }

    .editable-clear-x {
        background: url('clear.png');
    }

    .editableform-loading {
        background: url('loading.gif');  
    }

  </style>
</head>

<body>

  <p id="e12" class="canedit">This is some text you can edit.</p>

  <p id="e13">This is some text you can't edit.</p>

</body>

<script src="jquery-1.11.2.min.js"></script>
<script src="bootstrap.min.js"></script>
<script src="bootstrap-editable.min.js"></script>

</html>

For stylesheets you'll need Bootstrap (bootstrap.min.css in my example) and X-editable (bootstrap-editable.css). You'll also need the jQuery JavaScript, Bootstrap's JavaScript, and then finally X-editable's JavaScript.

By default, X-editable tries to find some images in the "../img/" path. That will only work if you keep your static images in a directory called "img" which is sibling to the "css" directory where you keep the X-editable CSS file. That wasn't an option for me, so I get around it by just copying the "clear.png" and "loading.gif" images to my preferred location and then overriding the CSS for those elements with my own background URL path.

In my example I have two sections of text, one that should be editable and other other not. To make the text editable inline, just execute the following JavaScript:

$(".canedit").editable({
    type: "text",
    mode: "inline",
    escape: false
});

It finds all the elements with the canedit class and makes them editable. The options object passed indicates that the type of editor will be a simple text box, using inline mode (a popup box is the alternative), and the contents will not be escaped. "Escaping" in this context means getting the value via jQuery's .text() vs. .html(). I choose not to escape so that I can put links and other HTML markup in the speech balloons. A full list of options can be found in the documentation. After executing that code, the editable text will be indicated by a blue dotted underline. Clicking on the text will replace it with a text box and some buttons. Type something new and hit enter or click the check and it turns back into plain text with the new value.

X-editable has a whole set of features that enable it to automatically POST updates to the text to a server as soon as the changes are made, but my comic editor uses a different model. I arrange all my images and text and then click on a "Save" button that reads through the DOM and builds a complete JSON representation of the comic. For speech balloons, I just do something like this:

var balloons = [];
$('p.editable').each(function(idx, elem) {

    var b = {
        // ...
        // other balloon data
        // ...
        text: $(elem).html()
    };
    balloons.push(b);

});

That finds all the editable text (in my comic editor, all <p> tags inside cells are editable), grabs the contents, creates a "balloon" object out of it, and adds it to the list of balloons.

If you're wondering what "other balloon data" I need to collect, consider that editable <p> tags can also be made draggable and resizable with jQuery UI (see my previous post where I did it with images) which enables me to easily position, size, and edit the frog speech balloons. So in addition to the actual contents of the balloons, I also read their top, left, and width CSS attributes just like I do for images.

There are lots more ways you can use X-editable in your applications. It supports built-in validation functions so you can check the input before accepting it. For example, execute this JavaScript on the demo page:

$(".canedit").editable({
    type: "text",
    mode: "inline",
    escape: false,
    validate: function(value) {
        if (value.match(/cat/gi)) {
            return "no talking about cats!";
        }
    }
});

Now you won't be able to enter "cat" in the text area. If you try it, you'll get a warning message and the edit won't complete. Your only options are to remove the cat or cancel the edit.

I mentioned above how X-editable has the capability to POST updates back to a server as soon as they are made. I don't use that capability at all, but it also lets you replace that behavior with your own custom handler. Instead of sending the data to a server via Ajax, you could send it via Websockets or update a client-side data model. To take advantage of this ability, specify a function instead of a string as the url parameter. The only catch is that the function has to return a Deferred object so the rest of the X-editable code can process it the same as a jQuery Ajax call. But you should like using Deferreds anyway, right? Check out this code:

$(".canedit").editable({
    type: "text",
    mode: "inline",
    escape: false,
    validate: function(value) {
        if (value.match(/cat/i)) {
            return "no talking about cats!";
        }
    },
    url: function(params) {
        var d = new $.Deferred;
        console.log("id of element changed: " + params.name);
        console.log("new value: " + params.value);
        setTimeout(function() {
            // to simulate some asynchronous processing
            d.resolve();
        }, 500);
        return d.promise();
    }
});

In your custom function, you can access the new value of the element in params.value. If the element that was edited had an id attribute, as my example does, it will be accessible in params.name. If you want to be able to tell your fields apart, you should probably make sure they all have ids! Finally, my example just sets a 500 millisecond timeout before resolving the Deferred, but if the action you need to take has no asynchronous component you could call d.resolve() immediately.

With all of these great tools, creating WYSIWYG editors for web content, even complex content like a comic, is easier than ever before. Now if only writing jokes for the comics was somehow made easier...

Amphibian.com comic for 8 May 2015

Monday, May 4, 2015

The Secret of My Success - a WYSIWYG Comic Editor

One thing I haven't written much about is the editor that I use to make the comics. Sure, the framework that turns the JSON data into a comic is somewhat interesting, but it's mostly just some Jade templates and basic Express routes. The really complex part of the comic is the editor.

When I started out to make a comic, I knew that being able to publish consistently would be a big issue. How many web comics succeed when their publishing schedule is erratic? The key to success, in my opinion, was the ability to make comics quickly. That way I could keep up with a schedule of multiple new comics per week. Ideally, I should be able to throw together a comic in well under an hour. After many months of fine-tuning the editor, that's exactly what I can do.

The Comic Editor in action
This isn't the first time I've done something like this. Way back in the late 90's, before it was popular, I had the idea that web pages with dynamic content should be editable from a WYSIWYG editor that was either built right into the pages or was accessible from an administration page. This was not some high-minded exercise in human-computer-interaction, it was just my way of streamlining the production of web sites for clients. If I could give them a way to change their own page content by clicking on the page and typing new data, they wouldn't have to call me up every time they wanted to have a sale on potato chips (I did a chip factory site once. Really). I could focus on programming instead of fixing basic text, which freed up more time to make more complex web sites. It was a win-win.

Of course, back in those days the interface was a lot less refined. We didn't have all the nice HTML5 and JavaScript capabilities we have today. But it worked, and over the years I've created a number of different editors for different web applications. Most have been WYSIWYG or, as I like to call it, NearlyWYG (pretty close to WYSIWYG, but not exact). My web comic editor looks almost 100% identical to the way the comics look when published, and it's all HTML/JavaScript.

The comic editor gets refined over time as I use it and fight with it. When I find myself going into the JSON directly to tweak some content, I ask myself if I should add a feature (or fix a feature) in the editor instead. There are a lot of front-end JavaScript tricks I learned along the way, so I thought I'd take the next few blog posts to share some of them.

I made today's comic in about 45 minutes, including the time it took me in Inkscape to draw the cat. You're either saying to yourself, "Wow! Only 45 minutes!" or "Yeah? I would have guessed 10" depending on your opinion of my work...

Amphibian.com comic for 4 May 2015

Wednesday, February 25, 2015

Achievement Unlocked

I had a major achievement this week - I guest-authored for Sarah Frisk's web comic Tavern Wenches!

My Guest-Authored Tavern Wenches
I'm happy to help out another webcomic, and I am excited because it makes me feel more like a "real" webcomic author. I mean, it's one thing to call myself a webcomic author because I can put frog graphics on a web page, but I must be a real webcomic author if another webcomic author believes me to be a webcomic author.

Drawing someone else's comic for them is a little terrifying. I didn't think of that before I volunteered, but afterwards I developed some anxiety. I hadn't been reading Tavern Wenches that long (I discovered Sarah's work through my participation in CodeNewbie's Twitter chats) and I was nervous that I didn't have enough background to do it justice. So I read over most of the site's archive a few times. Tavern Wenches had guest authors twice before, so at least I wasn't the first. But I also considered that as an established comic, the regular readers will most likely have expectations that my comic style might not be able to meet. Would people be disappointed with some silly puns and a frog? Finally, with the exception of a crayon sketch of my daughter here and there, I hadn't really drawn a human character in a looooong time. While I did once draw with pencil and paper (a comic strip about a frog that ran in a local newspaper for many years) all my work lately has been in vector graphics. I wasn't quite sure where to start drawing a person again. Could I really do this?

The Last Person I Drew
I wasn't sure. But after a day I decided to do my best and stick with what works for me. That meant Inkscape, frogs, and puns. Being a guest author doesn't mean you have to do everything the way the regular author does it. It's more about bringing your own unique style and perspective to someone else's comic world (at least that's what I decided). So I made an SVG of Veronica and had her do a double act with a frog I styled to appear more medieval. After I had everything arranged in Inkscape, I exported it to a .PNG and sent it to Sarah.

Despite my trepidation, I think it turned out pretty good. It was beneficial for me to work outside my comfort zone and draw a person again. I'm not saying that Amphibian.com will start having human characters, but I won't rule it out simply because I'm not sure that I can draw one.

So in addition to today's regular frog comic, I would encourage everyone to go check out Tavern Wenches - not just my guest comic but be sure to read Sarah's comics as well. I really like her art style. She also does a monthly comic called Monster Markup Manual which features monsters from Tavern Wenches helping to instruct people on software development topics. My frogs should probably look into reading those, because, well, you know...the frogs don't really know what they're doing.

Amphibian.com comic for 25 February 2015

Monday, December 29, 2014

Atomic Feed!

There just aren't enough ways to get my comic delivered to you. In the old days, a newspaper would be dropped off at your house each morning and would contain a page full of comics. You didn't have to go to the publisher and get them. But today, there are no newspapers (well, maybe a few are left) and I am my own publisher. You have to remember to go to amphibian.com every Monday, Wednesday, and Friday to see the comics. Who can remember to do that, with all the other stuff you have to remember every day?
How do you get your comics? (photo by cartese)

I had the idea that I should be supplying an RSS or Atom feed of my comic so those people who use readers like Feedly will be automatically given the new comics when they are available.

Since my comic server application is JavaScript on Node, of course I looked for a module on npm that would enable me to easily create feeds. The top one is simply called feed. And it worked fine. But I found a few limitations and quirks with it when I was creating Atom format.

First, the validation was a little off from the Atom specification. Link is not technically a required element of the feed element, but an error was generated if I didn't include it. It was also putting in a "hub" type link, but that's not an Atom thing - it's from the RSS 2.0 type. There was an option for "image" which mapped to the logo element, but there was not an option for specifying the icon element. The logo should be more like a banner (twice as wide as it is tall) and the icon should be square.

For individual entries in the feed, again link was mandatory when it doesn't have to be. It was also missing the published date option for entries.

Now don't get me wrong, it's a great package. The issues I found are relatively minor, so I forked the project on GitHub and made changes to correct them. My version is available at https://github.com/cwleonard/feed.

I am still playing around with how to structure the content and which optional elements make a difference to readers such as Feedly, so I don't recommend subscribing to the feed just yet. But here's an example of how I am currently using it:

var feed = new Feed({
    id: "http://amphibian.com",
    title: "Amphibian.com",
    description: "A web comic about frogs who run a technology company.",
    link: "http://amphibian.com",
    feed: "http://amphibian.com/feeds/atom",
    icon: "http://amphibian.com/simg/og_logo.png",
    copyright: "All rights reserved 2014, Casey Leonard",
    author: {
        name: "Casey Leonard",
        email: "casey at amphibian.com",
        link: "http://caseyleonard.com"
    }
});

var comics = loadComics(); // get the latest comics

for (var c in comics) {

    feed.addItem({
        title: comics[c].title,
        link: "http://amphibian.com/" + comics[c].id,
        description: "Amphibian.com comic for " + comics[c].pubDate,
        date: comics[c].pd,
        content: "comic html goes here"
    });

}

feed.updated = comics[0].pd; // date of the newest comic

var atom = feed.render("atom-1.0");

I'll be sure to announce when I think it's done and hope to get the comic listed in the "comics" category on Feedly as well.

Amphibian.com comic for 29 December 2014

Monday, August 25, 2014

PhantomJS for Image Capture

I don't know if you've ever noticed, but when you share one of my comics on Twitter, Facebook, or Pinterest (hint: you should be sharing my comic) it is able to grab a preview of the comic that is a static image. This meta tags on the page tell the social media sites which image to use:

<meta property="og:image" content="http://amphibian.com/cell/25.png">

Dynamically Created Image of a Comic
Since the comics are actually SVGs positioned inside <div> tags, it is necessary to create these images somehow. Early on, I created some manually but I really needed an automated method that was integrated with the online editor. Every time I create or update a comic it needs to automatically generate new images.

The solution I came up with was integrating PhantomJS with my web application. You can do a lot of cool stuff with PhantomJS, like automated testing of web applications, but I am just using it for its image capture capabilities right now.

To make an image out of a web page, you just need to make a JavaScript file to control PhantomJS. Here is an example similar to what I use.

var page = require('webpage').create();
page.viewportSize = { width: 1202, height: 5000 };
page.open('http://amphibian.com/basic/25/1', function() {
    page.clipRect = page.evaluate(function() {
        var areaRect = document.getElementById('comicArea').getBoundingClientRect();
        var cellRect = document.getElementById('cell-0').getBoundingClientRect();
        var r = JSON.parse(JSON.stringify(areaRect));
        r.height += (r.top * 2);
        r.top = 0;
        r.left = cellRect.left - 102;
        r.right = cellRect.right + 102;
        r.width = cellRect.width + 204;
        return r;
    });
    page.render('demo.png');
    phantom.exit();
});

Line 1 is just pretty standard, to make a page object. You can read more about the different modules in the PhantomJS documentation.

One line 2, I set the viewport size. This controls how wide my "virtual" web browser client will be. Remember that my comics resize themselves based on the client, so I want to use the maximum size in order to make the rendered image very large. The social media sites almost always shrink the images down and starting with the largest size will result in the cleanest picture in the end.

Line 3 is where I make the call to open the page. When the page is opened, the callback function is called. That's where the good stuff happens.

When you want to make an image out of a web page you can either do the whole thing or just part of the page. If you want to do a partial page, you need to set the clipRect property of the page object before calling render. That's what I'm doing starting at line 4, with the call to page.evaluate.

When calling evaluate on the page object, you pass in a function that should be executed as JavaScript in the context of the page. It would be the same as if the page had JavaScript in a script block.

To make the clip rectangle that I want, I need to get the dimensions of 2 parts of the page - the comicArea div and the div of the first cell. I get those on lines 5 and 6, by simply getting the elements by id and then calling their getBoundingClientRect methods. However, the rectangle I want to return is neither of these exactly. I need to make some adjustments.

On line 7 I do something that you might find a bit strange. I create a new variable r by parsing the JSON returned from a stringifying the areaRect object. Why am I doing this? Because the rectangles returned from the calls to getBoundingClientRect are immutable and I want to make changes. By dumping the object to a JSON string and then reading that string into a new object, I get a mutable copy.

Lines 8 through 12 are the adjustments to the clip rectangle I want to return. I change the top and height of the rectangle around the comicArea and then adjust the left, right, and width values using the corresponding values from the cell as a starting point.

Try it yourself and you should get an image of a single cell of one of my comics. Or change some stuff around and try getting images of other pages.

The complete solution in my web application is slightly more complicated, because I have to dynamically generate the JavaScript for PhantomJS, call PhantomJS from Node, read the resulting image into the database, and then cleanup the temporary files. But we can talk about that some other time...

Amphibian.com comic for August 25, 2014

Friday, August 1, 2014

Web Comic Launch

Today I launch my web comic, which I have so creatively titled Amphibian.com. I've wanted to do a new comic for years, but I was waiting for the right inspiration. Unless you live in central Pennsylvania (inside this area) you probably don't know that 20 years ago I published a comic about a frog in a local newspaper. But newspapers are dead. Print comics are dead. My comic was barely alive in the first place. What's changed?

Web Comics Made with 100% Real Web


If you've ever read a comic on your computer or mobile device, you may have noticed that most of them are pretty much just print comics converted to a JPG or PNG and stuck on a web page. Most are probably made by talented artists, working with some artisty tools, maybe on a computer but maybe still on paper. Anyone who's ever seen my work knows that I am not a talented artist. But I am pretty good with the technologies that make web sites. And I have been drawing a frog so for so long that it looks like an almost legitimate frog.

So I decided to make a web comic that was made with real web technologies. I designed it to be optimized for mobile devices, and easy to share with your friends. Here's a breakdown of what it's made of.

  • HTML + CSS: The comics are just HTML markup and CSS styling. I didn't draw a box and then draw some stuff in it, I style a <div> to have a border and then position individual <img> objects in it to arrange the scene. Even the speech balloons are just <p> tags with appropriate styles applied.
  • SVG: I take responsive design to a whole other level. When you look at the comics on your phone, not only is the page header and navigation being restyled to fit the smaller screen, the comic images themselves are scaled down as well. Have a retina display? Make the page as big as you want, my frogs won't get all pixelated on you. And with Gzip compression, most of the images are actually smaller downloads than a large PNG would be.
  • JavaScript: In both the client and server, I'm using JavaScript to make things better. In your browser, jQuery lets me animate parts of the comic scenes easily. I can make things change when you click (or touch) the characters in the cells. These can be part of the jokes or just for silly fun. The back-end is made with Node and the Express framework. The part that you don't see is an editor that combines client-side goodness with REST web services to enable me to take the ideas from my head and put them in the comics with ease.
Comic Editor

Why Would Someone Do This?


Are there advantages to this approach to a web comic? I think there are many.

First of all, using web technologies to make web comics really uses the medium to its fullest. Web sites don't just look like newspaper pages (at least not anymore) so why should comics? We have all these great features in our web browsers and we are barely using them. I want to use them just a little bit more.

Second, comics made this way are mobile-friendly. They read top-to-bottom and don't require you to pinch or swipe or poke or jab or jump or anything. If you've ever tried to read a "normal" comic on a mobile phone in it's typical configuration - a.k.a. portrait mode - you know what I'm taking about. My comics scale to your device without giving up image quality and can be enjoyed with just your thumb on the phone.

Also, the HTML content can be read by the crawlers and even translators. Like I mentioned, the words my frogs speak are just normal HTML paragraph tags. The contents of my comics will be picked up by the search engine spiders and make it easier to find my stuff. And a tool like Google Translate can show you the comics in lots of other languages. I'm not sure if the jokes make sense in Swedish, but the words might!

Take My Code, Please


As always, my code is open source and on GitHub. My frogs and jokes aren't, but you probably don't want them anyway.


Amphibian.com comic for August 1, 2014