Lim Chee Aun

Designing Super Silly Hackathon

Two weeks ago, I gave a talk on "CSS-ing the Super Silly Hackathon website" at Talk.CSS #23 Party Edition 🎉 meetup. I realised that I didn't give any talks at all since last year in any meetups, so this is basically my first talk of the year and perhaps my last for this year 😌

Chee Aun speaking about Super Silly Hackathon at Talk.CSS #23 Party Edition

The video of my talk is available on Engineers.SG (YouTube) and the slides are on my talks repository.

It's a 20-minute talk as I try to keep it short, so not everything is revealed. Thus, I'm going to write them down here.

🤔 What is Super Silly Hackathon?

It's an initiative sparked from a Facebook post by Min on September 29, which is then followed by 4 enthusiastic people including myself who somehow got into organizing a totally different kind of hackathon, inspired by The Stupid Hackathon.

In short, instead of focusing on building something to make the world a better place, it's an unhackathon for building something silly that no one ever needs 🤷‍♂️

Turns out there were already few stupid hackathons around the world:

🎨 Design time

For the past few months, I've been sort of active in designing logos for the community 😉 So, I volunteered to design the logo for Super Silly Hackathon and came up with this around October:

First/old version of Super Silly Hackathon banner

The font is Vanilla designed by Cutie Explosion, as I wanted it too look like Comic Sans but not too much. The color scheme and art style is mostly inspired by a conference web site called Keep Ruby Weird and the opening of Boruto anime (Yes, that's Naruto's son).

For some reason, I tilted the text to give the impression of silliness, which later became the art direction of the whole web site. After a while, I felt that there were too many colors competing each other, so I adjusted and reduced the colors a bit:

Updated current version of Super Silly Hackathon banner

All the colors have 100% saturation, no straight lines and no perfect circles 😜

And… UniCat as the mascot (mascat?)!

Unicat, mascot for Super Silly Hackathon

It's everyone's favorite cat with a unicorn-like pink horn, creepy green eyes, cute red nose, two-instead-of-three blue whiskers and a cool purple bow tie. As I mentioned, there are no perfect circles. Every "circle" glyph, including the cat face and eyes, comes from the tittle of the letter 'i' of the Vanilla font!

Super Silly Hackathon designs on Affinity Designer

I use Affinity Designer and all files are open-sourced on GitHub.

😎 Webmaster mode

Over the years, I've always been fascinated by unique website designs, usually inspired by other mediums such as print magazines. I did a large header design for my personal site in 2005 (12 years ago! 😱). I made a horizontal-scrolling Tumblr theme in 2008 (9 years ago! 😱).

Once again, I'm reminded by Hui Jing's talk on drawing inspiration from graphic design and print designs that couldn't be built before, but they can be now.

As I look at the logo and think how I could make it different this time, I wonder if it would be horizontal scrolling again or something else? Horizontal scrolling is not that new anymore, so why not… diagonal? 🤔

Yeah, why not?

Lo and behold.

Super Silly Hackathon website on the iPhone X simulator

This is not a visual or Photoshop trick. There's nothing wrong with the phone frame. I purposely tilt it so that the site itself would look… vertical.

It's pretty difficult for me to explain to people how cool the site is until I tell them to try it on their phones. And when they try it out, I absolutely love the reaction on their faces!

At first, I don't fully understand why I tilt the logo for that impression of silliness. Until I see someone else look at the site on his/her phone, I realised that the person has to tilt the head or the phone to read the site, which in turn make themselves look silly! 🤣

Go on, try the site now at supersillyhackathon.sg. I've also prepared a video to show how the scrolling feels like on Mobile Safari.

Video of Super Silly Hackathon website scrolling

This could be the world's first diagonal-scrolling web site!

I posted a sneak peek on Twitter and Facebook on October 27. I officially launched the site on November 23.

🔮 Diagonal sorcery magic

Some folks ask me how I did the diagonal scrolling thing. I suspect most people thought that it's just transform: rotate(-15deg).

Diagram of the rotated content of Super Silly Hackathon website, with the 'transform-origin' in the middle

Nope, not that easy. By default, CSS transform-origin is at the center of the element. If the whole content (yellow bordered box) is rotated, the top part of the page would be shifted and hidden from the viewport (red bordered box) and no one can see the content 😱

Diagram of up-shifted content of Super Silly Hackathon website, after being rotated

What I need to do is to move the content up, to the viewport area. Now, how do we scroll here? If you scroll downwards, you'll see nothing again because the rotated content is slowly shifted to the right. It's kind of weird. Should the user scroll horizontally to see the other side of the content? What happens if the user pinch zoom out of the page on mobile browsers?

