Josiah Winslow Profile picture
Jan 1 30 tweets 14 min read
I'm a web developer, and a lover of anagrams.

Here's how I create animated #anagrams for the @AnagramPost. (LONG THREAD 🧵)

#webdev #html #css #javascript #php #thread
In November 2022, I made a Python program to animate anagrams of tweets as an experiment.

It wasn't very easy to use or maintain, though. It's over 1,000 lines, ~160 of which are to work around Pygame being unable to wrap text or render emojis in color.
Sometime later, I decided to try refactoring it to make it less of a headache.

But partway through, I thought to myself, "Wait, why am I trying to recreate a web layout in Pygame? I should just...do it on the web!"

So I did.
It certainly helped that, when I created pelicanizer.com, I figured out how to recreate a tweet layout HTML/CSS from scratch, using what I learned in college.

It was just a matter of making the displayed info customizable, with a (relatively) simple set of inputs. A webpage for recreating the visual layout of a tweet. A pre
To create the actual animation takes quite a few steps.

1. Set the GIF dimensions
2. Turn each phrase into a list of letters with X/Y positions
3. Create a smooth path between each matching letter
4. Render frames of letters following their paths
5. Turn the frames into a GIF!
Step 1 is the easiest.

I just have to inject both phrases, and set the width and height of the text container to the highest values they took. Then, the GIF dimensions are the dimensions of the container for the entire layout. A VS Code screenshot. This JavaScript code sets the width an
Step 2 is also very manageable.

As it turns out, a Range object can contain a fragment of a document (such as a single character of a text node), and getBoundingClientRect can get the X/Y position of that range. A VS Code screenshot. This JavaScript code loops through eac
Step 3...let's just say, it took some work.

There are multiple parts to this step.

3a. Partition each phrase into 1-or-more-letter segments, which can be rearranged into each other
3b. Match each segment in both phrases
3c. Create Bezier curves between their letters' positions
Step 3a ensures repeated words/fragments move together, and effectively act as one letter.

To do this, I can find a Minimum Common Substring Partition (MCSP).

Partition = breaking up into segments
Common Substring = repeated words/fragments
Minimum = as few segments as possible
As it turns out, MCSP is an NP-hard problem (read: slow).

But if I don't require the partition to be *minimal*, there are good-enough greedy algorithms that run in polynomial time (read: fast).

Getting one to work was simple enough. Here's an example of such a string partition. A VS Code screenshot. This JavaScript code is the first few A JavaScript console window, which reads:  > partitionAnagra
In Step 3b, I turn phrases A and B into partitions, and for each segment in partition A, its letters' destinations are set to those of a matching segment in partition B.

I'm not proud of my solution for finding the matching segments, but hey, it works. A VS Code screenshot. This JavaScript code uses a common sub
Step 3c took much inspiration from @FreyaHolmer's fantastic video on Bezier curves. Without it, I would've been lost.

Basically, Bezier curves are a type of spline — a smooth curve used to join points together — and they can define continuous paths.
Each path followed by the letters is a cubic Bezier curve, with 4 control points.

For simplicity, I decided to place the middle control points like this. They're placed some amount upwards (or downwards) based on the X distance the letter has to travel.
But in cases where such an arcing path would look awkward, I place the middle control points in between the Y positions of the first and last control points.

I have a formula for deciding when to switch to this path shape. Don't ask me how it works. 😅
With that out of the way, we can almost move on to step 4 (rendering frames). But that step has its own complicated parts.

4a: Convert each image (including Twitter emojis!) to a data URL
4b: Render a background plate without the letters
4c: Render frames with letters one by one
Step 4a needs to be done because a pesky problem having to do with Cross-Origin Resource Sharing (CORS).

Basically, if you draw onto a <canvas> an image from another source without CORS approval, that canvas becomes "tainted", and its data can't be read. developer.mozilla.org/en-US/docs/Web…
However, we can get around this entirely by converting the images to data URLs.

Data URLs are URLs that represent files. When you visit one in a browser, instead of fetching the file from an external resource, it decodes the data from the URL itself. en.wikipedia.org/wiki/Data_URI_…
Even though I have almost no experience in PHP, I was able to whip up a small PHP script that would request a URL (cURL is a lifesaver!) and convert it to a base64-encoded data URL.

My JavaScript code calls this PHP script on each <img>'s src and replaces it with the data URL. A VS Code screenshot. This PHP code requests an image and co
This conversion is done for profile pictures, embedded media, and (perhaps surprisingly) emojis!

All the way back in step 1, I used a function called twemoji.parse() to convert emojis to emoji images, which are called "Twemoji" and provided by Twitter. twemoji.twitter.com
(Relatedly, I also used a function called addTwitterLinks(). This also uses a resource provided by Twitter, but annoyingly, it parses Twitter usernames like @<a>TwitterDev</a> instead of <a>@TwitterDev</a>. Simple to fix, but again, this is annoying.) github.com/twitter/twitte… A VS Code screenshot. This JavaScript code uses twttr.txt.au
Step 4b requires me to convert an HTML element to an image using JavaScript...somehow.

This would be completely infeasible without the immensely helpful dom-to-image library. Ever since I first heard of it in late 2019, it's been an invaluable resource. github.com/tsayen/dom-to-…
With that, we can convert our layout container to an image in one line of code!

(Note: for whatever reason, it seems to put a weird offset on the image if there is a margin on the element, so I pass an option to remove the margin. We don't need it anyways.) A VS Code screenshot. This JavaScript code selects an HTML e
Step 4c (actually rendering the frames) is easy, and it's a breath of fresh air after the sheer complexity of the previous steps.

(glyph.update() and glyph.draw() are custom functions that set the letter's position and draw it to the canvas context.) A VS Code screenshot. This JavaScript code renders animation
Finally, at step 5, we can turn these frames into a GIF. To render my GIFs, I use gif.js from @almost_digital. jnordberg.github.io/gif.js

It requires me to store the file locally because it uses Web Workers, but that's a small price to pay.
All it takes is a few event handlers, and we've got ourselves a rendered GIF!

(The "progress" event is undocumented, as far as I'm aware, but it is handled in the demos. It returns a number from 0 to 1, indicating the progress of GIF rendering.) A VS Code screenshot. This JavaScript code uses gif.js to re
Now that every basic step of rendering an anagram animation is complete, it's easily extensible to layouts other than tweets.

This anagram of a headline from @axios was my first experiment with a different layout.
All that's required for a change of layout is a change of HTML (with CSS to accompany it, of course). To accomplish this the least awkwardly in JavaScript, I used @optimizoor's Aris library to great effect. github.com/Vectorized/Aris
My "themes", as I call them, are defined with default textual content, a set of options, and an HTML creation function.

Aris makes the create functions look very clean...most of the time. (Although the more complicated ones are almost certainly my fault!) A HuffPost-like headline layout. It reads:  Example HuffPostA VS Code screenshot. This JavaScript code defines a HuffPosA Daily Beast-like headline layout. It reads:  Franchise (->A VS Code screenshot. This JavaScript code defines a Daily B
These tweets are my first times showcasing the previous layouts with headline anagrams. Nothing else needed to be added to the main code to support these layouts — just some CSS, and a little bit of JavaScript.


If you want to see the end result of all of this, I've temporarily put it here until I know where else to put it. pelicanizer.com/newsites/anima…

It still needs a ton of work to become more user-friendly (for instance, I need a lot more error handling), but it gets the job done!

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Josiah Winslow

Josiah Winslow Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!

:(