As a followup to our "announcement" earlier today for April Fools, we had a few people interested in how we added such a buzz to our website, so we wanted to spill the... bees? Okay, no more bees puns, we promise. ๐ If you missed the announcement, you can find the bees here. This post talks about how we implemented this, and also provides the full source code for this fun project.
Initial Discussion
The original joke stemmed from some internal communication after a hilarious moment on our stream, so we created a super quick in-line edit of our website, added some bees, and took a screenshot. It was cute, but everyone internally seemed to really enjoy it, and quickly it was requested for us to make this something we could share with everyone for April Fools.
Preparation
We'd already written a small amount of JavaScript that we were running directly on the page, so wanted to take this and build from it for a small April Fools project. Being already familiar with Cloudflare Workers and specifically HTMLRewriter, this seemed like a perfect fit, as we would be able to inject and manipulate a few things on the page, without adding any additional workload to our team and our infrastructure directly. If you're not familiar with Cloudflare Workers, we'd recommend reading through one of our previous writeups where we go into detail about the platform and its benefits whilst migrating an entire website over to it.
HTMLRewriter Usage
The HTMLRewriter API available in Cloudflare Workers allows us to manipulate our page contents dynamically with ease. With this capability, we wanted to do a few things:
Inject our client-side script and style for the bees. This was our primary objective.
Rewrite a few
meta
tags such asrobots
to ensure this page didn't get indexed by search engines.And then finally, rewrite a few image URLs we knew wouldn't work being proxied.
Client-side Script
The meat and potatoes of this April Fools was the JavaScript code that we inject into (almost) every page on the site. It spawns a bunch of bees relative to the viewport of the device, each at different sizes and positions. A small number of bees also animate, and an even smaller number become "shiny" bees, with a different color.
// get random between 2 numbers (inclusive)
const randomBetween = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
const viewportHeight = document.body.clientHeight;
const viewportWidth = document.body.clientWidth;
const fragment = document.createDocumentFragment();
const container = document.createElement('div');
container.className = 'bees';
fragment.append(container);
let startLeft = 100;
let beesNum = Math.floor(viewportHeight / 12);
if(viewportWidth < 1000){
// (probably) mobile, start as early as we can, reduce number
startLeft = 0;
beesNum = Math.floor(beesNum / 4);
}
for(let i = 0; i < beesNum; i++){
const img = document.createElement('img');
img.src = 'https://nodecraft.com/assets/images/sales/spring-2021/icon.svg';
img.height = '50';
img.width = '50';
img.classList.add('bee');
img.style.top = `${randomBetween(0, viewportHeight)}px`;
img.style.left = `${randomBetween(startLeft, viewportWidth - 100)}px`;
img.style.height = `${randomBetween(20, 50)}px`;
// every 8 or so, animate
if(i % 8 === 0){
img.classList.add('bee-animated');
img.style['animation-delay'] = `${randomBetween(0, 10)}s`;
}else{
img.style.transform = `rotate(${randomBetween(180, 360)}deg)`;
}
// every 18 or so, use a shifted color
if(i % 18 === 0){
img.style.filter = `hue-rotate(${randomBetween(0, 360)}deg)`;
}
container.append(img);
}
document.body.append(fragment);
The code is a little rough and could be optimised to run better, but it was "good enough" for our joke. There were a few minor CSS styles to inject too.
Injecting Code Into Page
Now that we had our working script, we wanted to inject this to the page using Cloudflare Workers. This is where HTMLRewriter
shines. We created a rewriter that appended our script and style elements at the end of the body
element, using something like this:
class BodyHandler {
element(element){
element.append(`<style>${beeCss}</style><script>${beeJs}</script>`, {
html: true
});
}
}
async function handleRequest(request){
const url = new URL(request.url);
url.host = 'nodecraft.com';
const nodecraft = await fetch(url);
const transformed = new HTMLRewriter().on('body', new BodyHandler()).transform(nodecraft);
return transformed;
}
One of the great things about HTMLRewriter
is its streaming-native capabilities. We don't have to wait for the entire page to be downloaded by the worker, parsed, manipulated, and then sent on to the client - this happens in real time as the data is received, making it take a median of less than 1ms CPU time, and therefore delivered to clients almost instantaneously.
Other Interesting Bits
Some of the bees (every 8 or so) will bounce, with a random animation delay offset. This was accomplished using a simple CSS animation. The animation start offset makes them feel a whole lot more random, even if the animation is exactly the same.
It turns out that it's (as far as we can tell) impossible to proxy
/cdn-cgi
requests using Workers. This makes sense, as this path is controlled by Cloudflare, but it did mean that a lot of our images were broken, as we make heavy use of Cloudflare's Image Resizing product, which uses these URLs. We were able to work around this by adding anotherHTMLRewriter
to our script and rewriting everyimg
'ssrc
andsrcset
attributes to remove the resizing URLs. It's not a perfect solution, as we're now loading unnecessarily large images in some places on our website, but it was fine for this joke.We deploy a strict Content Security Policy on our website which meant we had to do a bit of work to manipulate this to get it to work correctly. Check the full source for more information.
Did you notice the "shiny" bees? Every 18 or so will be spawned with a random
hue-rotate
offset.
Open Source
We've open sourced the script in its entirety on our GitHub. If you have any questions or comments, don't hesitate to let us know!
Closing Thoughts
Cloudflare Workers and HTMLRewriter
remain a fantastic tool for any developer to hold in their toolbelt. They allow rapid prototyping, testing, and deployment of experiments on top of existing sites, as well as the ability to publish new sites in their entirety.