Mobile, Global Variables, and Other Things That Hurt Less Than You Think - RaphaelJS: Graphics and Visualization on the Web (2014)

RaphaelJS: Graphics and Visualization on the Web (2014)

Chapter 8. Mobile, Global Variables, and Other Things That Hurt Less Than You Think

Up until about a year ago, my strategy for dealing with people with mobile browsers was to pretend they didn’t exist. This worked reasonably well. Alas, times change.

The number of viewers accessing a site from a phone or tablet varies dramatically depending on the type of site. For example, if you’re working on the website for the Laptop Fan Club, this might not be a concern. In almost any other situation, it’s something you have to deal with.

When it comes to visualizations, there are some practical decisions to be made about what’s realistic on different screen sizes. A county-level map is not going to be all that informative on a 300-pixel screen. But in many cases, you do want a visual to resize dynamically to fit whatever size screen is loading it.

This is generically known as responsive design, and there are sophisticated libraries out there like Bootstrap, an open-source project from Twitter that intelligently moves or hides parts of a page (like menus and sidebars) depending on the size of the screen. The idea is to code your website only once, not once for every type of device that might hypothetically access it.

Raphael predates the responsive design movement, but in fact scalable vector graphics are absolutely perfect for responsive design since they are, as you may have divined, scalable vectors. Since they’re drawn dynamically, you don’t have to worry about the nasty side effects you get when a photo that’s supposed to be one size is squeezed into a smaller container. You just have to do a little extra work to make SVGs adjust to their surroundings.

Measuring the Container

Let’s freshen up on dynamically resizing containers. The easiest way to do this is to use CSS to set the width of a block-level element, like a regular old <div>, to 100% (or any other percent you like).

You can then measure the width of the element by finding it with JavaScript and retrieving the offsetWidth property.

<style>

#canvas {

width: 100%;

height: 300px;

border: 1px solid #CCC;

}

</style>

<div id="canvas"></div>

<div id="output"></div>

<script>

var w = document.getElementById("canvas").offsetWidth;

document.getElementById("output").innerHTML = w;

</script>

See this code live on jsFiddle.

It’s very common for viewers using smartphones or tablets to rotate the device to get a better view, so we want to detect the width when this happens. In native JavaScript, you can do this using the onsize event listener attached to the global window variable:

function resize() {

var w = document.getElementById("canvas").offsetWidth;

document.getElementById("output").innerHTML = w + "<br />" +

document.getElementById("output").innerHTML;

}

// page load

resize();

window.onresize = function() {

// also fire on resize of page

resize();

}

See this code live on jsFiddle.

Now the width of the container is measured both right away and whenever the page resizes. You can test this functionality in a browser by manually resizing the browser window with the mouse.

WARNING

Assigning a function to window.onresize overwrites other functions already attached to this event, which can cause problems on pages using other libraries as well. It’s best to use jQuery or at least be very mindful of what else might be sensitive to page resizing.

Let’s put some stuff on the page to experiment with:

var paper = Raphael("canvas", 200, 200);

for (var c = 0; c < 20; c += 1) {

paper.rect(c * 10, c * 10, 10, 10).attr("fill", "hsl(" + Math.random() + ",0.5,0.5)");

}

Our goal here is to resize the objects dynamically to fit their container. Now that we have the width of that container, along with a function that conveniently fires every time that width changes, we could pretty easily use the transform method to do this. But that requires locating every object on the canvas, or at least dutifully placing each one in a global set of objects, which requires more tedious coding. As always, there’s a better way.

We’re going to use two methods of the paper object here: .setSize(), which adjusts the size of the paper object, and .setViewBox(), which adjusts the size of objects on that paper. The former is straightforward, but the latter requires a little bit of explanation.

Imagine the objects that you create with Raphael painted on the surface of a balloon that has been stretched a moderate amount. To resize them all at once, you might stretch out that balloon more, thus taking up a larger overall amount of area, or ease the tension to shrink them, thus taking up less area. The viewBox in the SVG specifications functions like this balloon.

Setting the viewBox to the native size of the objects—whatever area they take up before any transformations—then adjusting the size of the canvas functions like the easing or stretch of the balloon. Observe:

var paper = Raphael("canvas", 200, 200);

