Getting ideas out of people’s heads and into the real world is a goal that’s key to what Teespring does, and our t-shirt design tool is front and center in that process. We put a lot of effort into making the experience as useful and performant as possible, and we had a lot of fun working with the modern browser graphics stack on the way.
There are a lot of exciting new graphics technologies on the web now, from canvas, to SVG, to complex animations in plain CSS. Each one can do some pretty fun stuff on its own, but when you use them all together you have the tools you need to build a fully featured graphics app. Teespring uses a layered DOM UI, an SVG design surface, and a few canvas instances to achieve all the expectations we have for this tool.
We want our users to be able to instantly visualize their design on any product, in any color we offer, and we use a fun bit of DOM trickery to achieve this. We stack five different layers of DOM elements on top of each other, with each one contributing to the overall visualization of a design on a t-shirt.
On top of everything is our UI layer, which is what the user interacts with. All the buttons and handles that the user can manipulate are implemented in the DOM just like normal, which allows us to take advantage of all the usual tools for capturing mouse events as the user interacts with their design.
Next up is our ghosted product image, which you might have thought was just a normal image of a t-shirt at first glance. As you can see here, the shirt is what’s transparent and the background is actually fully opaque. This will allow us to change how the shirt looks deeper down in our layers.
The user’s design itself is sandwiched right in the middle of everything. This allows us to show it to the user in the most realistic way possible, with boundaries and wrinkles from the ghosted image having an effect on the design. There is no fancy edge detection happening when the user drags their art over the edge of the shirt, it’s just going behind the opaque part of the image.
Next, the texture layer allows us to add effects for more detailed types of clothing dyes. We can load in any semi-transparent image as a texture, and it will produce a realistic looking effect on the product.
And finally, the product color layer let’s us instantly change the product to any color the user wants. All we do is change the background color of a big rectangular div and let it show through the layers above.
SVG Design Surface
There’s a lot going on in all these layers, but the SVG layer is where the magic happens. The SVG directly holds the user’s design and updates live whenever changes are made. It needs to be fast, portable, and, since we’re designing real world products here, we need to be able to get it onto a t-shirt. SVG fits these criteria nicely.
For the uninitiated, SVG stands for Scalable Vector Graphics and is different than many other graphics formats. Instead of representing pixel data in binary, SVG represents shapes and paths in a human-readable XML format.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <ellipse cx="100" cy="100" rx="50" ry="50"/> </svg>
If you guessed a circle, you can read SVG.
Because image data is encoded as paths instead of pixels, you can scale an SVG up to any level of detail with no pixelation. This is very useful when you’re going to turn something into a bunch of silk screens and transfer it onto a physical product of any size.
Rendering in SVG
Depending on the type of content that a user adds to their design, we have a few different ways of rendering that content into SVG.
For text, simply enough, we just insert an SVG
<text> node into the SVG.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <text x="0" y="100">asdf</text> </svg>
Text in SVG isn’t a perfect match to everything you might need for rich text editing on a t-shirt, but it gets the job done. Coming from the DOM, you’ll immediately miss not having text alignment available to you. There are also some quirks with determining the dimensions of the rendered text when using different fonts. SVG text does support some interesting graphics tricks like text on a path, though, and it does support all the web fonts that you’re used to from the DOM. Overall, it makes up for its shortcomings by being simple to use.
We also provide a bunch of clip art for our sellers to use via the Noun Project. These images are provided as SVGs, and lucky for us, you can easily use an SVG within another SVG:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="clipart"> <ellipse cx="100" cy="100" rx="50" ry="50"/> </svg> </defs> <use xlink:href="#clipart"></use> </svg>
Best of all, you can still style the embedded SVG. We use this fact to allow our users to change colors and outlines to art to customize it to their design.
Lastly, we allow users to design with whatever images they want via uploads.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <image xlink:href="./hamburger.png" x="0" y="0" width="50" height="50"></image> </svg>
Allowing the user to inject whatever images they want into our neatly defined design surface presents a few extra challenges, however. The design you see on the screen is just the beginning of our process at Teespring; if everything goes well, this is going to end up being a physical product. We need to know the physical location and and color of every region of ink that will go onto the product. Because of this, we need to take a look at the data given to us at the pixel level and figure out some extra information about these uploads.
Canvas Image Processing
Fortunately, the modern web gives us the tools we need once again. Canvas provides a simple API for for reading and writing raster image data. It can be used for drawing pixel by pixel without the extra features (and extra baggage) of the DOM.
If we render an uploaded image into a hidden canvas element, we can use the getImageData method to read the RGBA values out for each individual pixel as 8 bit integers. Using this, we can calculate useful information like the location of opaque regions that will need to be printed, and what color inks we’ll need to use to print those regions. Doing this at upload time on the client allows us to give the user immediate feedback on their design as they work.
It’s also possible to do the reverse in canvas and write RGBA data back to the canvas. This gives you the power to do all sorts of really fun image processing. Check out the canvas tutorial on MDN if you’re interested in learning more.
Bringing it to Life
Getting all of this on the screen is only half of the story. Most importantly, we need to let the user edit their design live at 60 frames per second. We use a bit of knowledge of the user’s design and the SVG transform attribute to bring everything to life.
This one attribute is a little unintuitive to look at, but it provides some great benefits. It consists of six numbers that encapsulate all of the information needed to specify a change in position, scaling, and skew (or any combination thereof). It uses a fun bit of matrix math to calculate these transformations in a way that’s able to be combined with other transformations while being unambiguous to order. That means that no matter how complicated the user’s image manipulation gets, we can always render it by running a single matrix multiplication on each key point in the design.
If you’re interested in learning more about the math behind 2D matrix transformations, I highly recommend playing around with this sandbox tool by Rocco Balsamo. I promise you’ll find the above comic funny once you’re done.
As the user clicks, drags, and manipulates their art, we manually calculate this matrix and apply it to the highest level element in the SVG that the user intends to transform. The result is that even when the user is dragging around a complex piece of art with many paths, like this:
We still render a snappy 60FPS experience for our users despite the complexity.
What We Learned
The graphics stack in modern web browsers isn’t perfect. It can be daunting looking at canvas, SVG, the DOM, and even WebGL and wondering which one fits your app best. For those coming from native graphics application development, the tooling might not be quite at the level you’re used to, and you’ll have to deal with the additional issue of browser compatibility.
Just like everything else on the web though, you can’t beat the accessibility. Teespring needed something that anyone with a great idea could stumble onto and use to create a product people want, just as well as someone that makes a living on our platform could use to launch dozens of items per day. For that goal, the web allowed us to build something that fit our needs well and that we had a lot of fun working with.