I've made a few attempts.

First attempt: Move the transform-origin. I personally find this weird but if you understand transform-origin well enough, it's possible to actually move things around with it. Usually, we transition or animate elements based on the origin, but now we transition the origin itself!

Diagram of moving the origin of the rotated element, while scrolling down the page

As the page is scrolled downwards, I wrote a piece of JavaScript code to get the top scroll offset of the page and manipulate the transform-origin of the rotated element (yellow bordered box).

It's pretty cool and I was really mind-blown that this actually works!

window.onscroll = function(){
  var top = window.scrollY || window.pageYOffset;
  var origin = top + window.innerHeight/2;
  container.style.transformOrigin = 'center ' + origin + 'px';
}

As simple as… that 😱

Unfortunately, it's a bit slow and my gut feeling tells me that transitioning transform-origin is weird and doesn't feel right.

This leads to my second attempt: translateX CSS with awesome Math.

Photos of my sketches and math

I took out my trusty notebook and started sketching. I looked up the Internet, found the formula that I've forgotten the name and realised that it's called Pythagorean theorem. I made a few mistakes and miscalculations in the beginning but somehow manage to slowly revive my good old Math skills.

Diagram of the arrow lines for downward scrolling and leftward translation of the content

First, the basics; scroll down, move content to the left. I'll need the scrollTop value in addition to the viewport height divided by 2, because the origin is somewhere around there.

Diagram of the arrow lines for the actual left VS the "translateX" direction of the rotated content

Unfortunately, things don't always go the way you want. Turns out that translateX doesn't move to the actual left. Since the content (yellow bordered box) is rotated, the whole X/Y axis has been rotated so translateX will eventually move to the bottom left 😅

At this point, awesome Math is definitely needed to solve the problem.

m = s tanΘ, s = scroll top offset, m = horizontal translation, Θ = 15°

Yeap. m = s tanΘ. s is roughly the scroll top offset, m is the horizontal translation of the content and Θ is 15° because I'm the one who purposely set it.

x = m cosΘ, y = m sinΘ, m = horizontal translation of content, Θ = 15°

Yeap again. x = m cosΘ and y = m sinΘ. In order to move to the real left, I need to get the x and y values, as the content will move to bottom left (x) then move up again (y).

With all these formulas combined, magic happens and everything works exactly the same way as my first attempt! Zero compromise on functionality but more lines of code now:

var rad15deg = 15 * Math.PI / 180;
var tanRad15deg = Math.tan(rad15deg);
var cosRad15deg = Math.cos(rad15deg);
var sinRad15deg = Math.sin(rad15deg);
var bodyStyle = document.body.style;

window.onscroll = function(){
  var top = window.scrollY || window.pageYOffset;
  var a = -top * tanRad15deg;
  var x = a * cosRad15deg;
  var y = a * sinRad15deg;
  bodyStyle.transform = 'rotate3d(0, 0, 1, -15deg) translate3d(' + x + 'px, ' +  y + 'px, 0)';
};

Again, it's still kind of slow 😕. I think probably there's too much Math for the browser to handle?

Onwards to my third attempt: wrap around another div and translateX it.

Diagram of two elements, one being rotated, the other being translated to the left

I wrap the content (yellow bordered box) with another container element div (dotted yellow bordered box). Since this new container is not rotated together with the content, its X/Y axis is not affected! Therefore, I don't need to calculate the sine and cosine values to get the real horizontal translation!

This attempt feels a bit faster and easier to control. With this extra container, I can hide the scrollbars easily with overflow-x: hidden, which will work on both desktop and Mobile Safari (special case here).

One very clear and surprising fact; the site performs well on mobile browsers, but decent on desktop browsers. Usually, mobile browsers have worse performance than desktop browsers, but this time, it's the other way around 🤷‍♂️

Scroll jank on Super Silly Hackathon website

On my machine, the site scrolling is very, very laggy on Firefox. I have no idea why. After revealing the site, I've gotten a few feedback from some people...

Damn. On my monitor, for every scroll down, the entire thing is sort of like vibrating. My eyes died.

Then I use another Mac and the scroll is fluid. Ahhhh...

— Kiong

It doesn't work well on one Mac, but works well on another Mac 🤔

Because the background is not fixed, scrolling on a mouse wheel creates a pretty strong flicker.

— Ted

I suspect mouse-wheel scrolls at certain number of lines, so it "jumps" at specific transition points instead of smoothly transitioning between them with a smooth scroll 🤔

