Accessible sidebar drawer with great UX

If you are on mobile, or if you shrink the window, you’ll see a hamburger menu with a toggleable sidebar. This is functional with javascript disabled, although some javascript was added to enhance the experience. So, how do you implement it?

Start by adding two radio buttons. One for the “on” button, one for “off” button. The label button is a UX trick to dismiss the modal by clicking outside of the menu. Make sure to add the aria-hidden attribute to these so screen readers don’t pick it up. The state of the drawer is kept in these radio buttons.

<input type="radio"
  class="menu-toggle open"
  name="menu-toggle"
  title="Open"
  aria-hidden="true"
  tabindex="-1"
  id="ta_menuOpen">
<input type="radio"
  class="menu-toggle close"
  name="menu-toggle"
  title="Close"
  aria-hidden="true"
  id="ta_menuClose"
  tabindex="-1">
<label class="menu-toggle overlay hidden"
  for="ta_menuClose"
  tabindex="-1"></label>

This CSS makes the radio buttons act like a menu toggle in the top right corner. When the menu is open, the z-index of the close button is put to the top. At the same time, the overlay is brought up behind the menu which also acts like a close button. The menu is optimized for right-handed users, which is the majority of the population. Sorry, left-handers!

.menu-toggle, .menu-toggle.overlay, .theme-toggle {
  position: fixed;
  opacity: 0;
  display: block;
}

.menu-toggle.overlay {
  top: 0;
  right: 100%;
  width: 100%;
  height: 100%;
}

.menu-toggle.open, .menu-toggle.close {
  width: 40px;
  height: 40px;
  cursor: pointer;
  top: -1px;
  right: -1px;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent; 
}

.menu-toggle.open {
  z-index: 500;
}

.menu-toggle.open:checked ~ .menu-toggle.close {
  z-index: 500;
}

.menu-toggle.out {
  width: 100%;
  height: 100%;
}

.menu-toggle.open:checked ~ .menu-toggle.overlay {
  right: 0;
  position: fixed;
  z-index: 100;
}

The nav bar is kept to the right of the screen at all times. When the menu bar is toggled, a css selector applies a transform to bring out the navigation drawer with a neat animation using a css transition. Here, I set the animation time to 0.25s which feels appropriate. To the best of my knowledge, the menu must be a sibling of the radio button, but that may not be necessaary with newer combinators I haven’t learned how to fully utilize yet. Something extra I do for my site is I offset the content with as well. This is why I have the radio buttons on the same level as the content. One other thing to note is this sidebar drawer is turned into a topnav with a media query, when the window is greater than 800px.

<div class="nav-bar-container" id="ta_navbarContainer">
  <nav class="nav-bar">
    <!--Nav bar contents-->
  </nav>
</div>
.nav-bar .menu {
  right: 0;
  transform: translateX($nav-bar-width-plus-border);
  transition: transform 0.25s;
  position: fixed;
  width: $nav-bar-width;
  top: 0;
  height: 100vh;
  padding-top: $nav-bar-height;
  box-shadow:-4px -4px 18px -9px rgba(0,0,0,0.77);
}

.menu-toggle.open:checked ~ .nav-bar-container .nav-bar {
  transform: translate(0, -$nav-bar-height-plus-border);
}

Voila! You should have a functioning drawer!

Enhancing with vanilla javascript

While we can definitely call it done here, there’s another user experience trick we can add: gestures. On most of the apps on my phone, the drawer can be opened by swiping the edge of the screen, closed by swiping the drawer closed. This is pretty easy to implement in pure vanilla Javascript. First I’ll paste the code, and then I’ll go over it.

