Dissecting A Dweet #6: Breaking Broke

Today we will examine the JavaScript code to make this awesome shatter effect. The concepts demonstrated by this dweet can be used for making a variety of cool effects. In one of my previous posts I showed a similar technique for making text spiral.

This effect can even be animated with just a bit more code which I will share at the end of the post. Keep reading full explanation of how it works!

with(x)save(beginPath(font="85em'")),moveTo(j=1e3,540),clip(arc(j,540,2e3,a=t*6.3+9,a+.1)),restore(t>1||fillText("BROKE",29*S(a**3),j,1900))

I love playing with typographic effects and have published over forty plays on the concept of a word being drawn with effects that demonstrate that word. This is probably the single most striking variation on the theme. The broken effect originating from center of the letter O really helps sell it. The code is tightly packed but fairly straightforward.

Here’s the actual dweet running live in an iframe, you can play around with the code while we talk about it!

Our first step is to reformat the code so it’s easier to read. I’ve just added some whitespace to make it flow better. Here’s a link to open the code in my custom JavaScript live editor, CapJS. This is a tool I’ve put quite a bit of work into, so it’s quite nice and helps to follow along on your second monitor.

with( x )
save( beginPath( font = "85em'" ) ),
moveTo( j=1e3, 540 ),
clip( arc( j, 540, 2e3, a=t*6.3+9, a+.1 ) ),
restore( t>1 || fillText( "BROKE", 29*S(a**3), j, 1900 ) )

The basic plan is to break the canvas into shards that clip what is being rendered. Then we can render the same text into each shard with a slightly shifted position to make it look like the shards are at different angles. Here’s a special slow motion animation I made to help explain how the code works…

Alright, let’s walk through what each line of code does.

with( x )

First we use the with statement to extend the scope of x (the canvas context created by dwitter). This eliminates the need for a “.x” prefix, saving 2 bytes for every call to x.

Every call to x must be in the next statement to make this work. That is done here by combining each line with a comma rather then a {} block to save 1 byte.

save( beginPath( font = "85em'" ) ),

This dweet will be using a nesting technique to save space. This can be done in cases where the parameters passed to the functions are ignored. Since otherwise the parens would be empty this saves us 1 byte for each nested statement. Again, just to be clear, font is not used by beginPath, and beginPath is not used by save. Just be aware that the code executes from the inside out, or somewhat confusingly from right to left.

So, first we set the font to “85em'”. This creates a default 85 point font which is large enough to fill the entire canvas. I also want to point out that there is a single quote at the end of the string which is necessary to make the font statement valid.

Each frame we will be clipping to a new shard so we must call beginPath to clear out the old one. We also need to call save so we can later clear the clip state by calling restore.

moveTo( j=1e3, 540 ),

To make the effect originate from the center of the screen, we will first move to the center of the canvas. The canvas is 1920×1080 so (1000, 540) is close enough.

We could center it differently though, the image to the left shows a variation with the Y centering coordinate way off the top of the canvas at -1000.

The true X center is 960 but we will save space by using 1e3 (1000 in scientific notation) and storing it into j. When we need to use the value 1000 in the rest of the code, we can now just use the variable j.

clip( arc( j, 540, 2e3, a=t*6.3+9, a+.1 ) ),

Again we are nesting statements to save space, the clip function doesn’t actually use the output from arc for anything.

First we will center our arc in the same place we just moved to, (j, 540). We want the arcs to extend fully beyond the screen so a radius of 2e3 covers that.

For the angle a we want to rotate around the screen as time passes so that is achieved by multiplying t (time) by a constant. The value 6.3 radians controls the shard angle which is 2 * PI rounded up. This is because we want it to rotate once per second.

We will also add 9 to the angle because it will later be reused as a seed for random number generation. To give the shard some thickness we add .1 radians to the end angle, calculated from PI*2/60 because our code expect t to be updated at 60 frames per second. Rounding down to .1 leaves just a little bit of white space in between shards. The image to the right shows what it would look like with wider shards.

After the arc has been plotted we can call clip to set the clip region. This prevents anything outside the clip region from being rendered and is really the key to the whole effect!

restore( t>1 || fillText( "BROKE", 29*S(a**3), j, 1900 ) )

More nested statements here. First we check if t>1 because we want to stop after circling around once. The or operator || prevents the fillText from happening after one second.

The fillText command is what renders our text “BROKE” to the canvas. The X coordinate of the position is randomized by calling S(a**3). This takes the sine of the cube of the current angle which produces a somewhat randomized result.

This is why it was important earlier to add 9 to a, because lower values produce less random results. A constant higher then 9 would be better but there was only enough space for 1 digit. The image to the left shows what it would look like without adding 9 first. The drawing starts from the 3 o’clock position, and you can see where there is much less randomization as it gradually becomes more random in the clockwise direction. Adding 9 allows the randomization to “warm up” so instead of starting at S(0**3) it starts at S(9**3). The output of sine is only -1 to 1 so we need to scale it by 29 to make the offset noticeable.

The Y coordinate is j (1000), for a 1080 pixel high canvas that draws text from the bottom. We will also limit the max width of the font to 1900 pixels which allows us to use larger font that is squished horizontally to fit in the 1920 pixel wide canvas.

The image to the right shows what would happen without the 1900 max width setting, as you can see it needs to be squished quite a bit.

Of course you can change the text to whatever you want, or even draw something besides text! The image to the left shows a simple “broken heart” variation with the heart unicode symbol.

The last thing we need to do is call restore to set the clip region back to default so we can be fresh and ready to start rendering the next frame.

I don’t have any new ideas for saving more space this time so let’s take one final look at the original dweet in all it’s glory!

with(x)save(beginPath(font="85em'")),moveTo(j=1e3,540),clip(arc(j,540,2e3,a=t*6.3+9,a+.1)),restore(t>1||fillText("BROKE",29*S(a**3),j,1900))

That wraps up another dissection. I hope you had fun and learned something new. Take some time to play around with the code, it won’t bite!

If you liked reading this please retweet my tweet about it, each one of these take a lot of work!

I will leave you with a slightly more complex remix with animation, see you next week!

// SMASH - Animated broken mirror effect by Frank Force
c.width|=0;for(i=29;i--;)with(x)save(beginPath(font="70em impact")),moveTo(j=1e3,540),clip(arc(j,540,2e3,q=i/4.61+9,q+.2)),restore(fillText("SMASH",9+29*S(q**4+t),j+49*C(q**5+t),1900))
This entry was posted in JavaScript and tagged , , , . Bookmark the permalink.

One Response to Dissecting A Dweet #6: Breaking Broke

  1. Pingback: My 2019 In Review – Adventures in Tiny Coding | Killed By A Pixel

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.