for (var c = 0; c < 20; c += 1) {

paper.rect(c * 10, c * 10, 10, 10).attr("fill", "hsl(" + Math.random() + ",0.5,0.5)");

}

// viewbox takes (x, y, w, h);

paper.setViewBox(0, 0, 200, 200);

function resize() {

var w = document.getElementById("canvas").offsetWidth;

paper.setSize(w, w);

}

resize();

window.onresize = function() {

resize();

}

Now that we have this resize function set up, we’re going to use a Raphael method called .setSize() to dynamically resize the canvas:

function resize() {

var w = document.getElementById("canvas").offsetWidth;

paper.setSize(w, 200);

}

See this code live on jsFiddle.

Fire that up and drag the browser to differnt sizes and you’ll see the 20 blocks we just made resize along with it.

Of course, you could easily add constraints to the resize function that prevent it from getting absurdly small. But the browser will handle it even if you don’t.

Responsive design is a rich and complex subject, but at its core it’s about moving and resizing things on the page according to the room they’re given. The simple code here gets you 90% of the way. The other 10% is up to you.

Raphael in Every Context

We have begun every example in this book so far with some variation on the same line of code:

var paper = Raphael("canvas", 500, 500);

We’ve always either referred to a large <div> element or placed the canvas object directly into the DOM. Now I’d like to show you an example of how to make Raphael blend in with other elements on the page.

Let’s say we have a logo for a restaurant called “Paper Moon,” for which we want to replace the vowels in “moon” with actual little moons.

First, we’ll make a function to draw the shape of the moon, taking as an argument the phase from zero to one. (Technically, the moon appears to wax and wane from different directions, but here at Paper Moon we’re more concerned with fine cuisine than with astronomical accuracy. But I invite you to fix it.)

function shape(phase) {

phase = typeof phase === "number" ? phase : 0.25;

// limit phase to [0,1]

phase = Math.max(0, Math.min(1, phase));

// convert to [-1,1]

phase = (phase - 0.5) * 2;

// left arc

var path = "M" + opts.r + ",0";

path += "a" + opts.r + "," + opts.r + " 0 0,0 0," + opts.r * 2;

var clockwise_flag = phase > 0 ? 0 : 1;

phase = Math.abs(phase);

// avoid divide by zero

phase = phase || 0.0001;

opts.inner_r = opts.r / Math.pow(phase, 0.5);

path += "M" + opts.r + "," + opts.r * 2;

path += "a" + opts.inner_r + "," + opts.inner_r + " 0 0," + clockwise_flag + " 0," + opts.r * -2;

return path;

}

Next, let’s make a function that makes a moon and returns an object. For good measure, we’ll return it with a method for changing the phase of said moon after it is instantiated.

function moon(opts) {

opts = opts || {};

// set defaults (using ternery if/else statements)

opts.r = typeof opts.r === "number" ? opts.r : 100;

opts.phase = typeof opts.phase === "number" ? opts.phase : 0.25;

opts.x = typeof opts.x === "number" ? opts.x : 0;

opts.y = typeof opts.y === "number" ? opts.y : 0;

if (opts.el && typeof opts.el === "string") {

var paper = Raphael(opts.el, opts.r * 2, opts.r * 2);

} else {

var paper = Raphael(0, 0, opts.r * 2, opts.r * 2);

}

var shadow = paper.circle(opts.r + opts.x, opts.r + opts.y, opts.r).attr({

'stroke-width': 0,

fill: '#999'

});

function shape(phase) {

// see above

}

var orb = paper.path(shape(opts.phase)).attr({

'stroke-width': 0,

stroke: "#999",

fill: "#FF9"

}).transform("T" + opts.x + "," + opts.y);

return {

setPhase: function(new_phase) {

orb.attr("path", shape(new_phase));

}

}

}

Okay. Try it out if you don’t believe me.

To make the logo, we could use Raphael’s .text() function to draw all the letters other than the two O’s. But I don’t particularly like text in the SVG specifications because it’s such a pain to manually space everything out. We would need to figure out the distance that the two moons occupy and then resume the “n” in “moon” at exactly the right place. If only we had a technology that could snap to fit text on the page and take care of all the spacing itself.

Wait, we do! It’s called HTML. Instead of rendering the entire logo in Raphael, we can make most of it the old-fashioned way. Unlike previous examples, we’re going to use a <span> as the container for the canvas, meaning that it will display inline next to the letters.