function initDraggableDrawer() {
  const beginEvents = ["mousedown", "touchstart"];
  const releaseEvents = ["mouseup", "mouseleave", "touchend", "touchcancel"];
  const moveEvents = ["mousemove", "touchmove"];

  function addEventListeners(target, events, method, options) {
    for(let i of events) {
      target.addEventListener(i, method, options);
    }
  }

  function removeEventListeners(target, events, method) {
    for(let i of events) {
      target.removeEventListener(i, method);
    }
  }

  function getEventX(e) {
    if(e.targetTouches) {
      if(e.targetTouches[0]) {
        return e.targetTouches[0].clientX;
      } else {
        return lastX;
      }
    } else {
      return e.clientX;
    }
  }

  function getStyleTransformX(element) {
    let style = window.getComputedStyle(menu);
    return new DOMMatrixReadOnly(style.transform).m41;
  }

  function updateTransform(target, value) {
    if(value > 0) {
      value = 0;
    }
    let style = window.getComputedStyle(menu);
    let matrix = new DOMMatrix(style.transform);
    matrix.m41 = value;
    target.style.transform = matrix.toString();
  }

  function checkForDrawerOpenGesture(e) {
    if(document.body.clientWidth - getEventX(e) < 30) {
      prepareDrag(e);
    }
  }

  function addGestureEventListeners() {
    addEventListeners(menu, beginEvents, prepareDrag, {passive: true});
    addEventListeners(document, beginEvents, checkForDrawerOpenGesture, {passive: true});
  }

  function prepareDrag(e) {
    removeEventListeners(menu, beginEvents, prepareDrag);
    removeEventListeners(document, beginEvents, checkForDrawerOpenGesture);
    addEventListeners(document, moveEvents, drag, {passive: false});
    addEventListeners(document, releaseEvents, release, {once: true, passive: false});
    lastX = initialX = getEventX(e);
    transformStart = getStyleTransformX(menu);
    timeStart = new Date().getTime();
    dragged = false;
    updateTransform(menu, transformStart);
    menu.style.transition = "none";
  }

  function drag(e) {
    lastX = getEventX(e);
    let delta = initialX - lastX;
    let transformCurrent = transformStart - delta;
    if(dragged || Math.abs(delta) > 15) {
      dragged = true;
      if(e.cancelable) {
        e.preventDefault();
        e.stopImmediatePropagation();
      }
    }
    updateTransform(menu, transformCurrent);
  }

  function release(e) {
    removeEventListeners(document, moveEvents, drag)
    addGestureEventListeners();
    menu.style.transform = menu.style.transition = null;
    if(dragged && e.cancelable) {
      e.preventDefault();
      e.stopImmediatePropagation()
    }
    /* Check that the user didn't potentially tap/click*/
    if(!dragged) return;
    var timeEnd = new Date().getTime(),
        diff = initialX - getEventX(e);
    // the logic in here is manually tuned for user experience
    if( /* Check if it is over half*/
      ((transformStart + diff) > -(menu.clientWidth/2)) ||
        /* Check if the user pulled fast and it's over 1/8 */
      (timeEnd - timeStart < 100 /*ms*/ && diff > (menu.clientWidth/8))
    ) {
      document.querySelector(".menu-toggle.open").checked = true;
    } else {
      document.querySelector(".menu-toggle.close").checked = true;
    }
    initialX = null;
  }

  let menu = document.getElementById("ta_hamburgerMenu"),
          initialX, lastX, transformStart, timeStart,
          dragged = false;
  addGestureEventListeners();
}

If you look at the bottom of the function, it gets the ta_hamburgerMenu element and calls addGestureEventListeners. The first event listener in addGestureEventListeners adds the event mouseDown and touchStart to the navigation drawer. If it is open, a drag gesture is initiated as long as the mouse is held down. The second event listener calls checkForDrawerOpenGesture, which initiates a drag gesture if the right 30 pixels of the screen were pressed.

Now let’s look at the prepareDrag function. It starts by removing event listeners that were used to. When a drag gesture is finished, those event listeners are added back. This is to prevent this function from being called multiple times without using an extra state boolean. Then, it adds event listeners for dragging and for releasing the drag. Then it keeps track of the initial X position, the start time, and we set the boolean dragged to false, so micro drags caused by taps/clicks are not tracked. Finally, we stop the manual css transform by setting transition to “none” and manually setting the transform. A nice side-effect of this is if you were to release and restart a drag gesture, you can catch the transform mid-transition. That’s why we are getting and setting the css transform manually in the getStyleTransformX and updateTransform functions. The updateTransform function just does a check to make sure the drawer isn’t dragged beyond its size and then updates the transform css property. The state of the transform is kept in css at all times.

The drag method, which is triggered on touch move or mouse move, does some calculations based on the mouse/touch positions to delta and the beginning of the transformation to determine the current transform position. The only other thing this does, is if the movement is less than 15 pixels, then we don’t consider this a drag gesture at all, tracked by the “dragged” boolean. This is so users don’t click a link, make a micro movement and have their click event cancelled. Finally, the transform is set.