🕵️‍♂️ Research time

At this point, I don't know what's the cause of the problem. There are a few speculations in my mind, but I'll need to research.

One of the first things that I stumble upon is the term "scroll jank". It took me a while to find out the official definition of this term but I gave up and use this instead:

When you scroll a page and there's such a delay that the page doesn't feel anchored to your finger, that's called scroll jank.

Improving Scroll Performance with Passive Event Listeners

This explanation seems to focus more on finger touch latency when scrolling web pages, but can be applied to normal trackpad or mouse scrolling as well.

Ultimately this leads to the discovery of passive event listeners. I looked up Can I use… and was surprised to see a lot of browsers now support it. I've also read about Firefox Quantum's latest Async Pan/Zoom (APZ) feature which decouples the scrolling from the JavaScript thread. Apparently I was confused by all these terms as I know that both are meant for making sure that nothing blocks scrolling, one is meant for touch events, and the other is a browser-side implementation.

I've also discovered Animation Worklet, while reading about Scroll-linked effects on MDN. It's quite a long read and honestly I just skimmed through keywords like "sticky positioning", "scroll-snapping" and "web animations". Animation Worklet was previously known as "Compositor Worker" since around 2015. There's an additional proposal to Web Animations specification that's for mapping scroll position to time and use it as a timeline for the animation. 🤯

Okay, let me step back a bit and rethink this over.

Scroll jank was a problem last time. Back in 2011, John Resig once wrote about attaching handlers to the window scroll event as a very, very bad idea. This leads to the term "Jank Busting" (2012) by preventing frame rate drops, debouncing scroll events, using requestAnimationFrame and ensuring a jank-free experience. Fast forward to today, browser developers have kind of solved this by introducing new methods, specifications and implementations.

But, why am I still seeing this lag on the Super Silly Hackathon website? 🤔

As I was trying to fix the jank issue, I saw this warning on Firefox Developer Tools' Console:

"will-change" warning on Firefox Developer Tools' Console

I applied the will-change CSS property on the whole rotated content and got this warning message:

Will-change memory consumption is too high. Budget limit is the document surface area multiplied by 3 (409431 px). Occurences of will-change over the budget will be ignored.

Whoa. From my basic understanding of this property, I see will-change property as a better version of the translate3d(0, 0, 0) or translateZ(0) hacks as mentioned by Sara Soueidan's article on Everything You Need to Know About the CSS will-change Property (2014).

It's like asking the browser that I want one (or more than one) specific CSS property to be optimized magically and the browser will somehow answer "Yes". In this case, the browser answered "No" 😭. A little bit more reading reveals that one of the proper usage of will-change is:

Don't apply will-change to too many elements. The browser already tries as hard as it can to optimize everything. Some of the stronger optimizations that are likely to be tied to will-change end up using a lot of a machine’s resources, and when overused like this can cause the page to slow down or consume a lot of resources.

MDN web docs: will-change

This is when it hits me.

Reducing paint areas is often a case of orchestrating your animations and transitions to not overlap as much, or finding ways to avoid animating certain parts of the page.

Simplify Paint Complexity and Reduce Paint Areas

I stepped back and look at the web site again…

The Super Silly Hackathon web site diagram with two "thinking face" emoji characters

I see two very important points.

  • Large paint areas. The whole rotated content is a huge large paint area. Technically speaking, the more content I put, the larger the paint area gets 😅
  • Animate everything. The whole rotated content is translated horizontally when scrolling. Instead of animating only certain parts of the page, everything on the page is animated 😅

All this while, I've been doing things that are against all these best practices. Instead of minimizing paint areas, compositions, transitions and animations, everything is maximised! 😅😅😅 Despite these realizations, I'm still dumbfounded by the fact it's slower on a desktop than on a mobile 🙄

So in the end, I still have no idea what I'm doing but at least I learnt a few things 😊. For the past few days, I've been trying out a few methods to speed things up and I hope that I could somehow solve this 😁. For those of you who asked me if I found a solution yet, the answer is no for now 😬.

🦄 Trivia time!

Conforming to the name and theme of this hackathon, I tried my very best, that no one ever was, to have as much fun as possible building the site 😂

I hid a lot of fun stuff in the source code and was secretly hoping that someone would view source and read them. I didn't reveal them during my talk due to shortage of time and me being usually tired after a whole day of work 😜

On December 2, I'm super glad that finally someone view the source and found a few gems, though not all of them 😝

Trivia #1: I use emoji characters as CSS class names, animation names and even font names!