<style>

.sign {

background-color: darkblue;

width: 375px;

padding-left: 25px;

}

.sign span {

color: silver;

font-family: "Arial";

font-size: 48px;

}

</style>

<div class="sign">

<span>PAPER M</span>

<span id="canvas1"></span>

<span id="canvas2"></span>

<span>N</span>

</div>

<script>

var m = moon({

el: "canvas1",

r: 18,

phase: 0.75

});

var m = moon({

el: "canvas2",

r: 18,

phase: 0.75

});

</script>

image with no caption

Ta-da! I would eat here every day. (There is actually a restaurant called Paper Moon where I live, but all I remember about it is that I spilled tomato sauce on my tie.)

See this code live on jsFiddle.

Stealth Raphael

The above example may seem like a laborious way to demonstrate that Raphael can accept the id of a <span> element when at this point you probably would have taken my word for it. But I want to use our Moon Generator to demostrate a few other useful ways to make Raphael work for you.

You may have noticed that I built a little flexibility into the opts.el parameter. If a user passes a string, it uses that string as the ID of the element to house the paper object. If it’s not a string or not present, we append the paper object to the DOM.

There’s one more addition I’d like to make: allowing users—whether they be other humans with whom we share code or merely our future selves—to pass a pre-existing Raphael object.

The paper object is just that—an “object,” as you can see by adding a console.log(typeof paper); somewhere in your code. But a lot of things are objects, so we ought to test to see if what the user passed is really a Raphael object.

If you log the paper object to the screen, you’ll see it has a property named canvas. That seems unique enough:

if (opts.el && typeof opts.el === "string") {

var paper = Raphael(opts.el, opts.r * 2, opts.r * 2);

} else if (opts.el && typeof opts.el === "object" && opts.el.canvas) {

var paper = opts.el;

} else {

var paper = Raphael(0, 0, opts.r * 2, opts.r * 2);

}

This way, we can make lots of moons on the same canvas, like one of those really expensive watches (it’s amazing what inspiration one can find in in-flight magazines).

var paper = Raphael("canvas", 500, 40);

for (var c = 0; c <= 1; c += 0.1) {

var m = moon({

el: paper,

r: 20,

phase: c,

x: c * 420,

y: 0

});

}

image with no caption

See this code live on jsFiddle.

What this also means is that, because the function falls back on making its own canvas object, invoking it doesn’t even require knowing that Raphael exists. If you were to distribute my moon function—and feel free to, it’s all yours—other people can use it without reading this book… Though they really ought to.

That said, they’ll still need to include the raphael.js script in the page before this function will work. Let’s talk about ways to make that a little easier.

Raphael Plus Require.js, Browserify, or Another AMD Framework

For small projects like my modest (and fictional) restaurant, it’s not really a big deal to just add a <script src=raphael.js></script> line.

In fact, Raphael is small enough—about 90Kb—that, if you use some sort of content management system that automatically adds the same header to every page, you can include Raphael in every page without much a performance hit to pages that don’t need it. (Most modern browsers and servers automatically compress files, so it’s typically much smaller.)

Still, most people will tell you that, for any sufficiently large site, every byte counts. Additionally, most large sites, like news publications or large retailers, probably have a lot of other JavaScript libraries firing off—those that track page views, those that serve advertisements, etc. The person in charge of overall site performance and stability, if it’s not you, is probably not a person who’s happy to toss any old library onto the page just because you read a book about it.

One of the main concerns in these situations is that the library will contribute global variables to the page that will accidentally overwrite other global variables from other libraries. This is one of the biggest difficulties in large-scale JavaScript development today. Because it was not designed to be a highly “module” language, with easy ways to drawn in libraries as you need them (the way you might in Python or NodeJS), you’re playing with fire if you start mixing and matching a lot of third-party code.

Raphael has a small global footprint, so it’s pretty safe. But to be extra cautious, you can use it’s “ninja” mode to condense it all into one global variable, Raphael. Other libraries, like jQuery, call this “no conflict” mode. I can only hope “ninja” is a nod to the red-banded Teenage Mutant Ninja Turtle of the same name as this library.

In ninja mode, you wrap all of your Raphael code in a closure and pass that function the library itself:

(function (local_raphael) {

var paper = local_raphael(10, 10, 320, 200);

})(Raphael.ninja());