The release method first resets the state by removing the movement event listeners (the release event listener is marked as “once”), adds back the event listeners for preparing a drag event and resets the inline styles for transition and transform. If a drag gesture was detected (at least 15px of movement), it then does a check to see if it should open or close. As noted, the logic was manually tuned: First, if the menu was pulled half-way open, it opens. Likewise, if it is pulled halfway closed, it closes. The second part of the if statement checks for if the edge of the screen was rapidly swiped open.

Bonus: Hamburger icon with animation

As a bonus, this is my hamburger icon. The three span elements are for a cute css animation. The hamburger icon turns into an X when the menu is toggled.

<div class="hamburger semisticky" id="ta_hamburger">
    <span></span>
    <span></span>
    <span></span>
</div>
.hamburger {
    display: block;
    position: absolute;
    right: 18px;
    top: 18px;
    z-index: 400;
    transition: transform 0.25s;
}

.hamburger span {
    display: block;
    width: 20px;
    height: 1px;
    border-radius: 3px;
    margin-bottom: 5px;
}

.menu-toggle.open:checked ~ .hamburger span {
    transition: 0.5s;
}

.menu-toggle.open:checked ~ .hamburger span:nth-last-child(1) {
    transform: rotate(135deg) translate(-4.5px, 4.5px);
}

.menu-toggle.open:checked ~ .hamburger span:nth-last-child(3) {
    opacity: 0;
    transform: rotate(135deg) translate(-10px, 3px);
}

.menu-toggle.open:checked ~ .hamburger span:nth-last-child(2) {
    transform: rotate(-135deg)
}

The past decade of blogging

Before today, there was only one blog post on my site, from 2020, helloable.c. I’m pretty sure nobody has read it. I don’t know, I don’t use analytics, but I never advertised it. My primary motivation for even writing that was to combat imposter syndrome I felt at the time, even though already had 4 years of professional experience at that point. If I could offer a useful blog post, maybe I could be a “real” developer, I thought. Pretty silly reasoning, really! I had a draft for the longest time about using #define in C to implement compile-time templates, which I never finished because the quality didn’t measure up. Honestly, I don’t care to finish it.

Today, I merged my programming and game development related posts from two of my older blogs (and a private journal entry) so that I could start tracking my past. It made me feel kind nostalgic. I’ve been trying to make a successful game for the past decade as a solo developer, and I didn’t, yet. What I did have, was a successful eight years of professional software development, a fun time doing improv comedy, and great personal development. While I am allowing myself to feel nostalgia for the past and wonder what could have been if I sacrificed those things for developing a game, I do not regret it. Instead, I feel excitement for the future.

Juggling personal projects, work, friends, and leisure life is not easy. No matter, I set unrealistic expecations. I expected too much from myself. I started projects with much excitement and gave up on them. I felt guilt and shame for not following through. It affected my social life. If I was playing video games, out socializing, living my life, or even working, I felt guilt that I wasn’t working on my game. Not only was that an unhealthy mindset, the stress I put on myself to accomplish my goals was unproductive in achieving them.

If there is one thing I could say that I regret, it’s not writing more. Nate Silver put out a blog post a few days ago titled Always. Be. Blogging. which while I don’t care to be a successful blogger, inspired me to reflect today. I read my past blogs and merged them here, starting from over a decade ago. I’m excited that maybe one day, this journey, which has been a dream I’ve had since at least the age of 5 or 6 will be inspiring for someone else. I wish I wrote more about it. Documented or not however, I understand my past of starting projects and not finishing them is hardly unique even if it were better documented. Instead, I’ll just quickly take a look at what I’ve done in the past, and just make a promise: I’ll be blogging a lot more.

A quick look at the past decade of my personal projects

Punching Out Cthulhu (project name)

Starting in 2011 while I was in high school, This was my first serious attempt at making a video game. It started out Sure I’ve dabbled, but this time I was serious. When it came to coding, I struggled to make any progress. This game became my hyperfixation up until 2014-2015 when I started City Night. My plan started out as me making a silly mobile game about punching monsters based on punch-out. If I stuck to that, I think I could have been successful. Instead, since it became my hyperfixation, it turned into an epic story in my head about love, betrayal, loss , So… scope creep. Maybe someday I’ll pick this back up.

Peewees

I thought I would mention this because I found a reference to it when going through christianbaum.com on archive.org. I made a barely functional demo for this in 2013, and I lost the source code. This was originally going to be a lemmings / pikmin style management game. I probably won’t pick this up.

City Night

