JS1k 2015, the yearly 1kb JavaScript contest, is around the corner and kuvos asked a couple of optimizer extraordinaires to open the show. Hopefully this little invitation will tingle the spider sense of talented developers and code golfers in time for them to submit high quality entries to JS1k 2015.
JS1k 2015
The yearly 1kb JavaScript contest, JS1k is comming! Get ready for a few weeks of crazy code golfing-
Workflow
For this unexpected project I tried a slightly different workflow.
Normally I build several prototypes of the idea, figure the most compact one, optimize it by hand to the max, check in a JavaScript packer how things go and optimize again from there. This works. This is pretty much how I've done all my productions. But this can be stressing when the deadline approaches and the size limit remains far.
Instead this time, I built a prototype of the idea, refined it until the visuals were interesting enough, ran the code through a RegPack, optimized for the packer and kept iterating on the project, always keeping a close eye on the packed size.
The resulting code is verbose and I'm not sure if this approach is more size efficient but the whole experience was smoother.
Also I save and test the code constantly. In a work environment I would use Git, but this brings too much friction to my "demo coding" workflow. It's a shame because I often wish I had more tracks of the progress. This time, I regularly pushed the code to a private gist. This also made it simple to share the progress with others using bl.ocks.org.
Revisions
The gist is now public. You can check all the revisions of the JS1k invitation.
To see individual revisions in actions, open the list of revisions on Github, view the gist at a given revision and replace the github.com
by bl.ocks.org
in the address field of your browser. I wish there was an easier way to do this. Although this might be an interesting pet project.
Gotchas
This workflow was smooth but I met two gotchas.
- SublimeText barfs some of the control characters used by the packer. Of course this breaks the code. So I had to use a Hex editor, again, to copy paste the packed code into the shim. I've got to look into this.
- RegPack does check the black list of variables when it preprocesses the code to do the hashing of the Canvas methods. It produced hashing code overwrite the variable holding the canvas context. Doh!
Together this two gotchas cost a day. Luckily there was hardly any time pressure for this release.
Fix
Trying to be a good citizen, I fixed the global variable overwriting issue in RegPack. As expected this was simple to fix. Glad this will benefit other developers.
Inspiration
When searching for images related to JS1k 2015, Emily Dove Gross's beautiful wedding stationeries stood out. The visuals are whismical and fitting. Not to mention I really wish Emily did our stationeries. Check out her artwork!
Technique
To render the train tracks and forest, it seemed obvious to treat the elements as particles and use a circular buffer to store their distance from the track. To make things easier and more compact, the x
coordinate modulo 3 determines whether to draw the tracks, or a particle above/below the tracks. There are 3 kinds of particles: grass, trees & texts.
Also, since the file size was not a big problem this time, I could use the verbose performance.now()
and requestAnimationFrame
to be more mobile friendly, and add a nice intro with a custom cut-out typography. The first versions of the intro used the system font. This was very compact but gave zero control about the exact position, size and shape of the glyphs so I went on to craft a nice path spelling JS1k.
Source code
Here is the original, and commented, source code.
// JS1k 2015 Invitation by Mathieu 'p01' Henri
// JavaScript code to use along with the shim of JS1k
// Compresses down to ~1020 bytes with Uglify + RegPack
(u=function(){
requestAnimationFrame(u);
a.width=x=1023;
a.height=h=x*innerHeight/innerWidth|1;
H=performance.now()/15|1;
c.textAlign='center';
c.font='2em cursive';
q=170;
c.fillStyle='hsl('+q+',20%,55%)';
c.fillRect(0,0,1023,h);
w=555;
for(;x--;){
s=x%3?H++&1||-1:0;
// exisiting || new portion of tracks + distance of the "particle"
c[H&1023]=x&&c[H&1023]||8+Math.random()*h/2;
// Y coordinate & parallax
Y=Math.sin(H/511-Math.sin(H/511*3)*3)*48+s*c[H&1023]+h/2;
X=1023-x+(511-x)*Y/h;
c.globalAlpha=1;
q=170;
// "particles"
if (s) {
// Adjust the color based on the Y coordinate
q+=Y&31;
c.fillStyle='hsl('+q+',55%,200%)';
if (H%511) {
// GRASS "particle"
// Adjust the color based on the Y coordinate, again
if (Y&1)
c.fillStyle='hsl('+q+',55%,20%)';
// vertical strands of grass
c.fillRect(X+Math.sin(q++)*3,Y+Math.sin(q++),1,5);
c.fillRect(X+Math.sin(q++)*3,Y+Math.sin(q++),1,5);
c.fillRect(X+Math.sin(q++)*3,Y+Math.sin(q++),1,5);
// little bits of grass
c.fillRect(X+Math.sin(q++)*48,Y+Math.sin(q++)*3,1,1);
c.fillRect(X+Math.sin(q++)*48,Y+Math.sin(q++)*3,1,1);
c.fillRect(X+Math.sin(q++)*48,Y+Math.sin(q++)*3,1,1);
// TREE "particle"
// wih varying intensity of the forest
if (!(H%(9+Math.sin(H/511/3)*3|1))) {
// Get the height of the tree
z=Y&15;
z+=6;
Y-=z*6;
c.globalAlpha=.3;
// Draw multiple triangles to make up a pine tree
for (;z--;) {
c.beginPath();
c.lineTo(X,Y);
c.lineTo(X-2*z+Math.sin(q++)*3,Y+6*z+Math.sin(q++)*3);
c.lineTo(X+2*z+Math.sin(q++)*3,Y+6*z+Math.sin(q++)*3);
c.fill();
}
}
} else {
// TEXT "particle"
c.fillText(['Cross browser','1kb demos','February 2015','JS1k 2015'][H/511&3],X,Y);
}
} else {
// tracks
c.fillStyle='hsl('+q+',55%,2%)';
c.fillRect(X,Y,1,5);
c.fillRect(X,Y,5,1);Y+=2;
c.fillRect(X,Y,5,1);
// TRAIN
if(x==w){
// wagons
X-=10;
c.fillRect(X,Y,20,-10);
// locomotive
if(x<511){
c.fillRect(X,Y,10,-15);X+=15;
c.fillRect(X,Y,2,-15);
// steam
c.fillStyle='hsl('+q+',55%,200%)';
c.globalAlpha=.3;
q=H;
z=9;
for(;z--;){
c.fillRect(X+Math.sin(q++)*3,Y-6*z+Math.sin(q++)*3,z,z);
}
} else {
w-=15;
}
}
}
}
// Cut out JS1k typography
c.globalCompositeOperation='destination-in';
H=Math.sin(H/511/2+10)*31+31;
H=H<3?9:H*H;
c.translate(511,h/2);
c.rotate(H/511+.1);
c.scale(H,H);
X=Y=-3;
c.beginPath();
// J
c.moveTo(X,Y);
c.arc(X-2,Y+3,2,0,Math.PI/2,0);
// S
X+=2;
Y+=4;
c.moveTo(X,Y);
c.arc(X,Y-1,1,Math.PI/2,Math.PI/2*3,1);
c.arc(X,Y-3,1,Math.PI/2,Math.PI/2*3,0);
// 1
X+=3;
c.moveTo(X,Y);
c.arc(X-2,Y-5,2,0,Math.PI/2,0);
// k
X+=2;
c.moveTo(X,Y);
c.lineTo(X,Y-6);
X+=3;
c.moveTo(X,Y);
c.arc(X-1,Y-2,1,Math.PI/2*3/2,Math.PI/2*3,0);
c.stroke();
})()
Feedback ?
For now my 4 years old daughter likes the little train and forest once the JS1k logo has moved out of the way, but she wondered why she couldn't hear the train. I would appreciate any help to explain why daddy didn't manage to make a proper 'choo-choo' train in 1kb.
Also you can find the JS1k 2015 invitation at Pouet.net. Suggestions and comments are welcome. But above all, go and make an entry for JS1k.
Other recent projects
There are many experiments and projects like JS1K 2015 INVITATION to discover other here.
- FRONTFEST MOSCOW It was an honour to be invited to Fronfest Moscow 2017 with the little family to give my first workshop; implementing a Twin-stick shooter using ES6 and Canvas, and to continue my CODE🎙ART series of talks + live coding aiming to inspire new web developer artists. on November 18th, 2017
- VOLTRA VOLTRA: Grinding the Universe, a gritty JavaScript demo, winner of the 1024 bytes demo competition at the Assembly 2017. on August 6th, 2017
- BREATHING EARTH Another take on Nadieh Bremer mesmerizing Breathing Earth visualisation, running at 60fps on a 2D Canvas without libraries or frameworks. on June 26th, 2017
- IMPOSSIBLE ROAD Back in October 2014, a good friend of mine, showed me IMPOSSIBLE ROAD and half joked that I should do a remake for JS1k the yearly 1kb JavaScript contest happens in Spring. This set my mind on hyperdrive. on March 14th, 2015
- DRAGON PUNCH An itsy bitsy Dragon curve renderer in 121 bytes. on February 26th, 2014
- THREAD The "10 print" maze generator in 15 bytes of x86 assembler. on December 19th, 2013
- ANDES The Andes are the world longest mountain range: 7,000 km and 1,022 bytes of JavaScript, making for a nice entry for Assembly 2013 on August 3rd, 2013
- PNEBULA Almost the first 256b intro using Canvas ever :p on September 30th, 2008
Let's talk
Don't be shy; get in touch by mail, twitter, github, linkedin or pouet if you have any questions, feedback, speaking, workshop or performance opportunity.