This function fires once when the page is ready, and whatever you do inside of it is protected from whatever other disasters your webmaster has inflicted on the global JavaScript environment.

RequireJS

If you use RequireJS to load external libraries—something that’s a bit outside our scope here—you’re probably used to ugly hacks to get libraries to work in that environment. Raphael is ready for you. It has a line in the source code to check for the presence of RequireJS and load itself correctly for use in that environment.

Browserify

I have recently become a convert to Browserify, a Node.js module that allows one to include modules as though he or she were writing Node, a server-side implementation of JavaScript that has taken the scene by storm in past several years. After doing so, you run a simple command line statement to wrap all of the code—the Raphael source, your own code, and anything else you included—into one compressed file to include on your page. This eliminates the need to manually include the raphael.js file on the page.

When you install Node, it comes prepackaged with a command line tool called npm (“node packaged modules”) for painless installation of third-party code. To install Raphael as a node module, you would enter this command:

npm install raphael

This will download the Raphael source code as a module that is correctly packaged for use in Node (meaning it has a few extra JSON files pointing Node in the right direction). Your code will then look like this:

var Raphael = require("raphael");

var paper = Raphael(0, 0, 500, 500);

var circle = paper.circle(100, 100, 50);

After saving this file as something like code.js, you would run this:

browserify code.js > script.js

The script.js file now contains the Raphael source code and the above. As a bonus, it automatically runs inside a function, so circle, paper, and whatever else you declare will not find their way into the global namespace.

Final Thoughts: The Future of Raphael and You

As recently as July 2013, Raphael creator Dmitry Baranovskiy (whom I do not know personally) tweeted that he is still working on Raphael and that future versions are forthcoming. If you poke around on the Github repository for the project, you can see active development and bug fixes.

At the same time, Baranovskiy, who works for Adobe, is also working on a very similar library called Snap.svg, which leaves behind support for old browsers in favor of the extra capabilities of modern ones. If you look at the sample code for Snap, you will see that it is nearly identical to Raphael.

Even though D3 has carved out a respectable place as the premier library for complex SVG graphics, I have no qualms about continuing to use Raphael for everything that is covered in this book and more. This is both for practical reasons—the IE8 holdouts will continue to cling to that awful browser for years to come—and because it is elegant and easy.

There’s one other reason: Raphael makes drawing on the page easy, but not so easy that you don’t still get a little paint on your fingers. There’s an almost ineffable joy in fiddling with code, refreshing the browser, and being surprised—sometimes even pleasantly—by the result. I have never found an environment where the connection between one’s ideas and the output is so thin and natural. Coding is turning ideas into instructions. I hope this book has given you enough command of the latter that you can explore the former to your heart’s content.

Colophon

The animal on the cover of RaphaelJS is a Nile Valley Sunbird (Hedydipna metallica), a colorful passerine (perching) bird that is commonly found in the Middle East and northern Africa. Every February, the male Sunbird grows “nuptial plumage,” which are vibrantly colored feathers that he displays to impress the females of the species.

The nuptial plumage tends to consist of glossy green/blue/violet feathers on the back and sides with a brilliant yellow underbelly and one or two long tail streamers. This is in stark opposition to the normal appearance of males and the year-round appearance of females: a musty brown body with a cream and dull-yellow colored belly and short tail. The mating display occurs for days, with the male being careful to display his plumage to the female and gain her attention through short calls that grow louder as the day continues. The male’s bright plumage starts to fade after two to three months, and then the two sexes become almost physically indistinguishable.

Sunbirds require good sources of nectar, and are similar to hummingbirds in their feeding behaviors—they are quite small (only 15cm long at their largest) so they can dart and flicker around very quickly, and even have a hummingbird-like beak that is best suited to trumpet-shaped flowers. Although the Nile Valley Sunbird population has not been officially quantified, it has been designated as stable because of the frequency of sightings and the birds’ large range of habitat. Sunbirds are best known for being frequent visitors to the famous walled gardens of Oman, Yemen, Saudi Arabia, and Egypt.

The cover image is from Meyers’ Kleines Lexikon. The cover fonts are URW Typewriter and Guardian Sans. The text font is Adobe Minion Pro; the heading font is Adobe Myriad Condensed; and the code font is Dalton Maag’s Ubuntu Mono.