I started this game as a winter game jam game in late 2014. It was supposed to be a cute little NES-style adventure showing how hard it is to survive in a city without a support system. Had I stuck with the original scope of this game, I have no doubt I would be done with this. Like Punching Out Cthulhu, I didn’t, because my perfectionism refused to stick with the original scope. If you start at the beginning and scroll through older posts of this blog, you can see how things quickly got out of control of from what was originally a simple game.

I think I would like to finish the original version of the game in a game jam and also explore ideas I had later on.

Kat’s Dream

This started out as an infinite runner While my mind loved to try and sabotage it with scope creep, I actually did publish this to Google Play, (source code at Kat’s Dream), but it was quite bad, nobody downloaded it and it was delisted last year for failure to target Google’s newer Android API, so I don’t really count that. I might also pick this one back up some day with the thoughts I had.

Stray Kitty

This is probably the most successful thing I’ve done! It has 50k users on the Chrome web store https://chromewebstore.google.com/detail/stray-kitty/pdiefgmeejbkamgippdjdchpgkdnelbl?hl=en and the source code can be found here. https://github.com/xianbaum/StrayKitty. Sometimes I think about expanding on this with more cat behaviors and porting it to other platforms, but I’m not sure, really. Funnily enough, I get offer emails about purchasing it all the time. The most recent offer was $5,000. I’m not going to do that.

I want to write more, but after adding all those posts to my blog and writing this, it’s almost 6am.

City Night Journal Entry

Note: Below is the only journal entry I have about City Night in a private file. I don’t see why I didn’t make a public blog post about this. Maybe it was too vulnerable for me to say at the time.

Last time I visited this document was 4 years ago, and 4 years before that. But I like the thought of keeping a journal. I’m renaming this document from timeline to journal. That sounds a little more comforting than a timeline. And I think I will be a little more proud updating this document. I feel a little sick opening this document I started. Eight years, this game has been circling around in my mind.

I have arthritis. It hurts a bit to type this, in addition to the pain of being reminded by this document,so that’s annoying. I’m getting older. 27. If I don’t start working on this daily, I won’t have a game at all. This year I decided it would make more sense to release the game in parts. But maybe it should be even smaller than that. Episodes, maybe.

This game is very ambitious, so I think to release in episodes is the wisest choice.

Helloable.c

I want to write a little bit about object oriented C, and how to implement patterns seen in languages like C++ in C, hopefully without sacrificing performance or elegance. I created helloable.c as a C interface implementation for comparable C++, in helloable.cpp.

C programmers usually code to interfaces in three ways: One, they use #ifdef drop-in implementations, two, function pointers in structs, or three, a pointer to a vtable. My implementation uses a vtable, but my implementation also goes an extra step to minimize CPU instructions to match the C++ equivalent implementation. that is fully comparable to how interfaces work in languages like Java. One reason might be that interfaces have overhead. While coding a web app in Java, you might code to an interface without giving a second thought. But coding for embedded systems or legacy machines, you might want to consider something else. Overhead can be avoided entirely if implementations are swapped out using ifdefs at compile time. I take advantage of this in cngine. However, that doesn’t satisfy every reason to use an interface in object oriented languages.

Requirements of the implementation:

  • Be at least partially encapsulated, as much as C allows
  • Be able to be contained in a collection of other types without sacrificing functionality
  • Cast to and from the interface
  • Generate comparable assembly to a C++ implementation

Three years ago, I made an effort to get close to C++ implementation down to the assembly, without being ugly and keeping abstractions as much as C allows.

Single inheritance in C

typedef struct
{
  Being base;
  char *breed;
} Animal;

Single inheritance is very simple in C. The very first member of a struct can be cast directly into the first member. One caveat is when accessing the Animal directly, you need to either cast it to Being, or call the base member. You can’t call the base members directly from animal, like you can in C++ or Java. Another drawback is if a struct is declared on the stack or globally, you can only cast to base class with a pointer.

Animal* animal;
// ...
// Pretend animal is a valid, populated Animal.
// This is valid in C.
Being* b = (Being*)animal;
// You can access Being->x with animal->base->x, or the casted b->x

Interface inheritance in C

In order to fully satisfy interfaces, three things are required.

Create the struct for the interface

struct Helloable{
  void (*SayHello)(void);
  void (*SayGoodbye)(struct Helloable const **helloable, char *name);
};

Put the interface you want to implement as a struct member

typedef struct
{
  Animal base;
  int barks_per_minute;
  Helloable *helloable;
} Dog;

After declaring the functions (or here, implementing them) create a vtable unique for that struct