@font-face {
  font-family: '🍦';
  src: url('fonts/vanilla.woff2') format('woff2'), url('vanilla.woff') format('woff');
}

...

h1, h2, h3, button {
  font-family: 🍦, sans-serif;
}

...

.🦄🐱 {
  filter:
    drop-shadow(10px 10px 0 rgba(0, 14, 102, .85))
    drop-shadow(-10px -10px 0 rgba(0, 14, 102, .85));
  animation: 😼 2s infinite ease-in-out alternate;
}

@keyframes 😼 {
  0% { transform: rotateZ(5deg) }
  100% { transform: rotateZ(-5deg) }
}

Trivia #2: I invented <em oji>, which is the em tag with the oji attribute. Technically speaking, oji attribute is invalid but still, it can be styled with CSS, like this:

em[oji]{
  font-style: normal;
  display: inline-block;
  animation: 🌀 10s infinite linear;
}

Don't try this at home!

Trivia #3: I use Viewport Unit Based Typography, mainly inspired by one of Zell's talks and Vitaly Friedman's talk, using this magic formula:

font-size calc() for responsive and fluid typography

Trivia #4: The site embraces the iPhone X notch. I don't own an iPhone X so I could only test this in the iPhone Simulator. I followed some of the new features highlighted on Designing Websites for iPhone X and added this meta tag:

<meta name='viewport' content='initial-scale=1, viewport-fit=cover'>

Here's a before and after comparison:

Super Silly Hackathon website on the iPhone X simulator, with the notch

Before applying the meta tag, the site doesn't bleed to the edges, but after that, it does. Do note that the site does not respect the safe areas at all. The reasoning is… it doesn't have to, due to its diagonal scrolling nature 😎

From the very beginning, this diagonal design already works in the "dangerous" areas, even for normal rectangular screens. Think about it, technically this layout can work for any shapes of screens without the need for applying spaces for safe areas. Personally I see all this hype on iPhone X notch as a small first step towards a non-rectangular-screen future.

Trivia #5: Due to parallax-ish and titled nature of the design, the site also implements the prefers-reduced-motion media feature which is meant for folks who experience motion sickness and vertigo. It's only supported on Safari for now, on both iOS and macOS. Web developers can test this locally by turning on the 'Reduce Motion' option in Accessibility settings.

Super Silly Hackathon website with and without  media feature

When "Reduced Motion" is turned on, I've made a few adjustments:

  • Use background-attachment: fixed to make the background image stays and reduce the "parallax" effect.
  • Fade out the repeating background image.
  • Make the backgrounds of the sections to be less transparent.
  • Un-tilt the whole content, make it vertical.
  • Disable all animations and transitions.

Disclaimer: I'm not 100% sure if these help at all since I don't have vestibular disorders or any tools to test this. There is a pretty detailed post on A List Apart, Designing Safer Web Animation For Motion Sensitivity which helps much.

And… that's all! 5 trivia. Or rather, "Easter eggs"? 🤷‍♂️

🦄 And so it begins…

On Saturday December 9th, we organised the Super Silly Hackathon starting at 9AM for setup, registration and stuff.

The t-shirts are super cute 😍:

Super Silly Hackathon unicat t-shirt
Super Silly Hackathon unicat on many colorful t-shirts

The stickers are super cute too 😍:

Super Silly Hackathon stickers, one of them is the unicat

There are more photos on the Super Silly Hackathon Facebook page photo album! Videos of all presentations from 17 teams are also up, thanks to the wonderful Engineers.SG.

I'm also quite pleased that the Super Silly Hackathon site was featured on Hui Jing's Team 486's presentation where they serve the site, uh… locally, from a IBM PS/1 Consultant 2133 19C machine 😱

Throughout the event, everything went surprisingly well with too many swags, over-fed participants, chill co-hacking environment, and great demos & presentations with lots of enthusiasm and laughter 😊

🎬 The aftermath

It's been a pleasure to be part of this fun-filled event. Hearing positive compliments from people about my designs and the event always make me feel warm and fuzzy inside. ☺️

To be honest, I didn't really put much thought into the designs. Most of the time, I just wing it with random colors, shapes and sizes. There are no grids, golden ratio or anything. I didn't know how things will turn out or how the design will look like in real life or how people would feel about it.

It's always that sense of uncertainty and doubt, which sometimes when things go well, I'll probably say it's "pure luck".

A friend told me this, which made me paused for a while:

Not lucky, it's because of your years of experience 👍

I guess I'll accept that for now 😊