void Dog_PrintStats(Dog *dog);
void Dog_SayHello(void);
void Dog_SayGoodbye(Helloable **helloable, char *name);
Helloable DogHelloableVtable = { Dog_SayHello, Dog_SayGoodbye };

Optionally, use the constructor pattern to for creation functions

Dog *Dog_Create()
{
  Dog *dog = malloc(sizeof(Dog));
  dog->helloable = &(DogHelloableVtable);
  return dog;
}

Have a way to access a the struct implementing an interface struct

The interface pointer needs to be passed into the function when called, but how can we access members outside of the interface struct? A naive way to do this is to have a pointer at the top of the interface struct for the dervi pointer, but that causes several issues. The first issue is struct size. If every vtable needs to contain a pointer, then every vtable needs to be copied into the struct instead of having a global vtable, increasing size.

However, using the offsetof keyword defined in stddef.h, we can cast to and from the base type simply given. This works well because each struct type can have its own unique implementation, so we know exactly what we are casting to and from. You can use these macros as a template for casting to and from the interface.

#define ToHelloable(casting) &(casting->helloable)
#define FromHelloable(type, helloable) \
  ((type*)((char*)helloable - offsetof(type, helloable)))

We can access Dog and all of its properties, or Being and all of its properties using FromHellable.

void Dog_SayGoodbye(Helloable **helloable, char *name)
{
  Being *self = (Being*)FromHelloable(Dog, helloable);
  printf("Woof-woof, from %s to %s\n", self->name, name);
}

While you can just get the helloable by accessing member thing->helloable, I wrote a ToHelloable macro to complement the FromHelloablemacro. Here, it’s used to cast dog, cat and person to helloable.

  my_helloable_array[0] = ToHelloable(dog);
  my_helloable_array[1] = ToHelloable(cat);
  my_helloable_array[2] = ToHelloable(person);

Collections

I demostrated it working by having a collection of helloables. When calling SayHello and SayGoodbye, the caller knows nothing about the implementation, but the functions are unique and successfully access members of Dog, Cat, and Person.

  for (i = 0; i < 3; i++)
  {
    Helloable **helloable = my_helloable_array[i];
    (*helloable)->SayHello();
    (*helloable)->SayGoodbye(helloable, "Jack");
  }

Drawbacks and things to consider

Performance

On modern machines, having everything coded to an interface is cheap. But on legacy machines and embedded hardware, it’s not so much. Having to dereference so many function pointers is not exactly ideal when you need to squeeze every last bit of performance.

Syntax and macros

If a base struct implements an interface and a derived struct doesn’t, due to how the offsetof macro works, you need to cast the struct to the base interface or pass the base member. If both a base struct and a derived struct implement an interface differently, you should not cast to the base struct as it will use the base struct implementation instead of the derived struct implementation.

Refactoring and abstraction

While it might be nice to know explicitly which function is virtual or not due to the syntax of calling a function pointer versus a normal function, the abstraction between the two are gone. If you decide to make the void function Cat_PrintStats an interface, then you’ll probably need to change every call to Cat_PrintStats to accommodate this change. Also, by having pointers to vtables containing pointers to functions, if called explicitly, it is painfully obvious which function is called via interface and which function is not. The same issues arise if you want to go from an interface to a normal function.

Memory management

Depending on your memory management patterns, inheritance could cause some confusion if you lose your reference to the base class. If you free a pointer to an interface, you will likely crash your program or cause memory corruption. Memory management can be made easier using this using the destructor pattern by putting a destructor in an interface or base struct.

More

I wrote some more notes in the actual gists for the struct. Check out Complete gist for helloable.c and the C++ equivalent gist, helloable.cpp.

I wrote a lot about the implementation years after I actually implemented it. I might re-visit the implementation more with fresh eyes and change it a little bit. I don’t want to mislead people, so if I made a mistake writing, please contact me.

Lua implementation: Day 3

The lua console is essentially complete. At this point, it should be very easy to add anything I may need to it. I don’t need anything specific right away, so I plan on adding features as I need them and be done with the console. There is a tiny bug with the console buffer printing old strings that I may or may not fix - it’s visible in the video above. I get a warm feeling just playing with it, seeing characters pop-up and be removed dynamically. Is that weird? :)

Are you programming a game in a C compatible language and interested in integrating Lua in your game? Do it! The documentation is in-depth and there is a great community at lua-users.org It is easy to use. For me, the most helpful sections of the documentation was that of usertypes, and peaking at an example of usertypes in action.